Bits & Flames bitsandflames/fyron

Imaging

fyron.imaging contains local medical imaging helpers built around SimpleITK, NumPy, and pydicom. Use it after files are already on disk: DICOM headers, DICOM series, NIfTI volumes, and normalized arrays for modeling or visualization.

Use this module after files are already on disk. It handles the practical tasks that usually sit between PACS download and analysis: reading headers, discovering series, loading volumes, preserving geometry, and normalizing intensities. Keep these operations explicit so spatial orientation, voxel spacing, and intensity scaling are traceable.

For generated GAN volumes that should be written with synthetic DICOM identifiers, use Synthetic DICOM/NIfTI export. For segmentation masks that need valid DICOM SEG objects, use DICOM SEG. For visual comparison of two already-aligned CT/MR volumes, use BOA Visualization.

Relationship To DICOM Downloads

Use fyron.dicom to download from DICOMweb. Use fyron.imaging once files are local.

Practical Imaging Notes

  • DICOM series can be nested differently across PACS exports; inspect discovered series before loading.
  • NumPy arrays follow image-library axis conventions, commonly (z, y, x).
  • CT normalization should use clinically meaningful HU windows.
  • MR normalization is scanner/protocol dependent; review percentile choices before modeling.
  • When writing NIfTI, pass a reference image when geometry matters.
  • For difference heatmaps, resample or align volumes explicitly before plotting; the visualization helper validates geometry but does not resample.
mermaid
flowchart LR
    A["DICOMweb endpoint"] --> B["fyron.dicom"]
    B --> C["Local DICOM / NIfTI files"]
    C --> D["fyron.imaging"]
    D --> E["Headers, SimpleITK images, NumPy arrays"]

Imports

python
from fyron import imaging as fim

Function Summary

FunctionWhat it doesTypical return
read_dicom_headerRead metadata from one DICOM filepydicom.Dataset or dict
get_dicom_series_idsDiscover series IDs in a DICOM folderlist[str] or DataFrame
read_dicom_seriesLoad one DICOM seriesSimpleITK.Image or (image, array)
read_niftiLoad a NIfTI fileSimpleITK.Image or (image, array)
write_niftiSave image/array as NIfTIPath
normalize_ctClip CT HU and scale to [0, 1]same container type as input
normalize_mrNormalize MR intensitiessame container type as input
summarize_dicom_series_geometrySummarize DICOM slice geometry and spacingDataFrame
summarize_nifti_geometrySummarize NIfTI shape, spacing, origin, directionDataFrame
check_image_mask_alignmentCompare image and mask geometrydict
summarize_mask_volumeCount mask voxels and volume in mlDataFrame
summarize_intensity_rangeSummarize min/max/mean/percentile intensitiesdict
dice_scoreDice overlap for binary masks or one labelfloat
hausdorff_distanceSymmetric Hausdorff distance between mask surfacesfloat
surface_diceSurface Dice at a tolerancefloat
volume_differenceAbsolute/relative mask volume differencedict
label_overlap_tableLabel-wise Dice and volume tableDataFrame
segmentation_metric_tableDice, Hausdorff, surface Dice, volume metricsDataFrame

read_dicom_header

Reads a single DICOM file without loading pixel data by default. Use this for metadata inspection, manifests, routing, or QA.

python
header = fim.read_dicom_header(
    "/data/patient_001/image001.dcm",
    selected_tags=["PatientID", "StudyInstanceUID", "SeriesInstanceUID", "Modality"],
    as_dict=True,
)

Parameters

ParameterRequiredTypeDefaultDescription
dicom_pathyes`strPath`nonePath to a DICOM Part 10 file.
stop_before_pixelsnoboolTrueSkip pixel data for faster metadata reads.
forcenoboolFalsePass force=True to pydicom for non-standard files.
as_dictnoboolFalseReturn a plain dictionary instead of a pydicom dataset.
selected_tagsno`list[str]None`NoneDICOM keywords or hex tags to extract, e.g. "PatientID" or "0010,0020".

Returns

