AneDet System Walkthrough

A complete guide to how the non-invasive anemia detection system works.
GitHub icon View on GitHub

System in Simple Terms

ⓘ Note
This section explains the entire system in simple terms. If you already understand machine learning in AI and software, you can skip ahead to Section 1.

What is Anemia?

Anemia is a condition where your blood doesn’t have enough hemoglobin (Hb) — the protein in red blood cells that carries oxygen around your body. When hemoglobin is too low, you may feel tired, weak, or dizzy.

Normally, to check for anemia, a nurse draws blood from your arm and sends it to a laboratory. This takes time, costs money, and requires a needle. AneDet tries to do the same check without any needles or blood — just by looking at your fingernail with a camera.

How Can a Camera Detect Anemia?

The colour of your fingernail bed (the pink area under your nail) changes depending on how much hemoglobin is in your blood:

This is actually the same thing doctors check during a physical exam — they press your nail and look at the colour. AneDet does this automatically with a camera and artificial intelligence.

What Does the System Look Like?

The system is a small device with three parts:

  1. A small computer (Raspberry Pi 5) — about the size of a credit card
  2. A camera (Raspberry Pi Camera Module 3) — points at the patient’s finger
  3. A touchscreen (5 inches) — shows the results and lets the operator control the system

Everything runs on this small device. It does not need an internet connection — all the intelligence is built in.

How Does a Test Work?

A typical test takes about 26 seconds and works like this:

  1. Place your finger — The patient holds their index finger or including middle and ring finger in front of the camera, about 10–15 cm away
  2. The system finds the nail — The first AI automatically locates the fingernail in the camera image (like how your phone camera finds faces)
  3. The system reads the colour — The second AI examines the nail colour and the skin colour next to it. It compares them to hundreds of examples it has seen before
  4. It takes many readings — Over 16 seconds, the system takes about 20 separate readings and combines them to get a reliable result
  5. The result appears on screen — A hemoglobin number (like “13.2 g/dL”) and a status (“Normal”, “Borderline”, or “Anemia”)

What Do the Results Mean?

Result on Screen What It Means What to Do
Normal Hemoglobin is above 12.2 g/dL. Blood appears healthy. No action needed for anemia.
Borderline Hemoglobin is between 12.0 and 12.2 g/dL. Too close to call. Consider a follow-up lab test to be sure.
Mild Anemia Hemoglobin is between 10.0 and 11.9 g/dL. Refer to a doctor for a confirmatory blood test.
Moderate Anemia Hemoglobin is between 8.0 and 9.9 g/dL. Refer to a doctor promptly.
Severe Anemia Hemoglobin is below 8.0 g/dL. Urgent medical attention needed.
⚠️ Important Reminder
AneDet is a screening tool, not a medical diagnosis. Think of it like a thermometer — it can tell you something might be wrong, but a doctor still needs to confirm with a proper blood test. Never use this result alone to make treatment decisions.

How Accurate Is It?

Based on testing with 673 patients:

This means the system is useful for screening (quickly checking many people) but is not a replacement for laboratory blood tests. Some healthy people may be flagged as anemic, and some anemic people may be missed.

How Does the AI “Learn” to Read Nail Colour?

Think of it like training a new doctor:

  1. Show many examples — The AI was shown 673 photos of fingernails from different patients, along with their actual hemoglobin levels from lab blood tests
  2. Let it find patterns — The AI gradually learned which nail colours correspond to which hemoglobin levels. Paler nails generally mean lower hemoglobin
  3. Test it on new patients — After training, the AI was tested on patients it had never seen before to make sure it works on strangers, not just the people it practised on

The AI also looks at the skin colour next to the nail as a reference. This helps compensate for different lighting conditions — just like how a human would compare “is the nail paler than the surrounding skin?” rather than judging nail colour in isolation.

Why Does It Take 20 Readings Instead of Just One?

Imagine you’re trying to weigh yourself on a shaky scale. One reading might say 70 kg, the next might say 72 kg, and the next 69 kg. None of them are perfectly accurate. But if you take 20 readings and use the middle value, you get a much more reliable answer.

The AI works the same way — each individual prediction has some uncertainty, so the system takes many readings, throws out the obvious outliers, and uses the stable middle value as the final result.

What Are the Safety Features?

The system has several built-in safety measures:


💡 End of Plain English Guide
Everything below this point is the technical documentation. It explains exactly how each component works at the code level.
Table of Contents
  1. Plain English Guide (above)
  2. What the System Does
  3. Hardware Overview
  4. Software Architecture
  5. File Inventory
  6. The Two AI Models
  7. How a Measurement Works (User Perspective)
  8. How a Measurement Works (Technical Deep-Dive)
  9. Signal Stabilisation Pipeline
  10. Unit Conversion & Calibration
  11. The Web Interface
  12. Database & Data Privacy
  13. How the Model Was Trained
  14. Configuration & Tuning Reference
  15. Starting the System

1. What the System Does

AneDet is a non-invasive anemia screening tool. Instead of drawing blood, it:

  1. Points a camera at the patient's fingernail (index finger)
  2. Detects the nail automatically using a YOLO object detection model
  3. Analyses the nail colour using a MobileNetV3 regression model
  4. Estimates the hemoglobin (Hb) level in g/dL
  5. Classifies the result as Normal, Mild Anemia, Moderate Anemia, or Severe Anemia

The system runs entirely on-device — no internet connection is needed. The whole process takes about 26 seconds per reading.

Clinical Thresholds (g/dL)

Status Hb Range
Normal > 12.2 g/dL
Borderline 12.0 – 12.2 g/dL
Mild Anemia 10.0 – 11.9 g/dL
Moderate Anemia 8.0 – 9.9 g/dL
Severe Anemia < 8.0 g/dL
💡 Borderline Band
The Borderline band (12.0–12.2 g/dL) is a deliberate buffer zone. Values in this range are too close to the anemia boundary to classify confidently, so the system labels them as “Borderline” rather than making a potentially wrong call.
ⓘ Screening Tool Disclaimer
These thresholds are roughly aligned with WHO guidelines. The system is intended as a screening tool, not a diagnostic device — positive results should be confirmed with a laboratory blood test.

2. Hardware Overview

The system runs on three physical components:

5-inch Touchscreen
Shows the web UI via Chromium browser
Raspberry Pi 5
Runs Flask server + AI models
Camera Module 3
640×480, continuous autofocus

Camera Configuration

The Raspberry Pi Camera Module 3 has autofocus, which is critical for close-up nail imaging at 10–15 cm distance.

Setting Value Why
Resolution 640 × 480 Fast enough for real-time processing on Pi 5
Format RGB888 Direct BGR in memory — no conversion needed for OpenCV
Autofocus Mode Continuous + Fast Keeps the nail sharp as the user adjusts position
Fallback Focus Manual, LensPosition = 8.0 ~12.5 cm focal distance if autofocus is unavailable

3. Software Architecture

The system is a Flask web application that runs locally on the Pi. The user interacts with it through a Chromium browser (typically launched in kiosk mode on the 5-inch display).

Thread Architecture

There are 3 concurrent execution contexts:

Thread Role Heavy Work?
Flask Main Serves HTML, handles API calls (/start_session, /get_result, /save_measurement) No — lightweight HTTP
Inference Worker Runs YOLO nail detection, evaluates focus sharpness, runs ONNX Hb model Yes — all AI runs here
Frame Generator Captures camera frames, draws bounding boxes + status overlay, encodes JPEG, streams via MJPEG Moderate — image encoding only
Flask Main Thread
Serves web pages + API endpoints
Frame Generator
Captures frames, draws overlays, streams MJPEG
Reads from InferenceState
Inference Worker Thread
YOLO detection + focus eval + ONNX Hb inference
Writes to → InferenceState (mutex-locked)
Writes to → Session State (mutex-locked)
Reads from → Picamera2 (mutex-locked)
⚠️ Why separate the inference worker from the frame generator?
YOLO detection and ONNX inference take ~0.4–0.8 seconds each on Pi 5. If these ran inside the frame generator, the video stream would freeze during every inference step. By putting heavy computation in a background thread, the MJPEG stream stays smooth (~10–15 FPS) while inference runs independently.

Shared State: InferenceState

The InferenceState dataclass acts as the communication channel between the inference worker and frame generator:

Field Type Purpose
cached_boxes list YOLO bounding boxes for overlay drawing
smoothed_nail_box list EMA-smoothed nail position for stable inference
best_live_nail_conf float Highest nail detection confidence
focus_ok bool Is the image sharp enough?
smoothed_hb float EMA-smoothed Hb value for display
hb_history deque(15) Rolling window of raw Hb predictions
quality_ok bool Combined nail + focus quality check
quality_score int 0–100 score for UI progress bar
quality_tip str User guidance text

