🫀

Measurements

The complete structure is exported as a type: `Measurement` in the SDK. For completeness, we will explain some parts more in detail here.

Creating a measurement

CameraData

Flutter
React Native
This snippet shows the interface that is implemented when performing a measurement via the flutter-camera-sdk. This way, you don't have to worry about populating these fields.
class CameraData {
acc?: MotionData;
rotation?: MotionData;
grav?: MotionData;
gyro?: MotionData;
heartrate: num;
measurement_timestamp: num;
quadrants: Yuv[][];
technical_details: {
camera_exposure_time: num;
camera_hardware_level: String;
camera_iso: num;
};
time: num[];
yList: num[];
abnormalities?: Abnormalities[];
attempts?: num;
skippedPulseDetection: bool;
skippedFingerDetection: bool;
skippedMovementDetection: bool;
}
This structure can be used for creating MeasurementCreationData, which can be posted to our backend via the sdk.postMeasurement()
Future _ononMeasurementProcessed(String measurementString) async {
var json = jsonDecode(measurementString);
var mCreationData = MeasurementCreationData.fromJson(json);
await sdk.postMeasurement(mCreationData, "v0.0.1");
}
This snippet shows the interface that is implemented when performing a measurement via the react-native-camera-sdk. This way, you don't have to worry about populating these fields.
interface CameraData {
acc?: MotionData;
rotation?: MotionData;
grav?: MotionData;
gyro?: MotionData;
heartrate: number;
measurement_timestamp: number;
quadrants: Yuv[][];
technical_details: {
camera_exposure_time: number;
camera_hardware_level: string;
camera_iso: number;
};
time: number[];
yList: number[];
abnormalities?: Abnormalities[];
attempts?: number;
skippedPulseDetection: boolean;
skippedFingerDetection: boolean;
skippedMovementDetection: boolean;
}
This structure can be used for creating MeasurementCreationData, which can be posted to our backend via the sdk.postMeasurement()

Context

When a measurement has been performed, context can be added. This context can contain one activity and multiple symptoms.
Flutter
React Native
enum Symptoms {
@JsonValue('no_symptoms')
noSymptoms,
lightheaded,
confused,
fatigue,
other,
palpitations,
@JsonValue('chest_pains')
chestPains,
@JsonValue('shortness_of_breath')
shortnessOfBreath,
}
enum Activity {
resting,
sleeping,
sitting,
walking,
working,
exercising,
other,
standing,
}
enum SymptomSeverity {
@JsonValue('1')
severity_1,
@JsonValue('2a')
severity_2a,
@JsonValue('2b')
severity_2b,
@JsonValue('3')
severity_3,
@JsonValue('4')
severity_4,
}
class MeasurementContext {
final List<Symptoms>? symptoms;
final Activity? activity;
final SymptomSeverity? symptomSeverity;
MeasurementContext({
required this.symptoms,
required this.activity,
this.symptomSeverity,
});
factory MeasurementContext.fromJson(Map<String, dynamic> json) => _$MeasurementContextFromJson(json);
Map<String, dynamic> toJson() => _$MeasurementContextToJson(this);
}
interface MeasurementContext {
symptoms:
| 'no_symptoms'
| 'lightheaded'
| 'confused'
| 'fatigue'
| 'other'
| 'palpitations'
| 'chest_pains'
| 'shortness_of_breath'[];
activity:
| 'resting'
| 'sleeping'
| 'sitting'
| 'walking'
| 'working'
| 'exercising'
| 'other'
| 'standing';
symptomSeverity?:
| '2a'
| '2b'
| '3'
| '4';
} noSymptoms,
These symptoms can be directly added to a measurement or can be updated at a later stage by using sdk.updateMeasurementContext. This can be beneficial to the user experience. If you send the raw measurement data first, the processing of the measurement can already take place on the backend. When the user has selected their symptoms, it can then be added to the already processed measurement.

Displaying a measurement

Indicator

This value represents the indicator that the FibriCheck algorithm has given to the measurement. When the status is reviewed, it means this indicator is validated by a medical professional. Possible values are 'normal' | 'quality' | 'urgent' | 'warning'

Status

A measurement can have multiple statuses, depending in which phase of the review process it is currently in:
measured
The measurement is received. The value of viewResult is being determined. The status should change to preprocessing_selection immediately.
preprocessing_selection
The value of algoPreprocessing is being determined. The status should change to analysis_selection immediately.
analysis_selection
The value of algoAnalysis is being determined. The status should change to pending_analysis immediately.
pending_analysis
The measurement is waiting for the algorithm to analyze it. The Algo Queue Manager will transition the measurement to under_analysis if the algorithm is ready to analyze
under_analysis
The measurement is being analyzed by the measurement. The Algo Queue Manager will transition the measurement to analysis_failed or processing_results depending on the response of the algorithm.
analysis_failed
The algorithm was not able to analyze this measurement. The measurement will stay in this status until manually transitioned back to pending_analysis.
processing_results
The result of the analysis is being processed by the process-measurement-result-task. The task will transition the measurement to the analyzed status.
analyzed
The measurement was successfully analyzed. Depending on the value of autoPendingReview, set by the task in the previous step, the measurement will immediately transition to pending_review or stay in this status until manually transitioned to pending_review.
pending_review
The measurement is awaiting revision by a human medical expert. The human revision is currently meant to be completed within 48 hours and will transition the status to
reviewed
The measurement is reviewed by a human medical expert. The measurement will stay in this status until manually transitioned to pending_review.