CaseReturn
defaultfull pydicom.Dataset without pixel data
as_dict=Truedict[str, Any]
selected_tags without as_dictsubset pydicom.Dataset

Raises

  • FileNotFoundError if the file does not exist.
  • ValueError if the file is not valid DICOM.
  • KeyError if a requested selected tag is missing.

get_dicom_series_ids

Discovers DICOM series under a directory using SimpleITK/GDCM.

python
series = fim.get_dicom_series_ids(
    "/data/patient_001/study",
    recursive=True,
    include_metadata=True,
)

Parameters

ParameterRequiredTypeDefaultDescription
dicom_diryes`strPath`noneFolder containing DICOM files.
include_metadatanoboolFalseReturn a metadata DataFrame instead of only IDs.
recursivenoboolFalseScan subfolders that contain .dcm files.

Returns

OptionReturn
include_metadata=Falsesorted list[str] of series instance UIDs
include_metadata=TrueDataFrame with series_id, number_of_files, first_file, modality, series_description, study_instance_uid, series_instance_uid

read_dicom_series

Loads one DICOM series as a SimpleITK image, optionally with a NumPy array.

python
image, arr = fim.read_dicom_series(
    "/data/patient_001/study",
    series_id=series.iloc[0]["series_id"],
    return_array=True,
)

Parameters

ParameterRequiredTypeDefaultDescription
study_pathyes`strPath`noneDirectory containing the target DICOM series.
series_idyesstrnoneSeries instance UID to load.
return_arraynoboolFalseAlso return NumPy array in SimpleITK (z, y, x) order.

Returns

OptionReturn
return_array=FalseSimpleITK.Image
return_array=True(SimpleITK.Image, numpy.ndarray)

Notes

  • Use get_dicom_series_ids(..., include_metadata=True) first when a study folder contains multiple series.
  • If you use recursive=True for discovery, pass the actual series folder to read_dicom_series.

read_nifti

Loads a .nii or .nii.gz volume with SimpleITK.

python
image, arr = fim.read_nifti("ct.nii.gz", return_array=True)

Parameters

ParameterRequiredTypeDefaultDescription
nifti_pathyes`strPath`nonePath ending in .nii or .nii.gz.
return_arraynoboolFalseAlso return NumPy array in (z, y, x) order.

Returns

OptionReturn
return_array=FalseSimpleITK.Image
return_array=True(SimpleITK.Image, numpy.ndarray)

write_nifti

Writes a SimpleITK image or NumPy array to NIfTI.

python
out_path = fim.write_nifti(
    arr,
    "outputs/ct_normalized.nii.gz",
    reference_image=image,
)

Parameters

ParameterRequiredTypeDefaultDescription
imageyes`SimpleITK.Imagenumpy.ndarray`noneImage object or array in (z, y, x) order.
output_pathyes`strPath`noneDestination .nii or .nii.gz; parent folders are created.
reference_imageno`SimpleITK.ImageNone`NoneCopy spacing, origin, and direction from this image. Strongly recommended for arrays.
compressnoboolTrueRequest compression. .nii.gz is compressed regardless.

Returns

Resolved pathlib.Path to the written file.

normalize_ct

Clips CT Hounsfield units and scales intensities linearly to [0, 1] as float32.

python
ct_norm = fim.normalize_ct(
    arr,
    hu_min=-1000,
    hu_max=400,
)

Parameters

ParameterRequiredTypeDefaultDescription
imageyes`SimpleITK.Imagenumpy.ndarray`noneCT image or array in Hounsfield units.
hu_minnofloat-1000.0Lower clipping bound.
hu_maxnofloat3000.0Upper clipping bound; must be greater than hu_min.

Returns

Same container type as input:

  • NumPy input returns a numpy.ndarray.
  • SimpleITK input returns a SimpleITK.Image with geometry preserved.

Common Windows

Usehu_minhu_max
lung-1000400
soft tissue-160240
broad model input-10003000

normalize_mr

Normalizes MR intensities to a float scale, usually by dividing by a high percentile.

python
mr_norm = fim.normalize_mr(
    arr,
    mode="percentile",
    percentile=99,
    ignore_zeros=True,
)