4. File Inventory

Core Application Files

File Purpose
app2.py Main application — Flask server, camera control, YOLO + ONNX inference, all routes
hb_regressor.onnx FP32 ONNX hemoglobin regression model (4.0 MB)
hb_regressor_config.json Model configuration: skin feature normalisation stats, calibration params, threshold
best.pt YOLO nail detection model (expected in the working directory)
templates/index.html Web UI template (Flask renders this)
static/style.css UI styling
static/script.js Frontend JavaScript (session control, polling, virtual keyboard)

Training & Evaluation Files

File Purpose
train_hb_regressor.py Training script that produces the ONNX model and config JSON
hb_regressor_best.pt PyTorch checkpoint (best model weights)
hb_regressor_training_summary.json Full training report: metrics, cross-validation, seed stability
hb_regressor_cv_summary.json 3-fold cross-validation results
hb_regressor_seed_runs.csv Per-seed training metrics comparison

Data

Path Content
data2/metadata.csv 673 patients, Hb levels, bounding box annotations
data2/photo/ Patient fingernail images

5. The Two AI Models

The system uses two models in sequence: one to find the nail, one to predict hemoglobin.

Model 1: YOLO Nail Detector (best.pt)

Property Value
Purpose Locate the fingernail in the camera frame
Architecture YOLOv8 (Ultralytics)
Input Full camera frame (640×480)
Output Bounding boxes with class ID and confidence score
Class 0 Nail
Live inference size 416 pixels (faster for real-time)
Session inference size 640 pixels (more accurate for Hb prediction)
Run frequency Every 0.4 seconds

Every 0.4 seconds, the inference worker runs YOLO on the latest camera frame. It finds all nail bounding boxes, picks the one with the highest confidence, and smooths its position using an exponential moving average (EMA) to reduce jitter.

Model 2: Hemoglobin Regressor (hb_regressor.onnx)

Property Value
Purpose Predict hemoglobin level from nail + skin colour
Architecture MobileNetV3-Small backbone + linear regression head
Input 1 image: nail crop resized to 224×224×3, ImageNet-normalised
Input 2 skin_features: 6 Lab colour values (nail L,a,b + skin L,a,b)
Output hb_prediction: single float in g/L
Runtime ONNX Runtime, CPUExecutionProvider, 4 threads
Run frequency Every 0.8 seconds during acquisition phase

How the 6 Skin Features Are Computed

def compute_nail_skin_ratio(nail_patch, skin_patch):
    nail_lab = cv2.cvtColor(nail_patch, cv2.COLOR_BGR2Lab)
    skin_lab = cv2.cvtColor(skin_patch, cv2.COLOR_BGR2Lab)
    nail_mean = nail_lab.reshape(-1, 3).mean(axis=0)   # → 3 values (L, a, b)
    skin_mean = skin_lab.reshape(-1, 3).mean(axis=0)   # → 3 values (L, a, b)
    return [nail_L, nail_a, nail_b, skin_L, skin_a, skin_b]  # → 6 total

These features are then z-normalised using training-set statistics from hb_regressor_config.json:

Feature Mean Std Interpretation
Nail L 153.4 18.3 Nail lightness
Nail a 140.3 2.2 Nail red-green (key anemia indicator)
Nail b 131.9 6.0 Nail blue-yellow
Skin L 159.2 21.4 Skin lightness (normalisation reference)
Skin a 133.3 4.4 Skin red-green
Skin b 141.4 4.3 Skin blue-yellow
💡 Why These Features Matter
The a channel in Lab space is the most clinically important — it represents redness. Anemic nails have pale nail beds with lower a values. The skin features act as a lighting normalisation reference: by comparing nail colour to adjacent skin colour, the model becomes less sensitive to ambient lighting conditions.

6. How a Measurement Works (User Perspective)

A measurement has 3 phases that the user sees on screen:

Phase 1: Positioning (at least 10 seconds)

  1. User taps “Start Reading”
  2. Camera feed appears on screen
  3. User places their index fingernail in front of the camera (~10–15 cm away)
  4. The Placement Quality bar fills up as the system detects the nail and confirms focus
  5. Green bounding box appears around the detected nail
  6. User guidance text updates: “Place index fingernail in the center”“Hold still”“Great position”
