From 19c5e543e9049458d99f1a0fb89f59b3155ad40d Mon Sep 17 00:00:00 2001 From: cbarkr Date: Thu, 18 Dec 2025 13:14:02 -0800 Subject: [PATCH 1/3] deps: `opencv-python` -> `opencv-python-headless` --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5633c9b..8faf7b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ pydicom numpy -opencv-python>=4.0.0 +opencv-python-headless>=4.0.0 dataclasses \ No newline at end of file From 48dc79ca2c93f6e6d18e1ea28c64acf5b7316bb9 Mon Sep 17 00:00:00 2001 From: cbarkr Date: Thu, 18 Dec 2025 13:14:22 -0800 Subject: [PATCH 2/3] docs: remove libGL as system dependency --- README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 52c1dc6..21a6d91 100644 --- a/README.md +++ b/README.md @@ -26,18 +26,12 @@ RT-Utils provides a builder class to faciliate the creation and loading of an RT The format for the ROI mask is an nd numpy array of type bool. It is an array of 2d binary masks, one plane for each slice location within the DICOM series. The slices should be sorted in ascending order within the mask. Through these masks, we extract the contours of the regions of interest and place them within the RT Struct file. Note that there is currently only support for the use of one frame of reference UID and structered set ROI sequence. Also note that holes within the ROI may be handled poorly. ## Installation -``` +```bash pip install rt_utils ``` -## -**libGL** - libGL is a required system dependency. Install it on Linux using: - ``` - ```bash - sudo apt install libgl1 -``` + ## Installation in editable mode -``` +```bash git clone https://github.com/qurit/rt-utils.git cd rt-utils pip install -e . From 59e301118c7bccbe5ec47c005644965415e6e807 Mon Sep 17 00:00:00 2001 From: cbarkr Date: Thu, 18 Dec 2025 13:15:34 -0800 Subject: [PATCH 3/3] test: add some `image_helper` sanity checks --- tests/test_image_helper.py | 79 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tests/test_image_helper.py diff --git a/tests/test_image_helper.py b/tests/test_image_helper.py new file mode 100644 index 0000000..853d23d --- /dev/null +++ b/tests/test_image_helper.py @@ -0,0 +1,79 @@ +import pytest +import numpy as np + +from rt_utils.image_helper import ( + load_sorted_image_series, + create_empty_series_mask, + find_mask_contours, + draw_line_upwards_from_point, +) + + +def create_full_series_mask(series_data): + ref_dicom_image = series_data[0] + mask_dims = ( + int(ref_dicom_image.Columns), + int(ref_dicom_image.Rows), + len(series_data), + ) + mask = np.ones(mask_dims).astype(bool) + return mask + + +def test_find_mask_contours_with_empty_mask(series_path): + series_data = load_sorted_image_series(series_path) + empty_mask = create_empty_series_mask(series_data)[:, :, 0] + + # `hierarchy` is `None` in this case, thus `hierarchy = hierarchy[0]` fails + with pytest.raises(TypeError): + actual_contours, actual_hierarchy = find_mask_contours(empty_mask, True) + + +def test_find_mask_contours_with_full_mask(series_path): + series_data = load_sorted_image_series(series_path) + full_mask = create_full_series_mask(series_data)[:, :, 0] + + # Entire mask is a single contour + expected_contours = [ + [ + [np.int32(0), np.int32(0)], + [np.int32(0), np.int32(511)], + [np.int32(511), np.int32(511)], + [np.int32(511), np.int32(0)], + ] + ] + expected_hierarchy = [[np.int32(-1), np.int32(-1), np.int32(-1), np.int32(-1)]] + + actual_contours, actual_hierarchy = find_mask_contours(full_mask, True) + + assert np.array_equal(expected_contours, actual_contours) == True + assert np.array_equal(expected_hierarchy, actual_hierarchy) == True + + +def test_draw_line_upwards_from_point_with_empty_mask(series_path): + series_data = load_sorted_image_series(series_path) + empty_mask = create_empty_series_mask(series_data)[:, :, 0] + start = (0, 0) + + # Should fill (0,0) and one point in each direction + expected_binary_mask = empty_mask.copy() + expected_binary_mask[0][0] = True + expected_binary_mask[0][1] = True + expected_binary_mask[1][0] = True + + actual_binary_mask = draw_line_upwards_from_point(empty_mask, start, 1) + + assert np.array_equal(expected_binary_mask, actual_binary_mask) == True + + +def test_draw_line_upwards_from_point_with_full_mask(series_path): + series_data = load_sorted_image_series(series_path) + full_mask = create_full_series_mask(series_data)[:, :, 0] + start = (0, 0) + + # Should remain unchanged + expected_binary_mask = full_mask.copy() + + actual_binary_mask = draw_line_upwards_from_point(full_mask, start, 1) + + assert np.array_equal(expected_binary_mask, actual_binary_mask) == True