Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 106 additions & 6 deletions src/allotropy/parsers/beckman_vi_cell_xr/vi_cell_xr_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from dataclasses import dataclass
from io import StringIO
import re
from typing import Any
from typing import Any, ClassVar

import openpyxl
import pandas as pd

from allotropy.named_file_contents import NamedFileContents
Expand Down Expand Up @@ -33,12 +34,34 @@ class ViCellData:
version: XrVersion


def _is_report_format(named_file_contents: NamedFileContents) -> bool:
"""Detect the single-sample report format by checking for key-value layout."""
if named_file_contents.extension not in ("xls", "xlsx"):
return False
try:
wb = openpyxl.load_workbook(
named_file_contents.get_bytes_stream(), read_only=True, data_only=True
)
ws = wb[wb.sheetnames[0]]
rows = list(ws.iter_rows(min_row=4, max_row=4, values_only=True))
wb.close()
if rows and rows[0][0] == "Sample ID":
return True
except Exception:
return False
return False


def create_reader_data(named_file_contents: NamedFileContents) -> ViCellData:
reader: ViCellXRReader | ViCellXRTXTReader = (
ViCellXRTXTReader(named_file_contents)
if named_file_contents.extension == "txt"
else ViCellXRReader(named_file_contents)
)
if named_file_contents.extension == "txt":
reader: ViCellXRReader | ViCellXRTXTReader | ViCellXRReportReader = (
ViCellXRTXTReader(named_file_contents)
)
elif _is_report_format(named_file_contents):
named_file_contents.contents.seek(0)
reader = ViCellXRReportReader(named_file_contents)
else:
reader = ViCellXRReader(named_file_contents)
return ViCellData(reader.data, reader.serial_number, reader.version)


Expand Down Expand Up @@ -127,6 +150,83 @@ def _get_file_info(self) -> SeriesData:
return SeriesData(info)


class ViCellXRReportReader:
"""Reader for the single-sample report format exported by Vi-CELL XR 2.04."""

data: list[SeriesData]
serial_number: str | None
version: XrVersion

RESULTS_FIELDS: ClassVar[dict[str, int]] = {
"Total cells": 9,
"Viable cells": 10,
"Viability (%)": 11,
"Total cells/ml (x10^6)": 12,
"Viable cells/ml (x10^6)": 13,
"Avg. diam. (microns)": 14,
"Avg. circ.": 15,
"Images": 16,
"Average cells / image": 17,
"Avg. background intensity": 18,
}

SETTINGS_FIELDS: ClassVar[dict[str, int]] = {
"Cell type": 9,
"Minimum diameter (microns)": 10,
"Maximum diameter (microns)": 11,
"Minimum circularity": 12,
"Dilution factor": 13,
"Cell brightness (%)": 14,
"Cell sharpness": 15,
"Viable cell spot brightness (%)": 16,
"Viable cell spot area (%)": 17,
"Decluster degree": 18,
"Aspirate cycles": 19,
"Trypan blue mixing cycles": 20,
}

def __init__(self, named_file_contents: NamedFileContents) -> None:
wb = openpyxl.load_workbook(
named_file_contents.get_bytes_stream(), read_only=True, data_only=True
)
ws = wb[wb.sheetnames[0]]
self.rows = list(ws.iter_rows(values_only=True))
wb.close()

self.version = _get_file_version(str(self.rows[0][0]))
self.serial_number = None
self.data = self._read_data()

def _read_data(self) -> list[SeriesData]:
data: dict[str, Any] = {}

data["Sample ID"] = self.rows[3][2]
data["File name"] = self.rows[4][2]
data[DATE_HEADER] = self.rows[5][2]
comment = self.rows[6][2] if len(self.rows) > 6 else None
if comment:
data["Comment"] = comment

for field, row_idx in self.RESULTS_FIELDS.items():
if row_idx < len(self.rows):
val = self.rows[row_idx][3]
if val is not None:
data[field] = val

for field, row_idx in self.SETTINGS_FIELDS.items():
if row_idx < len(self.rows):
val = self.rows[row_idx][8]
if val is not None:
data[field] = val

series = pd.Series(data)
series[DATE_HEADER] = pd.to_datetime(
series[DATE_HEADER],
format="%d %b %Y %I:%M:%S %p",
)
return [SeriesData(series)]


class ViCellXRTXTReader:
data: list[SeriesData]
serial_number: str | None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
"$asm.manifest": "http://purl.allotrope.org/manifests/cell-counting/REC/2024/09/cell-counting.manifest",
"cell counting aggregate document": {
"data system document": {
"ASM file identifier": "Beckman_Vi-Cell-XR_report_format.json",
"data system instance identifier": "N/A",
"ASM converter name": "allotropy_beckman_coulter_vi_cell_xr",
"ASM converter version": "0.1.134",
"file name": "Beckman_Vi-Cell-XR_report_format.xlsx",
"software name": "Vi-Cell XR",
"software version": "2.04",
"UNC path": "tests/parsers/beckman_vi_cell_xr/testdata/v2.04/Beckman_Vi-Cell-XR_report_format.xlsx"
},
"device system document": {
"device identifier": "N/A",
"model number": "Vi-Cell XR"
},
"cell counting document": [
{
"measurement aggregate document": {
"measurement document": [
{
"device control aggregate document": {
"device control document": [
{
"detection type": "brightfield",
"device type": "brightfield imager (cell counter)",
"custom information document": {
"Trypan blue mixing cycles": 3.0,
"Aspirate cycles": 1.0
}
}
]
},
"measurement identifier": "BECKMAN_VI_CELL_XR_TEST_ID_0",
"measurement time": "2026-05-11T14:59:27+00:00",
"processed data aggregate document": {
"processed data document": [
{
"data processing document": {
"cell type processing method": "Default",
"cell density dilution factor": {
"value": 1.0,
"unit": "(unitless)"
},
"minimum cell diameter setting": {
"value": 5.0,
"unit": "µm"
},
"maximum cell diameter setting": {
"value": 50.0,
"unit": "µm"
},
"custom information document": {
"Decluster degree": "Medium",
"Viable cell spot area (%)": 5.0,
"Minimum circularity": 0.0,
"Cell brightness (%)": 85.0,
"Viable cell spot brightness (%)": 75.0,
"Cell sharpness": 100.0
}
},
"viability (cell counter)": {
"value": 86.99453735351562,
"unit": "%"
},
"total cell density (cell counter)": {
"value": 0.9703072143554687,
"unit": "10^6 cells/mL"
},
"viable cell density (cell counter)": {
"value": 0.8441272808074951,
"unit": "10^6 cells/mL"
},
"average total cell diameter": {
"value": 16.80272565612793,
"unit": "µm"
},
"total cell count": {
"value": 915,
"unit": "cell"
},
"viable cell count": {
"value": 796,
"unit": "cell"
},
"average total cell circularity": {
"value": 0.6407691074371338,
"unit": "(unitless)"
},
"custom information document": {
"Avg. background intensity": 202.89999389648438,
"Average cells / image": 18.299999237060547
}
}
]
},
"sample document": {
"sample identifier": "7B"
},
"image aggregate document": {
"image document": [
{
"custom information document": {
"Images": 50.0
}
}
]
}
}
],
"custom information document": {
"experimental data identifier": "C:\\ViCELLXR\\Data\\7B.txt"
}
},
"analyst": "Vi-Cell XR"
}
]
}
}
Binary file not shown.
Loading