Parameters

ParameterRequiredTypeDefaultDescription
imageyes`SimpleITK.Imagenumpy.ndarray`noneMR image or array.
modenostr"percentile"One of "percentile", "max", or "value".
percentilenofloat99.0Percentile divisor when mode="percentile".
valueconditionally`floatNone`NoneRequired positive divisor when mode="value".
ignore_zerosnoboolTrueExclude zeros when computing percentile or max.
clipnoboolTrueClip normalized output to [0, 1].

Returns

Same container type as input. For SimpleITK input, spacing, origin, and direction are preserved.

Choosing A Mode

ModeUse when
percentileyou want robust scaling and may have outliers
maxthe maximum intensity is meaningful and stable
valueyou have a fixed scanner/site-specific divisor

Imaging QC Helpers

fyron.imaging.qc adds small, table-friendly checks for the boring but crucial parts of imaging studies: spacing, orientation, mask alignment, volume sanity, and intensity ranges.

python
from fyron.imaging.qc import (
    check_image_mask_alignment,
    summarize_dicom_series_geometry,
    summarize_intensity_range,
    summarize_mask_volume,
    summarize_nifti_geometry,
)

nifti_qc = summarize_nifti_geometry(["ct.nii.gz", "mask.nii.gz"])
alignment = check_image_mask_alignment("ct.nii.gz", "mask.nii.gz")
mask_volume = summarize_mask_volume("mask.nii.gz", labels={1: "liver"})
intensity = summarize_intensity_range("ct.nii.gz", mask="mask.nii.gz")
FunctionRequiredOptionalReturns
summarize_dicom_series_geometry(dicom_dir, ...)dicom_dirrecursive=Falseone-row DataFrame with slice count, spacing, orientation, z range
summarize_nifti_geometry(paths)one path or list of pathsnoneDataFrame with size, spacing, origin, direction
check_image_mask_alignment(image, mask)image and mask paths or SimpleITK imagesnonedict with geometry checks and aligned
summarize_mask_volume(mask, ...)mask path, image, or arraylabels, spacinglabel-level volume table
summarize_intensity_range(image, ...)image path, image, or arraymask, percentilesintensity summary dict

The same checks are exposed through the CLI:

bash
fyron imaging-qc \
  --dicom-dir dicom/case_001 \
  --nifti ct.nii.gz \
  --mask liver_mask.nii.gz \
  --output qc.csv

Segmentation Metrics

Use fyron.imaging.segmentation when two masks are already on the same grid and you need numeric quality-control or validation metrics.

python
from fyron import imaging as fim

metrics = fim.segmentation_metric_table(
    reference_mask,
    predicted_mask,
    labels={1: "liver", 2: "spleen"},
    spacing=(1.0, 1.0, 2.0),
    surface_tolerance=2.0,
)

metrics[["label_name", "dice", "hausdorff_distance", "surface_dice"]]
FunctionRequiredOptionalReturns
dice_score(mask_true, mask_pred, ...)two aligned maskslabelDice score
hausdorff_distance(mask_true, mask_pred, ...)two aligned maskslabel, spacing, percentiledistance
surface_dice(mask_true, mask_pred, ...)two aligned maskslabel, tolerance, spacingsurface Dice
volume_difference(mask_true, mask_pred, ...)two aligned maskslabel, spacingvolume difference dict
label_overlap_table(mask_true, mask_pred, ...)two aligned maskslabels, spacinglabel-wise DataFrame
segmentation_metric_table(mask_true, mask_pred, ...)two aligned maskslabels, spacing, surface tolerancemetric DataFrame

Run check_image_mask_alignment before computing metrics when masks come from different pipelines or exports.

Practical Checklist

  • Confirm orientation and spacing before comparing patients.
  • Keep raw files unchanged; write normalized outputs to separate folders.
  • Preserve geometry with reference_image when saving derived NIfTI volumes.
  • Track series IDs in manifests when studies contain several acquisitions.
  • For segmentation overlays, ensure CT and mask arrays are aligned before rendering.