Diagnosis

Once a measurement is reviewed, it can have (multiple) diagnoses. To get the most severe diagnosis, you can use the getMostSevereLabel function:
  • React Native: getMostSevereLabel(measurement)
  • Flutter: measurement.getMostSevereLabel()
Possible values of this diagnosis are:
'sinus_arrhythmia'
'extrasystoles_trig_episode'
'undiagnosable'
'extrasystoles_isolated'
'dubious_rhythm'
'extrasystoles'
'extrasystoles_trigeminy'
'tachy_episode'
'extrasystoles_frequent'
'phone_incompatible'
'extrasystoles_big_episode'
'increased_hrv'
'sinus'
'atrial_flutter'
'brady_episode'
'tachycardia'
'extrasystoles_bigminy'
'tachy_arrhytmia'
'pacemaker_rhythm'
'bradycardia'
'brady_arrhytmia'
'quality_to_low'
'atrial_fibrillation'
'other'
'no_diagnosis'

Heart rate

The algorithm also returns the calculated heart rate under heartrate property

Measurement timestamp

The date and time of the measurement is given in epoch format under the measurement_timestamp property.

Perform a measurement with the Camera SDK component

Flutter
React Native
You can use the FibriCheckView exported from the package:camera_sdk/fibri_check_view.dart package to perform a measurement and hook up sdk.postMeasurement to post the data returned from the camera to the backend in the onMeasurementProcessed event.
  • Before taking a measurement, you need to check if you are entitled to perform a measurement. This can be achieved by invoking sdk.canPerformMeasurement. If you try to execute a measurement when you are not entitled, a NoActivePrescriptionError will be thrown. So make sure you've Activated a Prescription.
  • It is highly recommended to provide the camera sdk version as a second argument, as shown in the example.
import 'package:camera_sdk/fibri_check_view.dart';
import 'dart:convert';
import 'package:camera_sdk/fibri_check_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_fibricheck_sdk/flutter_fibricheck_sdk.dart';
import 'package:flutter_fibricheck_sdk/measurement.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:wakelock/wakelock.dart';
import '../0_design_system/fc_colors.dart';
import 'widgets/fc_metrics.dart';
import 'widgets/fc_title.dart';
class CameraScreen extends StatefulWidget {
const CameraScreen({super.key, required this.sdk});
final FLFibriCheckSdk sdk;
@override
State<CameraScreen> createState() => _CameraScreenState();
}
class _CameraScreenState extends State<CameraScreen> {
Future<void>? _requestCameraPermission;
bool _hasCameraPermission = false;
bool _measurementFinished = false;
String _timeRemaining = "-";
String _heartBeat = "-";
String _status = "Place your finger on the camera";
Measurement? _measurement;
@override
initState() {
super.initState();
_requestCameraPermission = _requestCameraPermissionImpl();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: FCColors.brokenWhite,
appBar: AppBar(
backgroundColor: FCColors.green,
title: const Text('Camera'),
),
body: Column(
children: [
Expanded(
child: FutureBuilder(
future: _requestCameraPermission,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const DemoTitleWidget(title: "Requiring camera permission");
}
if (!_hasCameraPermission) {
return const DemoTitleWidget(title: "Camera permission not granted");
}
return Column(
children: [
DemoTitleWidget(title: _status),
Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(color: FCColors.lightGray, width: 1),
bottom: BorderSide(color: FCColors.lightGray, width: 1),
),
),
height: 200,
child: FibriCheckView(
fibriCheckViewProperties: FibriCheckViewProperties(
flashEnabled: true,
lineThickness: 4,
),
onCalibrationReady: () => {
debugPrint("Flutter onCalibrationReady"),
setState(() {
_status = "Recording heartbeat...";
}),
},
onFingerDetected: () => {
Wakelock.enable(),
debugPrint("Flutter onFingerDetected"),
setState(() {
_status = "Detecting pulse...";
}),
},
onFingerDetectionTimeExpired: () => debugPrint("Flutter onFingerDetectionTimeExpired"),
onFingerRemoved: () => {
Wakelock.disable(),
debugPrint("Flutter onFingerRemoved"),
},
onHeartBeat: (heartbeat) => {
debugPrint("Flutter onHeartBeat $heartbeat"),
setState(() {
_heartBeat = heartbeat.toString();
}),
},
onMeasurementFinished: () => {
debugPrint("Flutter onMeasurementFinished"),
setState(() {
_status = "Measurement finished!";
}),
},
onMeasurementProcessed: (measurement) async => {
await _onMeasurementFinished(measurement),
debugPrint("Flutter onMeasurementProcessed $measurement"),
if (Navigator.canPop(context)) Navigator.pop(context),
},
onPulseDetected: () => {
debugPrint("Flutter onPulseDetected"),
setState(() {
_status = "Calibrating...";
}),
},
onTimeRemaining: (seconds) => {
debugPrint("Flutter onTimeRemaining $seconds"),
setState(() {
_timeRemaining = seconds.toString();
}),
},
),
),
DemoMetricsWidget(timeRemaining: _timeRemaining, heartBeat: _heartBeat),
],
);
},
),
),
],
),
),
);
}
Future<void> _requestCameraPermissionImpl() async {
var result = await Permission.camera.request();
setState(() {
_hasCameraPermission = result.isGranted;
});
}
Future _onMeasurementFinished(String measurementString) async {
var mCreationData = MeasurementCreationData.fromCameraSdk(measurementString);
await widget.sdk.postMeasurement(mCreationData, "v0.0.1");
}
}
You can use the RNFibriCheckView exported from the @fibricheck/react-native-camera-sdk package to perform a measurement and hook up sdk.postMeasurement to post the data returned from the camera to the backend in the onMeasurementProcessed event.
  • Before taking a measurement, you need to check if you are entitled to perform a measurement. This can be achieved by invoking sdk.canPerformMeasurement. If you try to execute a measurement when you are not entitled, a NoActivePrescriptionError will be thrown. So make sure you've Activated a Prescription.
  • It is highly recommended to provide the camera sdk version as a second argument, as shown in the example.
  • The measurement context is prefilled here, but needs to be completed by the user. The best practice is to add this at a later stage with sdk.updateMeasurementContext. More information about this can be found in the Measurement Structure.
