From 43b750c43f6a15e7f6ccd8029aa4b550b32511b4 Mon Sep 17 00:00:00 2001 From: l-k- Date: Tue, 28 Nov 2023 23:29:45 -0500 Subject: [PATCH 1/2] adding tests for processing .cwa blocks with failed checksum --- ...ile_corrupt_blocks_0_13_14_142_143_144.cwa | Bin 0 -> 75264 bytes tests/testthat/test_readAxivity.R | 230 ++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100755 inst/testfiles/ax3_testfile_corrupt_blocks_0_13_14_142_143_144.cwa diff --git a/inst/testfiles/ax3_testfile_corrupt_blocks_0_13_14_142_143_144.cwa b/inst/testfiles/ax3_testfile_corrupt_blocks_0_13_14_142_143_144.cwa new file mode 100755 index 0000000000000000000000000000000000000000..9af76baba37f9babba926614fa4e5cc0e075a25b GIT binary patch literal 75264 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 literal 0 HcmV?d00001 diff --git a/tests/testthat/test_readAxivity.R b/tests/testthat/test_readAxivity.R index 4959645..18aa401 100644 --- a/tests/testthat/test_readAxivity.R +++ b/tests/testthat/test_readAxivity.R @@ -24,6 +24,236 @@ test_that("readAxivity reads data from AX3 file correctly", { }) +test_that("readAxivity handles failed checksums correctly", { + # read the blocks 1-11 from the non-corrupt file + cwafile = system.file("testfiles/ax3_testfile.cwa", package = "GGIRread")[1] + AX3 = readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 1, end = 12) + + # From now on we'll be reading a file with corrupt blocks 0, 13, 14, 142, 143 and 144 (which is the last block) + cwafile = system.file("testfiles/ax3_testfile_corrupt_blocks_0_13_14_142_143_144.cwa", package = "GGIRread")[1] + + # Since block 0 is corrupt, then if we start reading from that block, it gets skipped, and the results will be + # as if we read starting from block 1. No imputation will be done. + expect_warning(AX3_0_12 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 0, end = 12), + "Skipping corrupt block #0") + expect_equal(AX3_0_12$header$device, "Axivity") + expect_equal(nrow(AX3_0_12$data), nrow(AX3$data)) + expect_equal(floor(sum(abs(AX3_0_12$data[,2:4]))), floor(sum(abs(AX3$data[,2:4])))) + + expect_false(is.null(AX3_0_12$QClog)) + expect_equal(nrow(AX3_0_12$QClog),1) + expect_false(AX3_0_12$QClog$checksum_pass[1]) + expect_false(AX3_0_12$QClog$imputed[1]) + expect_equal(AX3_0_12$QClog$blockID_current[1], 0) + expect_equal(AX3_0_12$QClog$blockID_next[1], 1) + + # if we read blocks 1-11, results should be the same as for the non-corrupt file, + # and there should be nothing in QClog + expect_warning(AX3_1_12 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 1, end = 12), + "Skipping corrupt block #0") # the warning is there b/c block 0 is always read, and it's corrupt + + expect_equal(AX3_1_12$header$device, "Axivity") + expect_equal(nrow(AX3_1_12$data), nrow(AX3$data)) + expect_equal(floor(sum(abs(AX3_1_12$data[,2:4]))), floor(sum(abs(AX3$data[,2:4])))) + expect_true(is.null(AX3_1_12$QClog)) + + # Now read a bigger chunk of the file, so that the corrupt blocks 13-14 fall in the middle + expect_warning(AX3_1_20 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 1, end = 20), + "Skipping corrupt block #13") + expect_equal(AX3_1_20$header$device, "Axivity") + expect_equal(nrow(AX3_1_20$data), 2306) + expect_equal(ncol(AX3_1_20$data), 7) + expect_equal(floor(sum(abs(AX3_1_20$data[,2:4]))), 3244) + + # there should be 3 entries in QClog: one each for blocks 13 and 14 with failed checksums (these initially get skipped, not imputed), + # and one for block 12 that preceeds the corrupt blocks, because data needs to be imputed from block 12 (inclusive) to 15 (exclusive) + expect_false(is.null(AX3_1_20$QClog)) + expect_equal(nrow(AX3_1_20$QClog),3) + + expect_false(AX3_1_20$QClog$checksum_pass[1]) + expect_false(AX3_1_20$QClog$imputed[1]) + expect_equal(AX3_1_20$QClog$blockID_current[1], 13) + expect_equal(AX3_1_20$QClog$blockID_next[1], 14) + + expect_false(AX3_1_20$QClog$checksum_pass[2]) + expect_false(AX3_1_20$QClog$imputed[2]) + expect_equal(AX3_1_20$QClog$blockID_current[2], 14) + expect_equal(AX3_1_20$QClog$blockID_next[2], 15) + + expect_true(AX3_1_20$QClog$checksum_pass[3]) + expect_true(AX3_1_20$QClog$imputed[3]) + expect_equal(AX3_1_20$QClog$blockID_current[3], 12) # we finally found the first valid block after #12, so we imputed block #12, up to 15 + expect_equal(AX3_1_20$QClog$blockID_next[3], 15) # the first valid block after 12 is 15 + + # check that data in blocks 12-14 got imputed with the correct values + + nrow11 = nrow(AX3_1_12$data) + imputedValues = AX3_1_12$data[nrow11,2:4] + VectorG = sqrt(sum(imputedValues^2)) + imputedValues = imputedValues / VectorG + + expect_true(all(abs(imputedValues - AX3_1_20$data[nrow11+1,2:4] ) < .0001)) + expect_true(all(abs(imputedValues - AX3_1_20$data[nrow11+2,2:4] ) < .0001)) + expect_true(all(abs(imputedValues - AX3_1_20$data[nrow11+100,2:4] ) < .0001)) + expect_true(all(abs(imputedValues - AX3_1_20$data[nrow11+200,2:4] ) < .0001)) + expect_true(all(abs(imputedValues - AX3_1_20$data[nrow11+360,2:4] ) < .0001)) + expect_equal(sum(abs(imputedValues)), + sum(abs(AX3_1_20$data[(nrow11+1) : (nrow11+360), 2:4])) / 360, + tolerance = .0001, scale = 1) + + # make sure a request for only corrupt blocks returns an error but doesn't crash + expect_warning( + expect_error(readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 13, end = 14), + "All requested blocks are corrupt"), + "Skipping corrupt start block #13") + expect_warning( + expect_error(readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 14, end = 15), + "All requested blocks are corrupt"), + "Skipping corrupt start block #14") + expect_warning( + expect_error(readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 13, end = 15), + "All requested blocks are corrupt"), + "Skipping corrupt start block #13") + + # Now see what happens if the corrupt blocks fall in the very beginning of the requested interval. + # These blocks should be skipped and the file should be read from the first non-corrupt block. + # No blocks will be imputed. + # So reading blocks 13-20 or 14-20 should give the same output as reading blocks 15-20. + + expect_warning(AX3_13_20 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 13, end = 20), + "Skipping corrupt start block #13") + expect_warning(AX3_14_20 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 14, end = 20), + "Skipping corrupt start block #14") + expect_warning(AX3_15_20 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 15, end = 20), + "Skipping corrupt block #0") # the warning is there b/c block 0 is always read, and it's corrupt + + expect_equal(nrow(AX3_13_20$data), nrow(AX3_15_20$data)) + expect_equal(floor(sum(abs(AX3_13_20$data[,2:4]))), floor(sum(abs(AX3_15_20$data[,2:4])))) + + expect_equal(nrow(AX3_14_20$data), nrow(AX3_15_20$data)) + expect_equal(floor(sum(abs(AX3_14_20$data[,2:4]))), floor(sum(abs(AX3_15_20$data[,2:4])))) + + expect_false(is.null(AX3_13_20$QClog)) + expect_equal(nrow(AX3_13_20$QClog),2) + + expect_false(AX3_13_20$QClog$checksum_pass[1]) + expect_false(AX3_13_20$QClog$imputed[1]) + expect_equal(AX3_13_20$QClog$blockID_current[1], 13) + expect_equal(AX3_13_20$QClog$blockID_next[1], 14) + + expect_false(AX3_13_20$QClog$checksum_pass[2]) + expect_false(AX3_13_20$QClog$imputed[2]) + expect_equal(AX3_13_20$QClog$blockID_current[2], 14) + expect_equal(AX3_13_20$QClog$blockID_next[2], 15) + + # Now see what happens if the corrupt blocks 13-14 fall at the very end of the requested interval + # (but not at the end of the file). + # These blocks should be skipped, and the file should be read until + # the first non-corrupt block that follows the corrupt ones (so block 15). + # Data will be imputed from block 12 (inclusive) to 15 (exclusive). + # So reading blocks 1-13 or 1-14 should give the same output as reading blocks 1-15. + + expect_warning(AX3_1_13 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 1, end = 13), + "Skipping corrupt end block #13") + expect_warning(AX3_1_14 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 1, end = 14), + "Skipping corrupt end block #14") + expect_warning(AX3_1_15 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 1, end = 15), + "Skipping corrupt block #13") + + expect_equal(nrow(AX3_1_13$data), nrow(AX3_1_15$data)) + expect_equal(floor(sum(abs(AX3_1_13$data[,2:4]))), floor(sum(abs(AX3_1_15$data[,2:4])))) + + expect_equal(nrow(AX3_1_14$data), nrow(AX3_1_15$data)) + expect_equal(floor(sum(abs(AX3_1_14$data[,2:4]))), floor(sum(abs(AX3_1_15$data[,2:4])))) + + expect_false(is.null(AX3_1_13$QClog)) + expect_equal(nrow(AX3_1_13$QClog),3) + + expect_false(AX3_1_13$QClog$checksum_pass[1]) + expect_false(AX3_1_13$QClog$imputed[1]) + expect_equal(AX3_1_13$QClog$blockID_current[1], 13) + expect_equal(AX3_1_13$QClog$blockID_next[1], 14) + + expect_false(AX3_1_13$QClog$checksum_pass[2]) + expect_false(AX3_1_13$QClog$imputed[2]) + expect_equal(AX3_1_13$QClog$blockID_current[2], 14) + expect_equal(AX3_1_13$QClog$blockID_next[2], 15) + + expect_true(AX3_1_13$QClog$checksum_pass[3]) + expect_true(AX3_1_13$QClog$imputed[3]) + expect_equal(AX3_1_13$QClog$blockID_current[3], 12) # we finally found the first valid block after #12, so we imputed blocks [12, 15) + expect_equal(AX3_1_13$QClog$blockID_next[3], 15) # the first valid block after 12 is 15 + + # Now make sure corrupt blocks at the very end of the file are handled correctly (blocks 142-144). + + # make sure a request for only corrupt blocks returns an error but doesn't crash + expect_warning( + expect_error(readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 142, end = 143), + "All requested blocks are corrupt"), + "Skipping corrupt end block") + expect_warning( + expect_error(readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 142, end = 144), + "All requested blocks are corrupt"), + "Skipping corrupt end block") + expect_warning( + expect_error(readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 142, end = 145), + "All requested blocks are corrupt"), + "Skipping corrupt end block") + expect_warning( + expect_error(readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 143, end = 144), + "All requested blocks are corrupt"), + "Skipping corrupt end block") + expect_warning( + expect_error(readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 143, end = 145), + "All requested blocks are corrupt"), + "Skipping corrupt end block") + expect_warning( + expect_error(readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 144, end = 145), + "All requested blocks are corrupt"), + "Skipping corrupt end block") + + # If there are corrupt blocks at the very end of the file, then the last non-corrupt block is + # treated as the end block and no imputation is done. + # So in our case, since the end blocks 142-144 are corrupt, they are ignored and the file is + # treated as if it had only 142 blocks (0-141) + expect_warning(AX3_140_141 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 140, end = 141), + "Skipping corrupt block #0") + expect_warning(AX3_140_142 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 140, end = 142), + "Skipping corrupt end block") + expect_warning(AX3_140_143 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 140, end = 143), + "Skipping corrupt block") + expect_warning(AX3_140_144 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 140, end = 144), + "Skipping corrupt end block") + expect_warning(AX3_140_145 <- readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 140, end = 145), + "Skipping corrupt end block") + + expect_equal(nrow(AX3_140_141$data), nrow(AX3_140_142$data)) + expect_equal(floor(sum(abs(AX3_140_141$data[,2:4]))), floor(sum(abs(AX3_140_142$data[,2:4])))) + + expect_equal(nrow(AX3_140_141$data), nrow(AX3_140_143$data)) + expect_equal(floor(sum(abs(AX3_140_141$data[,2:4]))), floor(sum(abs(AX3_140_143$data[,2:4])))) + + expect_equal(nrow(AX3_140_141$data), nrow(AX3_140_144$data)) + expect_equal(floor(sum(abs(AX3_140_141$data[,2:4]))), floor(sum(abs(AX3_140_144$data[,2:4])))) + + expect_equal(nrow(AX3_140_141$data), nrow(AX3_140_145$data)) + expect_equal(floor(sum(abs(AX3_140_141$data[,2:4]))), floor(sum(abs(AX3_140_145$data[,2:4])))) + + # If the file has more consecutive corrupt blocks than allowed by maxAllowedCorruptBlocks, + # we should get an error. + + expect_warning( + expect_error(readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 1, end = 20, maxAllowedCorruptBlocks=1), + "Error reading file. 2 consecutive blocks are corrupt."), + "Skipping corrupt block #13") + + expect_warning( + expect_error(readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 140, end = 144, maxAllowedCorruptBlocks=2), + "Error reading file. The last 3 blocks are corrupt."), + "Skipping corrupt end block #144") +}) + + test_that("readAxivity reads data from AX6 file correctly", { cwafile = system.file("testfiles/ax6_testfile.cwa", package = "GGIRread")[1] AX6 = readAxivity(filename = cwafile, desiredtz = "Europe/Berlin", start = 1, end = 4) From 8be2db62afa19bcd837392de7ad3cddccbe9ccff Mon Sep 17 00:00:00 2001 From: l-k- Date: Tue, 28 Nov 2023 23:30:18 -0500 Subject: [PATCH 2/2] handle failed checksum in various parts of file --- R/readAxivity.R | 192 +++++++++++++++++++++++++++++++++++++-------- man/readAxivity.Rd | 5 +- 2 files changed, 164 insertions(+), 33 deletions(-) diff --git a/R/readAxivity.R b/R/readAxivity.R index ec52ec7..049a048 100755 --- a/R/readAxivity.R +++ b/R/readAxivity.R @@ -1,9 +1,11 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desiredtz = "", configtz = c(), interpolationType = 1, loadbattery = FALSE, - header = NULL, frequency_tol = 0.1) { + header = NULL, frequency_tol = 0.1, + maxAllowedCorruptBlocks = 20) { if (length(configtz) == 0) configtz = desiredtz blockBytes = 512 headerBytes = 1024 + # Credits: The original version of this code developed outside GitHub was # contributed by Dr. Evgeny Mirkes (Leicester University, UK) #======================================================================== @@ -75,6 +77,28 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desire return(NULL) } + # sampling rate in one of file format U8 at offset 24 + samplerate_dynrange = readBin(block[25], integer(), size = 1, signed = FALSE) + + if (samplerate_dynrange != 0) { # Very old files that have zero at offset 24 don't have a checksum + checksum = sum(readBin(block, n = 256, + integer(), + size = 2, + signed = FALSE, + endian = "little")) + checksum = checksum %% 65536 # 65536 = 2^16; the checksum is calculated as a 16-bit integer + if (checksum != 0) { + # Checksum doesn't match. This means some bits in this block got corrupted. + # We don't know which, so we can't trust this block. We skip it, and impute it later. + rawdata_list = list( + struc = struc, + parameters = parameters, + checksum_pass = FALSE + ) + return(invisible(rawdata_list)) + } + } + idstr = readChar(block, 2, useBytes = TRUE) if (idstr != "AX") { stop("Packet header is incorrect. First two characters must be AX.") @@ -114,22 +138,6 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desire } else { battery = 0 } - # sampling rate in one of file format U8 at offset 24 - samplerate_dynrange = readBin(block[25], integer(), size = 1, signed = FALSE) - - checksum_pass = TRUE - if (samplerate_dynrange != 0) { # Very old files that have zero at offset 24 don't have a checksum - # Perform checksum - checksum = sum(readBin(block, n = 256, - integer(), - size = 2, - signed = FALSE, - endian = "little")) - checksum = checksum %% 65536 # equals 2^16 the checksum is calculated on a 16bit integer - if (checksum != 0) { - checksum_pass = FALSE - } - } # offset 25, per documentation: # "top nibble: number of axes, 3=Axyz, 6=Gxyz/Axyz, 9=Gxyz/Axyz/Mxyz; @@ -244,7 +252,7 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desire length = blockLength, struc = struc, parameters = parameters, - checksum_pass = checksum_pass, + checksum_pass = TRUE, blockID = blockID ) if (complete) { @@ -302,14 +310,33 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desire accrange = bitwShiftR(16, (bitwShiftR(samplerate_dynrange, 6))) version = readBin(block[42], integer(), size = 1, signed = FALSE) #offset 41 - # Read the first data block without data - datas = readDataBlock(fid, complete = FALSE) - if (is.null(datas)) { - stop("Error reading the first data block.") + # Read the first data block without data. + # Skip any corrupt blocks, up to maxAllowedCorruptBlocks in number. + is_corrupt = TRUE + for (ii in 0:min(numDBlocks-1, maxAllowedCorruptBlocks)) { + datas = readDataBlock(fid, complete = FALSE) + + if (is.null(datas)) { + stop("Error reading the first data block.") + } + + if (datas$checksum_pass) { + is_corrupt = FALSE + break + } + + warning("Skipping corrupt block #", ii) } + if (is_corrupt) { + if (ii==numDBlocks-1) { + stop("Error reading file. Every block is corrupt.") + } + stop("Error reading file. The first ", maxAllowedCorruptBlocks+1, " blocks are corrupt.") + } + if (frequency_header != datas$frequency) { warning("Inconsistent value of measurement frequency: there is ", - frequency_header, " in header and ", datas$frequency, " in the first data block ") + frequency_header, " in header and ", datas$frequency, " in the first data block. ") } blockLength = datas$length # number of samples in a block @@ -340,6 +367,9 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desire on.exit({ close(fid) }) + + QClog = NULL # Initialise log of data quality issues + ############################################################################# # read header if (is.null(header)) { @@ -368,14 +398,59 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desire struc = list(0,0L,0) if (end < numDBlocks) { # the end block isn't part of the data we'll read, but its start will be our ending timestamp seek(fid, headerBytes + blockBytes * end, origin = 'start') - endBlock = readDataBlock(fid, struc = struc) + + # Skip any corrupt blocks, up to maxAllowedCorruptBlocks in number. + # Skip forward, so we'll end up reading more blocks than requested. + # This is important because when corrupt blocks are in the beginning of the requested interval, + # we also skip forward, ending up with fewer blocks than requested. + # for example, if we have a file with corrupt blocks 8-12, and we read the file 10 blocks at a time, + # then the 1st request will end up with blocks 1-12, and the 2nd request with blocks 13-20, + # and between the two requests, all 20 blocks will be accounted for. + is_corrupt = TRUE + for (ii in end : min(numDBlocks-1, end+maxAllowedCorruptBlocks)) { + endBlock = readDataBlock(fid, struc = struc) + + if (endBlock$checksum_pass) { + is_corrupt = FALSE + break + } + + warning("Skipping corrupt end block #", ii) + end = end + 1 + } + if (is_corrupt && ii == end+maxAllowedCorruptBlocks) { + stop("Error reading file. The last ", maxAllowedCorruptBlocks+1, " blocks are corrupt.") + } endTimestamp = as.numeric(endBlock$start) - } else { + } + + if (end == numDBlocks) { # end == numDBlocks, meaning we'll be reading all the remaining blocks. # There is no block #numDBlocks, so we can't get the ending timestamp from the start of that block. - # Instead read the very last block of the file, and project what the ending timestamp should be. - seek(fid, headerBytes + blockBytes * (end-1), origin = 'start') - lastBlock = readDataBlock(fid, struc = struc) + # Instead read the very last block of the file (if the last block is corrupt, fing the last non-corrupt one), + # then project what the ending timestamp should be. + + # Skip any corrupt blocks, up to maxAllowedCorruptBlocks in number. + # SInce we are at the last block, we have to go backwards. + is_corrupt = TRUE + for (ii in (end-1) : max(start, end-maxAllowedCorruptBlocks-1)) { + seek(fid, headerBytes + blockBytes * ii, origin = 'start') + lastBlock = readDataBlock(fid, struc = struc) + + if (lastBlock$checksum_pass) { + is_corrupt = FALSE + break + } + + warning("Skipping corrupt end block #", ii) + end = end - 1 + } + if (is_corrupt) { + if (start == end) { + stop("Error reading file. All requested blocks are corrupt.") + } + stop("Error reading file. The last ", maxAllowedCorruptBlocks+1, " blocks are corrupt.") + } # the end timestamp should fall right after the actual very last timestamp of the file endTimestamp = as.numeric(lastBlock$start) + blockLength * step # now pad it generously in case there are gaps in the last block @@ -386,7 +461,36 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desire # Reinitiate file and skip header as well as the initial start-1 blocks seek(fid, headerBytes + blockBytes * start, origin = 'start') pos = 1 # position of the first element to complete in data - prevRaw = readDataBlock(fid, struc = struc) + + # Skip any corrupt blocks, up to maxAllowedCorruptBlocks in number. + is_corrupt = TRUE + for (ii in 1 : min((end-start), maxAllowedCorruptBlocks+1)) { + prevRaw = readDataBlock(fid, struc = struc) + + if (prevRaw$checksum_pass) { + is_corrupt = FALSE + break + } + QClog = rbind(QClog, data.frame(checksum_pass = FALSE, + blockID_current = start, # the actual block ID wasn't read b/c the block is corrupt + blockID_next = start+1, + start = 0, + end = 0, + blockLengthSeconds = 0, + frequency_blockheader = 0, + frequency_observed = 0, + imputed = FALSE)) + + warning("Skipping corrupt start block #", start) + start = start + 1 + } + if (is_corrupt) { + if (start == end) { + stop("Error reading file. All requested blocks are corrupt.") + } + stop("Error reading file. The first ", maxAllowedCorruptBlocks+1, " blocks are corrupt.") + } + if (is.null(prevRaw)) { return(invisible(list(header = header, data = NULL))) } @@ -414,7 +518,7 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desire rawTime = vector(mode = "numeric", blockLength + 2) rawPos = 1 - QClog = NULL # Initialise log of data quality issues + consecutiveCorrupt = 0 # Read the data for (ii in (start+1):end) { @@ -431,6 +535,31 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desire } else { # read a new block raw = readDataBlock(fid, struc = struc, parameters = prevRaw$parameters) + if (!raw$checksum_pass) { + # If the checksum doesn't match, we can't trust any of this block's data, + # so we have to completely skip the block. + # Depending on the nature of the faulty block, the data for the time period it represented + # will probably get imputed later, once we encounter a block with a valid checksum. + QClog = rbind(QClog, data.frame(checksum_pass = FALSE, + blockID_current = ii, # the actual block ID wasn't read b/c the block is corrupt + blockID_next = ii + 1, + start = 0, + end = 0, + blockLengthSeconds = 0, + frequency_blockheader = 0, + frequency_observed = 0, + imputed = FALSE)) + warning("Skipping corrupt block #", ii) + + consecutiveCorrupt = consecutiveCorrupt + 1 + if (consecutiveCorrupt > maxAllowedCorruptBlocks) { + stop("Error reading file. ", maxAllowedCorruptBlocks+1, " consecutive blocks are corrupt.") + } + + next + } + + consecutiveCorrupt = 0 if (is.null(raw)) { # this shouldn't happen @@ -472,7 +601,6 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desire if ((ii < numDBlocks && raw$blockID != 0 && raw$blockID - prevRaw$blockID != 1) || - prevRaw$checksum_pass == FALSE || frequency_bias > frequency_tol) { # Log and impute this event doQClog = TRUE @@ -499,7 +627,7 @@ readAxivity = function(filename, start = 0, end = 0, progressBar = FALSE, desire } if (doQClog == TRUE) { # Note: This is always a description of the previous block - QClog = rbind(QClog, data.frame(checksum_pass = prevRaw$checksum_pass, + QClog = rbind(QClog, data.frame(checksum_pass = TRUE, blockID_current = prevRaw$blockID, blockID_next = raw$blockID, start = prevRaw$start, diff --git a/man/readAxivity.Rd b/man/readAxivity.Rd index 491d1cd..7a5c296 100644 --- a/man/readAxivity.Rd +++ b/man/readAxivity.Rd @@ -9,7 +9,7 @@ \usage{ readAxivity(filename, start = 0, end = 0, progressBar = FALSE, desiredtz = "", configtz = c(), interpolationType=1, loadbattery = FALSE, - header = NULL, frequency_tol = 0.1) + header = NULL, frequency_tol = 0.1, maxAllowedCorruptBlocks = 20) } \arguments{ \item{filename}{ @@ -55,6 +55,9 @@ differs by more than 5\%, but if this is less than frequency_tol the block will not be imputed. } + \item{maxAllowedCorruptBlocks}{ + Max number of consecutive blocks with a failed checksum that we'll tolerate. + } } \value{