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
.wifffiles 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
| Parser | Produces | Data |
|---|---|---|
Ppg2WiffParser | Ppg2WiffSlotModel | PPG2 (green, red, infra-red) |
AccWiffParser | AccWiffSlotModel | Accelerometer |
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 throughonError.
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)
}
| Callback | When |
|---|---|
onStart | A file is about to be parsed |
onNewSlotsAvailable | A batch of freshly decoded slots is ready |
onFileFinished | One file has been fully parsed |
onFinish | Every file in the call has been parsed; receives the complete set of slots |
onError | Parsing 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
| Field | Type | Description |
|---|---|---|
rawIndex | Int | Index of the sample within the file |
timestamp | Double | Timestamp of the sample |
chunkIndex | Int | Index of the chunk the sample belongs to |
bodyPose | Int? | Body position reported by the bracelet |
ledPdPos | Int? | LED / photodiode position the sample was measured on |
offset | Int? | Sensor offset |
exp | Int? | Exposure |
led | Int? | LED drive current |
gain | Int? | Sensor gain |
value | Int? | The PPG sample value |
metricType | Ppg2Type | GREEN, RED or INFRA_RED |
quality | Int? | Deprecated, do not use |
Warning: A channel is identified by the pair (
metricType,ledPdPos), not bymetricTypealone — the same PPG type is reported on several photodiode positions within the same second (for example GREEN onledPdPos6 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 perledPdPosalone, 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
.wifffiles 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
| Field | Type | Description |
|---|---|---|
rawIndex | Int | Index of the sample within the file |
timestamp | Double | Timestamp of the sample |
chunkIndex | Int | Index of the chunk the sample belongs to |
bodyPose | Int? | Body position reported by the bracelet |
valueX | Int? | X axis |
valueY | Int? | Y axis |
valueZ | Int? | Z axis |
quality | Int? | 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.