import client from '@fibricheck/javascript-sdk';
import { RNFibriCheckView } from '@fibricheck/react-native-camera-sdk';
import { useEffect, useState } from 'react';
const sdk = client({
consumerKey: '',
consumerSecret: '',
});
await sdk.authenticate({
token: '',
tokenSecret: '',
});
const App = () => {
const [cameraData, setCameraData] = useState();
const [context, setContext] = useState({
symptoms: [
'lightheaded',
'confused'
],
activity: 'sleeping',
});
async function postMeasurement(cameraData, context) {
const measurement = {
...cameraData,
context
};
await sdk.postMeasurement(measurement, RNFibriCheckView.version);
}
return (
<RNFibriCheckView
onHeartBeat={(heartRate) => {
console.log('heartRate', heartRate);
}}
onTimeRemaining={(seconds) => {
console.log('onTimeRemaining', seconds);
}}
onMeasurementProcessed={async (cameraData) => {
console.log('onMeasurementProcessed', cameraData);
setCameraData(cameraData);
}}
onFingerDetected={() => {
console.log('finger detected');
}}
/>
);
};
In some rare cases, it can occur that the motion sensors don't provide the correct data. In such cases, the movement detection will kick in, although the user is not moving. A way to fix this, is to disable the motion sensors by setting the movementDetectionEnabled to false on for example a onLongPress

Fetch a measurement

Use the sdk.getMeasurement function to get a single measurement based on an ID. Only measurements for the currently authenticated user can be requested.
Flutter
React Native
import 'package:flutter_fibricheck_sdk/flutter_fibricheck_sdk.dart';
const measurementId = '0000';
const measurement = await _sdk.getMeasurement(measurementId);
import client from '@fibricheck/javascript-sdk';
const sdk = client({
consumerKey: '',
consumerSecret: '',
});
await sdk.authenticate({
token: '',
tokenSecret: '',
});
const measurementId = '0000';
const measurement = await sdk.getMeasurement(measurementId);

Fetching all your measurements

Using sdk.getMeasurements will return a paginated result with all measurements for the currently authenticated user. You can find the measurements under the data property. You can also use the next and previous functions present on the result to navigate through the user's measurements.
First Tab
React Native
import 'package:flutter_fibricheck_sdk/flutter_fibricheck_sdk.dart';
_sdk.getMeasurements(true); // true -> get newest measurements first
await res.getNextMeasurements(); // get next 20 measurements
await res.getPreviousMeasurements(); // get previous 20 measurements
import client from '@fibricheck/javascript-sdk';
const sdk = client({
consumerKey: '',
consumerSecret: '',
});
await sdk.authenticate({
token: '',
tokenSecret: '',
});
// Returns the first 20 measurements
const measurements = await sdk.getMeasurements();
// Returns the next 20 measurements
const nextMeasurements = await measurements.next();