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 . 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 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