Skip to content

readAxivity()` fails with "object 'prevStart' not found" when only the final block is requested #98

Description

@j262byuu

What's going on

readAxivity(start = n - 1, end = n), where n is the number of data blocks in the file, errors with:

object 'prevStart' not found

readAxivity() reads blocks [start, end). The block at start is read into prevRaw before the main loop, while the block at end is used only to determine the ending timestamp when it exists. So start = n - 1, end = n requests exactly one block: the final one. Any range that starts earlier works, including a full read.

Reproducible example

Uses only the fixtures shipped with the package, on unmodified GGIRread 1.0.9:

library(GGIRread)   # 1.0.9

f <- system.file("testfiles/ax3_testfile.cwa", package = "GGIRread")
n <- (file.info(f)$size - 1024) / 512    # 145 data blocks

readAxivity(f, start = n - 3, end = n, progressBar = FALSE)   # ok, 362 rows
readAxivity(f, start = n - 1, end = n, progressBar = FALSE)   # Error: object 'prevStart' not found
Image

Same on ax6_testfile.cwa (283 blocks) with start = 282, end = 283.

Observed:

ax3_testfile.cwa     start=144 end=145 -> ERROR: object 'prevStart' not found
ax3_testfile.cwa     start=142 end=145 -> ok, 362 rows
ax3_testfile.cwa     start=  1 end=145 -> ok, 17477 rows

ax6_testfile.cwa     start=282 end=283 -> ERROR: object 'prevStart' not found
ax6_testfile.cwa     start=280 end=283 -> ok, 120 rows
ax6_testfile.cwa     start=  1 end=283 -> ok, 11388 rows

Cause

The main loop is:

for (ii in (start+1):end) {

Its first branch is taken when ii == numDBlocks:

GGIRread/R/readAxivity.R

Lines 529 to 530 in 45ef623

newTimes = (prevRaw$start - prevStart) / prevLength * prevRaw$length + prevRaw$start
prevLength = prevRaw$length

But prevStart and prevLength are normally assigned only in the other branch:

GGIRread/R/readAxivity.R

Lines 570 to 571 in 45ef623

prevStart = prevRaw$start
prevLength = prevRaw$length

So when the loop's first iteration is already the terminal-block branch (aka when only the final block is requested) that branch assumes state that would normally have been established by an earlier ordinary loop iteration.

prevLength is also unassigned at that point; prevStart is simply evaluated first in the expression, which is why it is the name reported in the error.

This appears to be more than just two missing initializations. The terminal branch also relies on other state normally created during a preceding iteration. For example, the ordinary branch initializes the first timestamp in rawTime, and later code assigns:

prevRaw = raw

where raw is normally created in that branch. In the single-final-block path, none of that setup has occurred.

This also explains why longer ranges work: at least one ordinary iteration runs before the final-block branch and establishes the expected state.

Regression point

In 0ef4cb5d the extrapolation was explicitly guarded on exactly this state:

if (prevRaw$start >= start & pos <= nr & exists("prevStart") & exists("prevLength")) {

By
Commit 0522630 "read requested cwa blocks, instead of requested 300-sample pages" the guard was gone and the terminal-block extrapolation had become unconditional.

So the refactor moved the loop onto requested CWA blocks, but retained terminal-block logic that assumes a preceding ordinary iteration has run. The earlier exists() guard suggests this missing-state case had previously been handled explicitly and was lost during the refactor.

Why it may matter in practice

GGIR reads CWA files in chunks, and the chunk size can be adjusted at runtime by updateBlocksize().

If a file happens to leave exactly one data block for the final chunk, that final call can become equivalent to:

readAxivity(..., start = n - 1, end = n)

and fail on that file even though a full read succeeds.

This is probably uncommon, but difficult to predict from user-facing settings because the effective chunk size can change during processing.

Fix considerations

I initially considered initializing prevStart and prevLength from prevRaw before the loop, but I do not think that is sufficient or necessarily correct.

For the single-final-block case, doing:

prevStart  = prevRaw$start
prevLength = prevRaw$length

would make:

(prevRaw$start - prevStart)

equal to zero, so the extrapolated newTimes would collapse to prevRaw$start rather than estimating the end of the final block from a genuine preceding-block interval.

The terminal path also depends on additional state normally established by an earlier iteration.

A correct fix may therefore need either:

  • to read the preceding block as timing context when the requested range contains only the final block, without returning that preceding block; or
  • to define a separate fallback rule for extrapolating the final block's timestamps when no preceding block is available.

I have not opened a PR because I am not certain which timestamp semantics are intended for this boundary case. Happy to prepare one once that intent is confirmed.

Environment

GGIRread 1.0.9
R 4.5.2
Windows

Line references above are permalinks pinned to the 1.0.9 source.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions