Simplified Blood Count Analysis

Complete Blood Count Analysis At Home— Using Python

Oliver Lövström
Internet of Technology
5 min readFeb 5, 2024

--

Photo by Testalize.me on Unsplash

Day 14: Yesterday, I published this article: Creating a CSV From HTML Titles. Feel free to check it out!

This week, we’ll focus on developing a tool for blood count analysis using Python

The Vision for PyCBCount

Imagine the convenience of conducting blood analyses at home as a complement to regular medical evaluations and laboratory tests.

Why is this beneficial?

PyCBCount aims to make this possible — by using a microscope and this Python tool, users will be able to monitor their blood health daily. Gaining insights into how lifestyle and dietary choice make affect their well-being.

It is important to note that PyCBCount is not meant to be a replacement for professional evaluations or laboratory analyses. Its purpose is to enable the average person to analyze their blood work on a daily basis without the expense of laboratory equipment.

Throughout this series, we will investigate the types of information that can be extracted from a blood smear. As my expertise does not include a medical degree, I welcome collaboration and guidance from those within this field and cannot guarantee that all information is accurate…

Initial Implementation

How far have we gotten so far?

We’ve set up the structure of PyCBCount:

pycbcount
├── LICENSE
├── README.md
├── setup.cfg
├── setup.py
└── src
├── pycbcount
│ ├── __init__.py
│ ├── blood_values.py
└── └── cbc_metrics.py

The first version of PyCBCount is already published on PyPI , and you can integrate it into your projects by running pip install pycbcount

For a look into the packaging and publishing process, check out my tutorial on getting started with Python package distribution on PyPI.

Diving Into Blood Values

Our journey begins with the blood_values.py file, which stores reference ranges for various blood count metrics across different age groups. This file introduces a specialized dictionary class, AgeGroupDict.

"""Reference Ranges for Complete Blood Count.

This module defines the reference ranges for various complete blood count
metrics across different age groups.

| Metrics | Units |
| :---------- | :--------------- |
| WBC | cells/µL |
| RBC | cells/µL |
| HGB | g/L |
| HCT | decimal fraction |
| HB | g/dL |
| MCV | fL |
| MCH | pg |
| MCHC | g/dL |
| RDW | decimal fraction |
| PLT | cells/µL |
| Neutrophils | cells/µL |
| Lymphocytes | cells/µL |
| Monocytes | cells/µL |
| Eosinophils | cells/µL |
| Basophils | cells/µL |
"""
from typing import Any, Union


class AgeGroupDict(dict):
"""A dictionary class that categorizes age-related data into age groups.

This class extends the standard dictionary and allows accessing values by
specifying either an age as an integer or an age group as a string.
The age groups are defined as:

`Neonate` (`age` <= 1), `Pediatric` (1 < `age` <= 7), `Adult` (`age` > 7).
"""

def __getitem__(self, key: Union[int, str]) -> Any:
"""Get the item corresponding to the given age or age group.

:param key: The key to look up in the dictionary. Can be an integer age
or a string age group.
:return: The value from the dictionary corresponding to the given key.
:raises KeyError: If the key is not an integer or string, or if it is an
integer but not a positive number, a KeyError is raised.
"""
if isinstance(key, int):
if key <= 1:
age_group = "Neonate"
elif key <= 7:
age_group = "Pediatric"
elif key > 7:
age_group = "Adult"
return super().__getitem__(age_group)
if isinstance(key, str):
# Handle age group input.
return super().__getitem__(key)
raise KeyError("Invalid input. Please provide an age or age group.")