ⓘ Phase Duration
This phase lasts at least 10 seconds, but it will wait longer if placement quality isn't confirmed yet. The system transitions to the next phase only when both 10 seconds have elapsed and quality_ok = true (nail detected with sufficient confidence + image is sharp).

Phase 2: Acquiring (16 seconds)

  1. Automatically starts after positioning quality is confirmed
  2. The system takes ~20 hemoglobin readings over 16 seconds
  3. Status shows “ANALYZING” with a countdown timer
  4. Each reading is a separate ONNX model inference
  5. Intermediate values are smoothed so the display doesn't jump around

Phase 3: Done

  1. The system computes the final Hb value from all collected samples
  2. The Estimated Hemoglobin Level appears (e.g., “11.7 g/dL”)
  3. A status badge shows the classification (e.g., “MILD ANEMIA”)
  4. User can tap “Save Reading Data” to store the result in the database

What Can Go Wrong

Problem What User Sees System Response
No nail detected Quality bar stays at 0% “Place index fingernail in the center”
Nail detected but blurry Quality bar partial “Hold still and move your finger to a sharper distance”
Too few valid samples “No Valid Reading” User should retry with better positioning
Readings are inconsistent Result still shown IQR filtering removes outliers automatically

7. How a Measurement Works (Technical Deep-Dive)

Startup Sequence

When app2.py starts, it performs these steps in order:

Load encryption key
Initialise SQLite database
Start Picamera2 (640×480, continuous AF)
Load YOLO model (best.pt)
Load ONNX model (hb_regressor.onnx)
Load config JSON (feature norms, calibration, threshold)
Start Flask server on port 5000

Phase Transitions (State Machine)

idle
User taps “Start”
positioning
Wait for nail detection + focus
10s elapsed AND quality_ok = true
acquiring
Collecting Hb samples (~20 readings)
16s elapsed
done
Final result displayed
User taps “Stop” or starts new session
idle

Positioning Phase: What the Inference Worker Does

Every cycle (~20ms sleep + processing time), the worker thread does:

  1. Captures a frame from Picamera2 (thread-safe via _camera_lock)
  2. Focus evaluation (every 0.25s):
  3. YOLO detection (every 0.4s):
  4. Quality assessment:

Acquiring Phase: Hb Inference Loop

Once quality is confirmed and 10 seconds have elapsed, the system transitions to acquiring. Every 0.8 seconds, the worker checks: Is there a smoothed nail box? Is confidence ≥ 0.34?

If yes, it runs the full Hb inference pipeline:

Camera Frame
Crop nail from smoothed bounding box
Derive skin patch (96×96px below nail)
TTA: 5 shifted crops
(0,0), (±5,0), (0,±5)
For each shift: preprocess → ONNX inference
Take median of 5 predictions
Convert g/L → g/dL (÷ 10) + slope correction
Clip to [5.0, 18.0] g/dL
Add to session_samples

Key Details:

Done Phase: Final Result Computation

When 16 seconds of acquisition have elapsed:

All session_samples (~20 raw Hb values)
Take last 10 samples (most recent)
IQR outlier filtering
Remove values outside Q1 − 1.5×IQR to Q3 + 1.5×IQR
Compute median Hb
Anemia Guard
Is median ≥ threshold BUT 30th percentile < threshold AND ≥40% of samples < threshold?
YES
Blend: 70% median + 30% lower percentile
NO
Use median directly
Classify result
Normal / Borderline / Mild / Moderate / Severe
Display on screen

Key safety features:

8. Signal Stabilisation Pipeline

Raw model predictions are noisy. The system applies four layers of smoothing:

Layer 1: TTA Median (Per-Frame)

Layer 2: Rolling History Median (Across Frames)

Layer 3: EMA Smoothing with Asymmetric Step Caps (Display)

delta = history_median - smoothed_hb
if delta >= 0:
    delta = min(delta, 0.05)    # slow upward moves (conservative)
else:
    delta = -min(abs(delta), 0.18)  # faster downward correction (safety)
candidate = smoothed_hb + 0.10 * delta
if abs(candidate - smoothed_hb) >= 0.10:   # deadband
    smoothed_hb = candidate
