Skip to content

Commit 20a27c4

Browse files
authored
Merge pull request #13 from qurit/11_colors
11 colors
2 parents 1a7c7d7 + 9d374d1 commit 20a27c4

5 files changed

Lines changed: 119 additions & 14 deletions

File tree

rt_utils/rtstruct.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List
1+
from typing import List, Union
22

33
import numpy as np
44
from pydicom.dataset import FileDataset
@@ -24,7 +24,7 @@ def set_series_description(self, description: str):
2424

2525
self.ds.SeriesDescription = description
2626

27-
def add_roi(self, mask: np.ndarray, color=None, name=None, description='', use_pin_hole=False):
27+
def add_roi(self, mask: np.ndarray, color: Union[str, List[int]] = None, name: str = None, description: str = '', use_pin_hole: bool = False):
2828
"""
2929
Add a ROI to the rtstruct given a 3D binary mask for the ROI's at each slice
3030
Optionally input a color or name for the ROI

rt_utils/utils.py

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,38 @@
22
from pydicom.uid import PYDICOM_ROOT_UID
33
from dataclasses import dataclass
44

5-
class SOPClassUID():
6-
RTSTRUCT_IMPLEMENTATION_CLASS = PYDICOM_ROOT_UID # TODO find out if this is ok
5+
COLOR_PALETTE= [
6+
[255, 0, 255],
7+
[0, 235, 235],
8+
[255, 255, 0],
9+
[255, 0, 0],
10+
[0, 132, 255],
11+
[0, 240, 0],
12+
[255, 175, 0],
13+
[0, 208, 255],
14+
[180, 255, 105],
15+
[255, 20, 147],
16+
[160, 32, 240],
17+
[0, 255, 127],
18+
[255, 114, 0],
19+
[64, 224, 208],
20+
[0, 178, 47],
21+
[220, 20, 60],
22+
[238, 130, 238],
23+
[218, 165, 32],
24+
[255, 140, 190],
25+
[0, 0, 255],
26+
[255, 225, 0]
27+
]
28+
29+
30+
class SOPClassUID:
31+
RTSTRUCT_IMPLEMENTATION_CLASS = PYDICOM_ROOT_UID # TODO find out if this is ok
732
CT_IMAGE_STORAGE = '1.2.840.10008.5.1.4.1.1.2'
833
DETACHED_STUDY_MANAGEMENT = '1.2.840.10008.3.1.2.3.1'
934
RTSTRUCT = '1.2.840.10008.5.1.4.1.1.481.3'
1035

36+
1137
@dataclass
1238
class ROIData:
1339
"""Data class to easily pass ROI data to helper methods."""
@@ -20,15 +46,42 @@ class ROIData:
2046
use_pin_hole: bool = False
2147

2248
def __post_init__(self):
49+
self.validate_color()
2350
self.add_default_values()
2451

2552
def add_default_values(self):
26-
if self.color == None:
27-
self.color = self.get_random_colour()
53+
if self.color is None:
54+
self.color = COLOR_PALETTE[(self.number - 1) % len(COLOR_PALETTE)]
2855

29-
if self.name == None:
56+
if self.name is None:
3057
self.name = f"ROI-{self.number}"
31-
32-
def get_random_colour(self):
33-
max = 256
34-
return [randrange(max), randrange(max), randrange(max)]
58+
59+
def validate_color(self):
60+
if self.color is None:
61+
return
62+
63+
# Validating list eg: [0, 0, 0]
64+
if type(self.color) is list:
65+
if len(self.color) != 3:
66+
raise ValueError(f'{self.color} is an invalid color for an ROI')
67+
for c in self.color:
68+
try:
69+
assert 0 <= c <= 255
70+
except:
71+
raise ValueError(f'{self.color} is an invalid color for an ROI')
72+
73+
else:
74+
self.color: str = str(self.color)
75+
self.color = self.color.strip('#')
76+
77+
# fff -> ffffff
78+
if len(self.color) == 3:
79+
self.color = ''.join([x*2 for x in self.color])
80+
81+
if not len(self.color) == 6:
82+
raise ValueError(f'{self.color} is an invalid color for an ROI')
83+
84+
try:
85+
self.color = [int(self.color[i:i+2], 16) for i in (0, 2, 4)]
86+
except Exception as e:
87+
raise ValueError(f'{self.color} is an invalid color for an ROI')

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import setuptools
22

3-
VERSION = '1.0.5'
3+
VERSION = '1.1.0'
44
with open("README.md", "r", encoding="utf-8") as fh:
55
long_description = fh.read()
66
with open('requirements.txt') as f:

tests/test_rtstruct_builder.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def test_add_valid_roi(new_rtstruct: RTStruct):
5555
assert len(new_rtstruct.ds.RTROIObservationsSequence) == 0
5656

5757
NAME = "Test ROI"
58-
COLOR = [123, 321, 456]
58+
COLOR = [123, 123, 232]
5959
mask = get_empty_mask(new_rtstruct)
6060
mask[50:100, 50:100, 0] = 1
6161

@@ -163,4 +163,3 @@ def get_empty_mask(rtstruct) -> np.ndarray:
163163
mask_dims = (int(ref_dicom_image.Columns), int(ref_dicom_image.Rows), len(rtstruct.series_data))
164164
mask = np.zeros(mask_dims)
165165
return mask.astype(bool)
166-

tests/test_utils.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import pytest
2+
3+
from rt_utils.utils import COLOR_PALETTE
4+
from tests.test_rtstruct_builder import get_empty_mask
5+
6+
7+
VALID_COLORS = [
8+
('fff', [255, 255, 255]),
9+
('#fff', [255, 255, 255]),
10+
(None, COLOR_PALETTE[0]),
11+
(COLOR_PALETTE[1], COLOR_PALETTE[1]),
12+
('#696969', [105, 105, 105]),
13+
('a81414', [168, 20, 20]),
14+
('#000', [0, 0, 0]),
15+
]
16+
17+
INVALID_COLORS = [
18+
('GGG', ValueError),
19+
('red', ValueError),
20+
('22', ValueError),
21+
('[]', ValueError),
22+
([], ValueError),
23+
([24, 34], ValueError),
24+
([24, 34, 454], ValueError),
25+
([0, 344, 0], ValueError),
26+
('a8141', ValueError),
27+
('a814111', ValueError),
28+
(KeyboardInterrupt, ValueError),
29+
]
30+
31+
32+
@pytest.mark.parametrize('color', VALID_COLORS)
33+
def test_mask_colors(new_rtstruct, color):
34+
color_in, color_out = color
35+
36+
name = "Test ROI"
37+
mask = get_empty_mask(new_rtstruct)
38+
mask[50:100, 50:100, 0] = 1
39+
40+
new_rtstruct.add_roi(mask, color=color_in, name=name)
41+
assert new_rtstruct.ds.ROIContourSequence[0].ROIDisplayColor == color_out
42+
43+
44+
@pytest.mark.parametrize('color', INVALID_COLORS)
45+
def test_mask_colors_fail(new_rtstruct, color):
46+
color_in, err = color
47+
48+
name = "Test ROI"
49+
mask = get_empty_mask(new_rtstruct)
50+
mask[50:100, 50:100, 0] = 1
51+
52+
with pytest.raises(err):
53+
new_rtstruct.add_roi(mask, color=color_in, name=name)

0 commit comments

Comments
 (0)