Skip to main content

Parsing Raw Files

PPG2 and Accelerometer data are too voluminous to store as individual database rows, so the SDK writes them to binary .wiff files instead. Raw File Transfer & Export covers how those files get onto the phone and how to export them. This page covers how to decode them into samples inside your app.

The binary layout itself is documented in Raw files.

Note: You do not need these parsers to export files, and the SDK does not parse .wiff files into the database on your behalf. Use them when your app needs the individual samples, for example to draw a waveform or run your own analysis.

The parsers

ParserProducesData
Ppg2WiffParserPpg2WiffSlotModelPPG2 (green, red, infra-red)
AccWiffParserAccWiffSlotModelAccelerometer

Both are constructed with an optional logger and expose a single method:

fun run(files: List<File>, listener: ParserListener<T>?)

Warning: A single run() call accepts at most 100 files. Above that the parser does no work and reports the problem through onError.

ParserListener

Parsing is asynchronous and streaming: slots are delivered as they are decoded rather than all at the end.

interface ParserListener<T> {
fun onStart(file: File)
fun onNewSlotsAvailable(slots: List<T>)
fun onFinish(slots: List<T>)
fun onFileFinished(file: File)
fun onError(t: Throwable)
}
CallbackWhen
onStartA file is about to be parsed
onNewSlotsAvailableA batch of freshly decoded slots is ready
onFileFinishedOne file has been fully parsed
onFinishEvery file in the call has been parsed; receives the complete set of slots
onErrorParsing failed, or too many files were passed

Parsing PPG2 files

Ppg2WiffParser().run(
files = ppg2Files,
listener = object : ParserListener<Ppg2WiffSlotModel> {
override fun onStart(file: File) {
Log.d("Corsano-SDK", "Parsing ${file.name}")
}

override fun onNewSlotsAvailable(slots: List<Ppg2WiffSlotModel>) {
// stream slots into your chart / analysis as they arrive
}

override fun onFileFinished(file: File) {
Log.d("Corsano-SDK", "Finished ${file.name}")
}

override fun onFinish(slots: List<Ppg2WiffSlotModel>) {
Log.d("Corsano-SDK", "Parsed ${slots.size} PPG2 slots")
}

override fun onError(t: Throwable) {
Log.e("Corsano-SDK", "PPG2 parsing failed: $t")
}
}
)

Ppg2WiffSlotModel

FieldTypeDescription
rawIndexIntIndex of the sample within the file
timestampDoubleTimestamp of the sample
chunkIndexIntIndex of the chunk the sample belongs to
bodyPoseInt?Body position reported by the bracelet
ledPdPosInt?LED / photodiode position the sample was measured on
offsetInt?Sensor offset
expInt?Exposure
ledInt?LED drive current
gainInt?Sensor gain
valueInt?The PPG sample value
metricTypePpg2TypeGREEN, RED or INFRA_RED
qualityInt?Deprecated, do not use

Warning: A channel is identified by the pair (metricType, ledPdPos), not by metricType alone — the same PPG type is reported on several photodiode positions within the same second (for example GREEN on ledPdPos 6 and 12). If you group, order or timestamp samples, key on both.

Two consequences follow, and both changed how parsed data comes out:

  • Timestamps. Time is tracked per (type, ledPdPos), so each channel keeps its own clock and all channels of a chunk start on the same timestamp. Previously the clock was shared per type, so every extra channel pushed it forward by a full second and timestamps drifted by one second per second.
  • Values. The rollover correction is also tracked per (type, ledPdPos). Previously it was tracked per ledPdPos alone, so types sharing a photodiode position overwrote each other's last value.

If you have PPG2 data parsed by an earlier SDK version, its timestamps and some of its values differ from what the same files produce now. Re-parse the original .wiff files rather than reconciling the two.

Parsing Accelerometer files

AccWiffParser works identically:

AccWiffParser().run(
files = accFiles,
listener = object : ParserListener<AccWiffSlotModel> {
override fun onStart(file: File) {}
override fun onNewSlotsAvailable(slots: List<AccWiffSlotModel>) {}
override fun onFileFinished(file: File) {}

override fun onFinish(slots: List<AccWiffSlotModel>) {
Log.d("Corsano-SDK", "Parsed ${slots.size} accelerometer slots")
}

override fun onError(t: Throwable) {
Log.e("Corsano-SDK", "ACC parsing failed: $t")
}
}
)

AccWiffSlotModel

FieldTypeDescription
rawIndexIntIndex of the sample within the file
timestampDoubleTimestamp of the sample
chunkIndexIntIndex of the chunk the sample belongs to
bodyPoseInt?Body position reported by the bracelet
valueXInt?X axis
valueYInt?Y axis
valueZInt?Z axis
qualityInt?Deprecated, do not use

See Accelerometer data for the units and for how to derive the movement norm.

Parsing ECG files

ECG has its own parser with a dedicated listener, and it is documented alongside the rest of the ECG flow — see ECG measurement.