Parameter Value Purpose
SMOOTHING_ALPHA 0.10 How much each new reading influences the display
MAX_HB_STEP_UP 0.05 g/dL Maximum increase per update (prevents false normal jumps)
MAX_HB_STEP_DOWN 0.18 g/dL Maximum decrease per update (allows fast anemia detection)
HB_DEADBAND 0.10 g/dL Minimum change before display updates (prevents flicker)
⚠️ Asymmetric Step Caps — Safety by Design
In screening, a false negative (missing anemia) is more dangerous than a false positive (flagging a normal person). So the system moves slowly upward toward “Normal” but moves quickly downward toward “Anemia.”

Layer 4: Session Aggregation (Final Result)

9. Unit Conversion & Calibration

The Unit Problem

The model is trained on data labelled in g/L (grams per litre), but clinical Hb is reported in g/dL (grams per decilitre):

g/dL = g/L ÷ 10

So a model output of 120 g/L becomes 12.0 g/dL.

Calibration Pipeline

The system has four calibration stages:

Model Output (g/L)
Config Calibration
slope × pred + intercept
if HB_USE_CONFIG_CALIBRATION=True
÷ 10 + BIAS_OFFSET
Converts g/L → g/dL + manual offset
Slope Correction
corrected = pivot + stretch × (raw − pivot)
pivot=12.0, stretch=1.3 — stretches outward from boundary
Legacy Cal. Curve
Piecewise linear with near-normal lift
if HB_USE_LEGACY_CALIBRATION_CURVE=True
Clip to 5.0 – 18.0 g/dL
Final Hb (g/dL)

Default runtime: The ÷10 conversion, slope correction (1.3× stretch), and final clip are active. The config calibration and legacy curve are both disabled by default.

10. The Web Interface

The UI is served as a single-page Flask template (index.html) with CSS and JavaScript.

API Endpoints

Endpoint Method Purpose
/ GET Serve the main HTML page
/video_feed GET MJPEG stream (live camera with overlays)
/start_session POST Begin a new measurement (→ positioning phase)
/stop_session POST Abort current measurement (→ idle)
/get_session_state GET Polled by frontend every ~500ms for phase, quality, Hb, status
/get_result GET Quick fetch of current Hb + status
/save_measurement POST Save a reading to the database (with optional patient name)
/admin/exit POST Password-protected: exit to desktop
/admin/export_records POST Password-protected: export database to CSV
/admin/reset_database POST Password-protected: delete all readings

Frontend Polling

The JavaScript polls /get_session_state continuously while a session is active. Example response:

{
    "phase": "acquiring",
    "remaining": 8.3,
    "quality_ok": true,
    "quality_reason": "Quality OK",
    "quality_score": 100,
    "quality_tip": "Great position. Keep still until analysis completes.",
    "samples": 12,
    "hb": 11.7,
    "status": "Mild Anemia"
}

11. Database & Data Privacy

Storage

Results are stored in a local SQLite database (data/readings.db):

Column Type Content
id INTEGER Auto-incrementing primary key
created_at TEXT UTC timestamp
measurement_time TEXT Time of measurement
predicted_hb REAL Hemoglobin value in g/dL
predicted_status TEXT “Normal”, “Mild Anemia”, etc.
patient_name_enc TEXT Patient name (encrypted)

Encryption

Patient names are encrypted at rest using a custom stream cipher with HMAC-SHA256 integrity:

Plaintext → XOR with SHA-256 keystream → prepend 16-byte nonce
→ append 32-byte HMAC → Base64-encode

Admin Functions (Password-Protected)

All admin operations require authentication:

12. How the Model Was Trained

The training pipeline (train_hb_regressor.py) produces the ONNX model that runs on the Pi.

Training Data

Model Architecture: HBRegressor

class HBRegressor(nn.Module):
    # Backbone: MobileNetV3-Small (ImageNet pretrained)
    features = mobilenet_v3_small().features  # → 576-dim feature vector
    pool = AdaptiveAvgPool2d(1, 1)

    # Regression head: concatenate 576 CNN features + 6 skin features
    regressor = Sequential(
        Linear(576 + 6, 128),   # fusion layer
        ReLU(),
        Dropout(0.3),
        Linear(128, 1),         # → single Hb prediction (g/L)
    )

Training Strategy

