Skip to content
Open
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Ignore

# Cache
# Caches
__pycache__/
.pytest_cache/

# Virtual environments
venv*/
Expand Down
43 changes: 40 additions & 3 deletions dicomparser/DICOMParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,18 +134,55 @@ def extract_common_metadata(self):

@staticmethod
def get_bscan_images_from_pixel_array(pixel_arr):
bscan_count = pixel_arr.shape[0]
bscan_images = {}
"""
Converts a 3D numpy array to a dictionary of PIL Image objects.

Parameters:
-----------
pixel_arr : numpy.ndarray
3D array with shape (n_scans, height, width)
First dimension represents individual B-scan slices

Returns:
--------
dict
Dictionary with keys "bscan{i+1}" and values as PIL Image objects
B-scans are numbered starting from 1 in the output keys
"""
bscan_count = pixel_arr.shape[0] # Get number of B-scans
bscan_images = {} # Initialize empty dictionary

# Process each B-scan slice
for i in range(bscan_count):
# Convert 2D array slice to PIL Image
bscan_image = Image.fromarray(pixel_arr[i, :, :])

# Store in dictionary with 1-based indexing
bscan_images[f"bscan{i+1}"] = bscan_image

return bscan_images

@staticmethod
def save_bscan_images(meta, output_pth):
"""
Saves B-scan images from the metadata dictionary to the specified output path.

Parameters:
-----------
meta : dict
Dictionary containing 'SOP Instance' ID and 'bscan_images' dictionary
'bscan_images' should contain PIL Image objects

output_pth : str
Base directory where images will be saved
"""
# Create folder path using SOP Instance ID
sop_path = os.path.join(output_pth, f"{meta['SOP Instance']}")
if not os.path.exists(sop_path): os.makedirs(sop_path) # make pdf (png) folder

# Create directory if it doesn't exist
if not os.path.exists(sop_path): os.makedirs(sop_path)

# Save each B-scan image as PNG
for bscan in meta['bscan_images'].keys():
meta['bscan_images'][bscan].save(os.path.join(sop_path, f"{bscan}.png"))

Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ authors = [
{name = "bbearce",email = "[email protected]"}
]
readme = "README.md"
requires-python = ">=3.12"
requires-python = ">=3.10"
dependencies = [
"hvf-extraction-script (>=0.0.4,<0.0.5)",
"matplotlib (>=3.10.1,<4.0.0)",
"pymupdf (>=1.25.4,<2.0.0)",
"oct-converter (>=0.6.0,<0.6.3)"
"oct-converter (>=0.6.0,<0.6.3)",
"pytest (>=8.3.5, <8.3.6)",
"pytest-cov (>=6.1.1,<6.1.2)",
"tox (>=4.25.0,<4.26.0)"
]


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest
import numpy as np
from PIL import Image
from dicomparser.DICOMParser import DICOMParser

@pytest.mark.filterwarnings("ignore")
@pytest.mark.parametrize("pixel_arr, expected_keys, expected_shapes", [
# Test case 1: Single B-scan, 2x2 image
(np.array([[[1, 2],
[3, 4]]], dtype=np.uint8),
["bscan1"],
[(2, 2)]),

# Test case 2: Two B-scans, 3x3 images
(np.array([[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]],
[[9, 8, 7],
[6, 5, 4],
[3, 2, 1]]], dtype=np.uint8),
["bscan1", "bscan2"],
[(3, 3), (3, 3)]),

# Test case 3: Empty input (0 B-scans)
(np.empty((0, 2, 2), dtype=np.uint8),
[],
[]),
])
def test_get_bscan_images_from_pixel_array(pixel_arr, expected_keys, expected_shapes):
"""
Test the conversion of a 3D numpy array to a dictionary of PIL Images.
"""
dicom_path = '/persist/QTIM/Active/23-0284/dashboard/Data/topcon_samples_from_steve_03_27_2025/OCT/100100_10142024_151925_OP_R_001.dcm'
dicom_parser = DICOMParser(dicom_path=dicom_path)
# Act
result = dicom_parser.get_bscan_images_from_pixel_array(pixel_arr)

# Assert
assert list(result.keys()) == expected_keys
assert all(isinstance(result[k], Image.Image) for k in expected_keys)
assert [result[k].size for k in expected_keys] == [(w, h) for h, w in expected_shapes]
50 changes: 50 additions & 0 deletions tests/dicomparser/base_class/test_save_bscan_images.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import pytest
import numpy as np
import os
from PIL import Image
from dicomparser.DICOMParser import DICOMParser

@pytest.fixture
def dicom_parser():
dicom_path = '/persist/QTIM/Active/23-0284/dashboard/Data/topcon_samples_from_steve_03_27_2025/OCT/100100_10142024_151925_OP_R_001.dcm'
return DICOMParser(dicom_path=dicom_path)

@pytest.mark.parametrize("bscan_count, img_shape", [
(1, (10, 10)),
(3, (5, 5)),
(19, (8, 8))
])
def test_save_bscan_images(dicom_parser, tmp_path, bscan_count, img_shape):
"""
Test that B-scan images are correctly saved to disk and match the original image content.
"""
# Create dummy pixel data and images
bscan_images = {}
original_arrays = []

for i in range(bscan_count):
arr = np.random.randint(0, 255, img_shape, dtype=np.uint8)
original_arrays.append(arr)
bscan_images[f"bscan{i+1}"] = Image.fromarray(arr)

meta = {
"SOP Instance": "TEST123",
"bscan_images": bscan_images
}

# Use temporary directory for output
dicom_parser.save_bscan_images(meta, tmp_path)

# Check that all images were saved correctly
saved_dir = os.path.join(tmp_path, "TEST123")
assert os.path.isdir(saved_dir)

for i in range(bscan_count):
fname = f"bscan{i+1}.png"
fpath = os.path.join(saved_dir, fname)
assert os.path.exists(fpath)

# Open saved image and compare pixel values
saved_img = Image.open(fpath).convert("L") # Ensure grayscale
saved_arr = np.array(saved_img)
np.testing.assert_array_equal(saved_arr, original_arrays[i])
19 changes: 19 additions & 0 deletions tests/example/test_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import pytest


@pytest.mark.parametrize("num1, num2, expected", [
(2, 3, 6),
(5, 4, 20),
(10, 0, 0),
(-2, 3, -6),
(2.5, 2, 5.0),
])
def test_multiplication(num1, num2, expected):
"""
Test the multiplication of two numbers.
"""
# Act
result = num1 * num2

# Assert
assert result == expected