🫀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

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");
  }

Context

When a measurement has been performed, context can be added. This context can contain one activity and multiple symptoms.

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);
}

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:

Diagnosis

A measurement can have multiple diagnoses, to get the most severe diagnosis, you can use the getMostSevereLabel function. Possible values of this diagnose 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 heartrate 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

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");
  }
}

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.

import 'package:flutter_fibricheck_sdk/flutter_fibricheck_sdk.dart';

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.

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

Last updated