Phase Epochs What's Trained Learning Rate
Head-only 10 regressor only (backbone frozen) 1e-3
Fine-tuning 20 Last 4 backbone blocks + regressor 3e-5 backbone, 1e-4 head

Achieved Performance (Best Seed = 42)

12.7
Test MAE (g/L)
82.1%
Sensitivity (anemia recall)
0.32
R² Score
80.4%
Specificity (normal recall)
Metric Test Set (303 samples) Patient-Level (101 patients)
MAE 12.7 g/L (1.27 g/dL) 11.4 g/L (1.14 g/dL)
RMSE 18.1 g/L 16.8 g/L
0.32 0.41
Sensitivity 82.1% 89.3%
Specificity 80.4% 80.8%
Accuracy 80.9% 83.2%

Cross-Validation (3-Fold)

Metric Mean ± Std
MAE 13.7 ± 0.9 g/L
Sensitivity 82.1% ± 7.2%
Specificity 82.5% ± 3.9%

13. Configuration & Tuning Reference

All runtime parameters can be overridden via environment variables without changing code:

Core Inference

Env Variable Default Description
HB_BIAS_OFFSET 0.0 Manual g/dL offset added to every prediction
HB_SCAN_INTERVAL 0.8 Seconds between Hb inference runs
HB_SMOOTHING_ALPHA 0.10 EMA weight for display smoothing
HB_MAX_HB_STEP_UP 0.05 Max g/dL increase per update
HB_MAX_HB_STEP_DOWN 0.18 Max g/dL decrease per update
HB_DEADBAND 0.10 Minimum change before display updates
HB_HISTORY_SIZE 15 Rolling window size for median filter

Session Timing

Env Variable Default Description
PREP_DURATION 10.0 Positioning phase minimum duration (seconds)
HB_ACQUIRE_DURATION 16.0 Acquisition phase duration (seconds)
HB_MIN_VALID_SAMPLES 6 Minimum samples required for a valid result

Confidence Thresholds

Env Variable Default Purpose
HB_LIVE_NAIL_CONF 0.15 Minimum YOLO confidence to draw a bounding box
HB_SESSION_NAIL_CONF 0.18 Minimum confidence for positioning quality check
HB_ACQUIRE_NAIL_CONF 0.34 Minimum confidence to accept an Hb sample

Camera

Env Variable Default Purpose
CAMERA_FRAME_WIDTH 640 Capture width in pixels
CAMERA_FRAME_HEIGHT 480 Capture height in pixels
LENS_POSITION 8.0 Manual focus position (dioptres, 1/metres)

Calibration Toggles

Env Variable Default Purpose
HB_USE_SKIN_FEATURE_NORM True Use z-normalisation on skin features
HB_USE_CONFIG_CALIBRATION False Apply slope/intercept from config JSON
HB_USE_LEGACY_CALIBRATION_CURVE False Apply piecewise linear calibration curve
HB_ANEMIA_THRESHOLD_GDL 12.20 Decision threshold in g/dL

14. Starting the System

On the Raspberry Pi

cd /path/to/oneModel
python app2.py

The Flask server starts on http://0.0.0.0:5000. On the Pi's display, Chromium should open in kiosk mode pointing to http://localhost:5000.

Typical Boot Sequence Console Output

load models
loaded hb config: /path/to/hb_regressor_config.json
runtime hb config | skin_norm=True | cfg_cal=False (slope=0.7000,
    intercept=20.00) | legacy_curve=False | anemia_threshold=12.20
models loaded
camera initialized (Picamera2, Camera Module 3, size=640x480)
autofocus: Continuous + Fast (Camera Module 3)
 * Running on http://0.0.0.0:5000

Complete System Data Flow (End to End)

Camera Module 3 (640×480)
raw frame
YOLO Nail Detector (best.pt, 416px)
nail bounding box
Crop Nail (224×224) + Skin Patch (96×96)
Image
Resize, RGB,
ImageNet Normalize
Colour
Lab means → 6 values
z-normalise
TTA × 5 shifted crops
5 predictions → median
÷ 10 → g/dL + Slope Correction
≲ EMA Smoothing + History Buffer
session samples
Session Aggregation
IQR filter → Median → Anemia Guard
final Hb
Classification
Normal / Borderline / Mild / Moderate / Severe
5-inch Display
SQLite DB
(encrypted names)

Walkthrough Sections