reference_range = AgeGroupDict(
{
"Adult": {
"WBC": (3.6e3, 10.6e3),
"RBC": {"Male": (4.20e6, 6.00e6), "Female": (3.80e6, 5.20e6)},
"HGB": {"Male": (135, 180), "Female": (120, 150)},
"HCT": {"Male": (0.40, 0.54), "Female": (0.35, 0.49)},
"HB": (13.3 - 16.7),
"MCV": (80, 100),
"MCH": (26, 34),
"MCHC": (32.0, 36.0),
"RDW": (0.115, 0.145),
"PLT": (150e3, 450e3),
"Neutrophils": (1.7e3, 7.5e3),
"Lymphocytes": (1.0e3, 3.2e3),
"Monocytes": (0.1e3, 1.3e3),
"Eosinophils": (0.0e3, 0.3e3),
"Basophils": (0.0e3, 0.2e3),
},
"Pediatric": {
"WBC": (5.0e3, 17.0e3),
"RBC": (4.00e6, 5.20e6),
"HGB": (102, 152),
"HCT": (0.36, 0.46),
"HB": "TBD",
"MCV": (78, 94),
"MCH": (23, 31),
"MCHC": (320, 360),
"RDW": (0.115, 0.145),
"PLT": (150e3, 450e3),
"Neutrophils": (1.5e3, 11.0e3),
"Lymphocytes": (1.5e3, 11.1e3),
"Monocytes": (0.1e3, 1.9e3),
"Eosinophils": (0.0e3, 0.7e3),
"Basophils": (0.0e3, 0.3e3),
},
"Neonate": {
"WBC": (9.0e3, 37.0e3),
"RBC": (4.10e6, 6.10e6),
"HGB": (165, 215),
"HCT": (0.48, 0.68),
"HB": "TBD",
"MCV": (95, 125),
"MCH": (30, 42),
"MCHC": (300, 340),
"RDW": "TBD",
"PLT": (150e3, 450e3),
"Neutrophils": (3.7e3, 30.0e3),
"Lymphocytes": (1.6e3, 14.1e3),
"Monocytes": (0.1e3, 4.4e3),
"Eosinophils": (0.0e3, 1.5e3),
"Basophils": (0.0e3, 0.7e3),
},
}
)

For details on the AgeGroupDict,

Exploring CBC Metrics

The cbc_metrics.py file lays the groundwork for calculating key blood metrics, such as Mean Corpuscular Volume (MCV) and Neutrophil to Lymphocyte Ratio (NLR), among others.

"""Complete Blood Count Metrics."""


def mcv(hct: float, rbc: float) -> float:
"""Calculate Mean Corpuscular Volume (MCV).

:param hct: Hematocrit as a decimal (e.g., 0.425 = 42.5%).
:param rbc: Red Blood Cell count (cells per microliter, /µL).
:return: MCV in femtoliters (fL).
"""
return hct * 10e8 / rbc


def mch(hb: float, rbc: float) -> float:
"""Calculate Mean Corpuscular Hemoglobin (MCH).

:param hb: Hemoglobin level (in grams per deciliter, g/dL).
:param rbc: Red Blood Cell count (cells per microliter, /µL).
:return: MCH in picograms (pg).
"""
return hb * 10e6 / rbc


def mchc(hb: float, hct: float) -> float:
"""Calculate Mean Corpuscular Hemoglobin Concentration (MCHC).

:param hb: Hemoglobin level (in grams per deciliter, g/dL).
:param hct: Hematocrit as a decimal (e.g., 0.425 = 42.5%).
:return: MCHC in grams per deciliter (g/dL).
"""
return hb / hct


def nlr(absolute_anc: float, absolute_alc: float) -> float:
"""Calculate Neutrophil to Lymphocyte Ratio (NLR).

:param absolute_anc: Absolute Neutrophil Count (cells per microliter, /µL).
:param absolute_alc: Absolute Lymphocyte Count (cells per microliter, /µL).
:return: NLR (dimensionless).
"""
return absolute_anc / absolute_alc


def anc(wbc: float, nphi: float, bands: float) -> float:
"""Calculate Absolute Neutrophil Count (ANC).

:param wbc: White Blood Cell count (cells per microliter, /µL).
:param nphi: Proportion of mature neutrophils as a decimal.
:param bands: Proportion of immature neutrophile as a decimal.
:return: ANC (cells per microliter, /µL).
"""
return (nphi + bands) * wbc


def alc(wbc: float, lym: float) -> float:
"""Calculate Absolute Lymphocyte Count (ALC).

:param wbc: White Blood Cell count (cells per microliter, /µL).
:param lym: Proportion of lymphocytes in total WBC count as a decimal.
:return: ALC (cells per microliter, /µL).
"""
return wbc * lym

Thank You For Your Support!

This initial implementation of PyCBCount marks the beginning of our deep dive into blood analysis using Python.

Initially, the entire codebase will be made available at python-projects. As we progress, the functional parts will be integrated into pycbcount and released on PyPI.

Further Reading

If you want to learn more about programming and, specifically, machine learning, see the following course:

Note: If you use my links to order, I’ll get a small kickback. So, if you’re inclined to order anything, feel free to click above.

--

--