pydicomrt 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pydicomrt/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ def main() -> None:
2
+ print("Hello from pydicomrt!")
@@ -0,0 +1,40 @@
1
+ from pydicom.uid import UID
2
+
3
+
4
+ CT_IMAGE_IOD = {
5
+ "PatientID": {},
6
+ "PatientName": {},
7
+ "PatientBirthDate": {},
8
+ "PatientSex": {},
9
+ "StudyInstanceUID": {},
10
+ "StudyID": {},
11
+ "StudyDate": {},
12
+ "StudyTime": {},
13
+ "AccessionNumber": {},
14
+ "SeriesInstanceUID": {"type": UID},
15
+ "SeriesNumber": {},
16
+ "Modality": {"value": "CT"},
17
+ "Manufacturer": {},
18
+ "FrameOfReferenceUID": {"type": UID},
19
+ "SOPClassUID": {"type": UID},
20
+ "SOPInstanceUID": {"type": UID},
21
+ "InstanceNumber": {},
22
+
23
+ "SliceThickness": {},
24
+ "ImagePositionPatient": {},
25
+ "ImageOrientationPatient": {},
26
+ "PixelSpacing": {},
27
+
28
+ "Rows": {},
29
+ "Columns": {},
30
+
31
+ "ImageType": {},
32
+ "BitsAllocated": {},
33
+ "BitsStored": {},
34
+ "HighBit": {},
35
+ "RescaleIntercept": {},
36
+ "RescaleSlope": {},
37
+
38
+ "PixelData": {},
39
+
40
+ }
File without changes
@@ -0,0 +1,156 @@
1
+ import os
2
+ import numpy as np
3
+ import SimpleITK as sitk
4
+
5
+ from datetime import datetime
6
+ from pydicom.tag import Tag
7
+ from pydicom.dataset import Dataset, FileDataset, FileMetaDataset
8
+ from pydicom.sequence import Sequence
9
+ from pydicom.uid import generate_uid
10
+ from pydicom.uid import ImplicitVRLittleEndian
11
+
12
+
13
+ DICOM_UID_PREFIX = os.getenv('DICOM_UID_PREFIX', "1.2.826.0.1.3680043.8.498.") # PYDICOM_ROOT_UID
14
+
15
+ def generate_base_dataset() -> FileDataset:
16
+ file_name = "bear_dose"
17
+ file_meta = get_file_meta()
18
+ ds = FileDataset(file_name, {}, file_meta=file_meta, preamble=b"\0" * 128)
19
+ add_required_elements_to_ds(ds)
20
+ add_sequence_lists_to_ds(ds)
21
+ return ds
22
+
23
+ def get_file_meta() -> FileMetaDataset:
24
+ file_meta = FileMetaDataset()
25
+ file_meta.FileMetaInformationGroupLength = 190
26
+ file_meta.FileMetaInformationVersion = b"\x00\x01"
27
+ file_meta.TransferSyntaxUID = ImplicitVRLittleEndian
28
+ file_meta.MediaStorageSOPClassUID = "1.2.840.10008.5.1.4.1.1.481.2"
29
+ file_meta.MediaStorageSOPInstanceUID = (
30
+ generate_uid(prefix=DICOM_UID_PREFIX)
31
+ )
32
+ file_meta.ImplementationClassUID = DICOM_UID_PREFIX + "1"
33
+ # TODO file_meta.ImplementationVersionName = ""
34
+ return file_meta
35
+
36
+ def add_required_elements_to_ds(ds: FileDataset):
37
+ dt = datetime.now()
38
+ # Append data elements required by the DICOM standarad
39
+ ds.SpecificCharacterSet = "ISO_IR 192"
40
+ ds.InstanceCreationDate = dt.strftime("%Y%m%d")
41
+ ds.InstanceCreationTime = dt.strftime("%H%M%S")
42
+ ds.Modality = "RTDOSE"
43
+ ds.Manufacturer = "higumalu"
44
+ ds.ManufacturerModelName = "modelv1"
45
+ ds.InstitutionName = "higumalu"
46
+ # Set the transfer syntax
47
+ ds.is_little_endian = True
48
+ ds.is_implicit_VR = True
49
+ # Set values already defined in the file meta
50
+ ds.SOPClassUID = ds.file_meta.MediaStorageSOPClassUID
51
+ ds.SOPInstanceUID = ds.file_meta.MediaStorageSOPInstanceUID
52
+
53
+ ds.ApprovalStatus = "UNAPPROVED"
54
+
55
+ def add_sequence_lists_to_ds(ds: FileDataset):
56
+ ds.ReferencedRTPlanSequence = Sequence()
57
+
58
+ # ---------------------------------------------------------------------------------------- #
59
+
60
+ def add_patient_information(ds: FileDataset, reference_ds: Dataset):
61
+ ds.PatientName = getattr(reference_ds, "PatientName", "Unknown")
62
+ ds.PatientID = getattr(reference_ds, "PatientID", "Unknown")
63
+ ds.PatientBirthDate = getattr(reference_ds, "PatientBirthDate", "")
64
+ ds.PatientSex = getattr(reference_ds, "PatientSex", "")
65
+ ds.PatientAge = getattr(reference_ds, "PatientAge", "")
66
+ ds.PatientSize = getattr(reference_ds, "PatientSize", "")
67
+ ds.PatientWeight = getattr(reference_ds, "PatientWeight", "")
68
+ return ds
69
+
70
+ def add_study_information(ds: FileDataset, reference_ds: Dataset):
71
+ dt = datetime.now()
72
+ ds.StudyInstanceUID = getattr(reference_ds, "StudyInstanceUID", generate_uid(prefix=DICOM_UID_PREFIX))
73
+ ds.StudyDate = getattr(reference_ds, "StudyDate", dt.strftime("%Y%m%d"))
74
+ ds.StudyTime = getattr(reference_ds, "StudyTime", dt.strftime("%H%M%S"))
75
+ ds.StudyID = getattr(reference_ds, "StudyID", "")
76
+ ds.AccessionNumber = getattr(reference_ds, "AccessionNumber", "")
77
+ ds.ReferringPhysicianName = getattr(reference_ds, "ReferringPhysicianName", "")
78
+ return ds
79
+
80
+ def add_series_information(ds: FileDataset, reference_ds: Dataset, series_number: int = 1):
81
+ dt = datetime.now()
82
+ ds.Modality = "RTDOSE"
83
+ ds.SeriesInstanceUID = generate_uid(prefix=DICOM_UID_PREFIX)
84
+ ds.SeriesDate = dt.strftime("%Y%m%d")
85
+ ds.SeriesTime = dt.strftime("%H%M%S")
86
+ ds.SeriesDescription = getattr(reference_ds, "SeriesDescription", "") + "_dose" + dt.strftime("%Y%m%d%H%M%S")
87
+ ds.SeriesNumber = getattr(reference_ds, "SeriesNumber", series_number)
88
+ ds.OperatorsName = getattr(reference_ds, "OperatorsName", "")
89
+ return ds
90
+
91
+ def add_frame_of_reference_information(ds: FileDataset, reference_ds: Dataset):
92
+ ds.FrameOfReferenceUID = getattr(reference_ds, "FrameOfReferenceUID", generate_uid(prefix=DICOM_UID_PREFIX))
93
+ ds.PositionReferenceIndicator = getattr(reference_ds, "PositionReferenceIndicator", "")
94
+ return ds
95
+
96
+ # ---------------------------------------------------------------------------------------- #
97
+
98
+ def cp_information_from_ds(
99
+ ds: FileDataset,
100
+ reference_ds: Dataset,
101
+ ):
102
+ ds = add_patient_information(ds, reference_ds)
103
+ ds = add_study_information(ds, reference_ds)
104
+ ds = add_series_information(ds, reference_ds)
105
+ ds = add_frame_of_reference_information(ds, reference_ds)
106
+ return ds
107
+
108
+ # ---------------------------------------------------------------------------------------- #
109
+
110
+ def add_dose_grid_to_ds(
111
+ ds: FileDataset,
112
+ dose_sitk_image: sitk.Image,
113
+ ):
114
+ scaling = 1e-7
115
+ array = sitk.GetArrayFromImage(dose_sitk_image)
116
+ size = list(dose_sitk_image.GetSize())
117
+ spacing = list(dose_sitk_image.GetSpacing())
118
+ direction = list(dose_sitk_image.GetDirection())
119
+ origin = list(dose_sitk_image.GetOrigin())
120
+
121
+ ds.SamplesPerPixel = 1
122
+ ds.PhotometricInterpretation = "MONOCHROME2"
123
+ ds.BitsAllocated = 32
124
+ ds.BitsStored = 32
125
+ ds.HighBit = 31
126
+ ds.PixelRepresentation = True
127
+ ds.DoseUnits = "GY"
128
+ ds.DoseType = "EFFECTIVE"
129
+ ds.DoseSummationType = "PLAN"
130
+ ds.NumberOfFrames = size[2]
131
+ ds.FrameIncrementPointer = Tag(0x3004, 0x000C)
132
+
133
+ ds.ImagePositionPatient = origin
134
+ ds.ImageOrientationPatient = direction
135
+ ds.PixelSpacing = spacing[0:2]
136
+ ds.SliceThickness = spacing[2]
137
+ ds.Columns = size[0]
138
+ ds.Rows = size[1]
139
+ ds.GridFrameOffsetVector = [ov * spacing[2] for ov in range(size[2])]
140
+ ds.DoseGridScaling = scaling
141
+ ds.PixelData = (array // scaling).astype(np.uint32).tobytes()
142
+ return ds
143
+
144
+
145
+ # -------------------------------- example --------------------------------------------------- #
146
+
147
+ if __name__ == "__main__":
148
+ import pydicom
149
+ import SimpleITK as sitk
150
+ dose_sitk_image = sitk.ReadImage("example/data/DF_001/RD_003/CT_001_dose.dcm")
151
+ ds = pydicom.dcmread("example/data/DF_001/RD_003/CT_001.dcm")
152
+
153
+ dose_ds = generate_base_dataset()
154
+ dose_ds = cp_information_from_ds(dose_ds, ds)
155
+ dose_ds = add_dose_grid_to_ds(dose_ds, dose_sitk_image)
156
+ dose_ds.save_as("example/data/DF_001/RD_003/dose.dcm")
@@ -0,0 +1,42 @@
1
+ from pydicom.uid import UID
2
+
3
+ # RT Dose CIOD
4
+ RT_DOSE_IOD = {
5
+ "PatientID": {},
6
+ "PatientName": {},
7
+ "PatientBirthDate": {},
8
+ "PatientSex": {},
9
+ "StudyInstanceUID": {},
10
+ "StudyID": {},
11
+ "StudyDate": {},
12
+ "StudyTime": {},
13
+ "AccessionNumber": {},
14
+ "SeriesInstanceUID": {"type": UID},
15
+ "SeriesNumber": {},
16
+ "Modality": {"value": "RTDOSE"},
17
+ "Manufacturer": {},
18
+ "FrameOfReferenceUID": {"type": UID},
19
+ "SOPClassUID": {"type": UID},
20
+ "SOPInstanceUID": {"type": UID},
21
+ "InstanceNumber": {},
22
+
23
+ "NumberOfFrames": {},
24
+ "Rows": {},
25
+ "Columns": {},
26
+ "PixelSpacing": {},
27
+ "ImageOrientationPatient": {},
28
+ "ImagePositionPatient": {}, # if value == (3004,000C) GridFrameOffsetVector is required
29
+ "SliceThickness": {"optional": True},
30
+
31
+ "BitsAllocated": {},
32
+ "BitsStored": {},
33
+ "HighBit": {},
34
+ "PixelRepresentation": {},
35
+
36
+ "DoseUnits": {"value": "GY"},
37
+ "DoseType": {},
38
+ "DoseSummationType": {"value": "PLAN"},
39
+ "GridFrameOffsetVector": {},
40
+
41
+ "PixelData": {},
42
+ }
File without changes
@@ -0,0 +1,21 @@
1
+
2
+
3
+ class SpatialRegistrationBuilder:
4
+ def __init__(self, fixed_ds_list, moving_ds_list, transform_matrix):
5
+ self.fixed_ds_list = fixed_ds_list
6
+ self.moving_ds_list = moving_ds_list
7
+ self.transform_matrix = transform_matrix
8
+
9
+
10
+ class DeformableSpatialRegistrationBuilder:
11
+ def __init__(
12
+ self, fixed_ds_list, moving_ds_list,
13
+ pre_transform,
14
+ vectorial_field,
15
+ post_transform
16
+ ):
17
+ self.fixed_ds_list = fixed_ds_list
18
+ self.moving_ds_list = moving_ds_list
19
+ self.pre_transform = pre_transform
20
+ self.vectorial_field = vectorial_field
21
+ self.post_transform = post_transform
pydicomrt/reg/check.py ADDED
@@ -0,0 +1,30 @@
1
+ from pydicom.dataset import Dataset
2
+ from .ds_reg_ds_iod import DEFORMABLE_SPATIAL_REGSITRATION_IOD
3
+ from .s_reg_ds_iod import SPATIAL_REGSITRATION_IOD
4
+ from pydicomrt.utils.validate_dcm_info import check_iod
5
+
6
+
7
+ def check_ds_reg_iod(rs_ds: Dataset) -> dict:
8
+ result_dict = {
9
+ "result": True,
10
+ "content": []
11
+ }
12
+ failed_item_list = []
13
+ failed_item_list = check_iod(ds=rs_ds, config_map=DEFORMABLE_SPATIAL_REGSITRATION_IOD)
14
+ if len(failed_item_list) > 0:
15
+ result_dict["result"] = False
16
+ result_dict["content"] = failed_item_list
17
+ return result_dict
18
+
19
+
20
+ def check_s_reg_iod(rs_ds: Dataset) -> dict:
21
+ result_dict = {
22
+ "result": True,
23
+ "content": []
24
+ }
25
+ failed_item_list = []
26
+ failed_item_list = check_iod(ds=rs_ds, config_map=SPATIAL_REGSITRATION_IOD)
27
+ if len(failed_item_list) > 0:
28
+ result_dict["result"] = False
29
+ result_dict["content"] = failed_item_list
30
+ return result_dict
@@ -0,0 +1,40 @@
1
+ from pydicom.uid import UID
2
+
3
+ # Deformable Spatial Registration CIOD
4
+ DEFORMABLE_SPATIAL_REGSITRATION_IOD = {
5
+ "PatientID": {},
6
+ "PatientName": {},
7
+ "PatientBirthDate": {},
8
+ "PatientSex": {},
9
+ "StudyInstanceUID": {},
10
+ "StudyID": {},
11
+ "StudyDate": {},
12
+ "StudyTime": {},
13
+ "AccessionNumber": {},
14
+ "SeriesInstanceUID": {"type": UID},
15
+ "SeriesNumber": {},
16
+ "Modality": {"value": "REG"},
17
+ "Manufacturer": {},
18
+ "FrameOfReferenceUID": {"type": UID},
19
+ "SOPClassUID": {"type": UID},
20
+ "SOPInstanceUID": {"type": UID},
21
+ "InstanceNumber": {},
22
+ "ContentLabel": {},
23
+ "ContentDescription": {},
24
+ "DeformableRegistrationSequence": {
25
+ "SourceFrameOfReferenceUID": {"type": UID},
26
+ "submap": {
27
+ "DeformableRegistrationGridSequence": {
28
+ "ImagePositionPatient": {},
29
+ "ImageOrientationPatient": {},
30
+ "GridDimensions": {},
31
+ "GridResolution": {},
32
+ "VectorGridData": {}
33
+ },
34
+ "PreDeformationMatrixRegistrationSequence": {},
35
+ "PostDeformationMatrixRegistrationSequence": {},
36
+ "RegistrationTypeCodeSequence": {},
37
+ }
38
+ },
39
+
40
+ }
@@ -0,0 +1,29 @@
1
+
2
+ import numpy as np
3
+ from struct import unpack
4
+
5
+ from pydicom.dataset import Dataset
6
+
7
+ def get_deformable_reg_list(reg_ds: Dataset) -> list:
8
+ reg_dict_list = []
9
+ for reg in reg_ds.DeformableRegistrationSequence:
10
+ reg_dict = {}
11
+ reg_dict['SourceFrameOfReferenceUID'] = reg.SourceFrameOfReferenceUID
12
+ reg_dict['PreDeformationMatrixRegistration'] = reg.PreDeformationMatrixRegistrationSequence[-1].FrameOfReferenceTransformationMatrix
13
+ reg_dict['PostDeformationMatrixRegistration'] = reg.PostDeformationMatrixRegistrationSequence[-1].FrameOfReferenceTransformationMatrix
14
+ reg_dict['DeformableRegistrationGrid'] = {}
15
+ deformed_grid_data = reg.DeformableRegistrationGridSequence[-1]
16
+
17
+ reg_dict['DeformableRegistrationGrid']['GridDimensions'] = deformed_grid_data.GridDimensions
18
+ reg_dict['DeformableRegistrationGrid']['GridResolution'] = deformed_grid_data.GridResolution
19
+ reg_dict['DeformableRegistrationGrid']['ImagePositionPatient'] = deformed_grid_data.ImagePositionPatient
20
+
21
+ grid_dim = deformed_grid_data.GridDimensions
22
+ vector_grid_data = deformed_grid_data.VectorGridData
23
+ vector_grid_data = unpack(f"<{len(vector_grid_data) // 4}f", vector_grid_data)
24
+ deformed_array = np.reshape(vector_grid_data, grid_dim[::-1] + [3,])
25
+
26
+ reg_dict['DeformableRegistrationGrid']['VectorGridData'] = deformed_array
27
+ reg_dict_list.append(reg_dict)
28
+
29
+ return reg_dict_list
@@ -0,0 +1,38 @@
1
+ from pydicom.uid import UID
2
+
3
+ # Spatial Registration CIOD
4
+ SPATIAL_REGSITRATION_IOD = {
5
+ "PatientID": {},
6
+ "PatientName": {},
7
+ "PatientBirthDate": {},
8
+ "PatientSex": {},
9
+ "StudyInstanceUID": {},
10
+ "StudyID": {},
11
+ "StudyDate": {},
12
+ "StudyTime": {},
13
+ "AccessionNumber": {},
14
+ "SeriesInstanceUID": {"type": UID},
15
+ "SeriesNumber": {},
16
+ "Modality": {"value": "REG"},
17
+ "Manufacturer": {},
18
+ "FrameOfReferenceUID": {"type": UID},
19
+ "SOPClassUID": {"type": UID},
20
+ "SOPInstanceUID": {"type": UID},
21
+ "InstanceNumber": {},
22
+ "ContentLabel": {},
23
+ "ContentDescription": {},
24
+ "RegistrationSequence": {
25
+ "FrameOfReferenceUID": {"type": UID},
26
+ "submap": {
27
+ "MatrixRegistrationSequence": {
28
+ "submap": {
29
+ "MatrixSequence": {
30
+ "FrameOfReferenceTransformationMatrixType": {},
31
+ "FrameOfReferenceTransformationMatrix": {},
32
+ }
33
+ }
34
+ },
35
+ }
36
+ },
37
+
38
+ }
@@ -0,0 +1,141 @@
1
+ import numpy as np
2
+ import SimpleITK as sitk
3
+
4
+
5
+ def rigid_registration(
6
+ fixed_image: sitk.Image,
7
+ moving_image: sitk.Image,
8
+ histogram_bins: int = 50,
9
+ learning_rate: float = 1.0,
10
+ iterations: int = 100,
11
+ convergence_minimum_value: float = 1e-6,
12
+ convergence_window_size: int = 10,
13
+ ) -> sitk.Transform:
14
+ """
15
+ Perform rigid registration (translation and rotation only) between two 3D SimpleITK images and return an affine transform.
16
+ Args:
17
+ fixed_image (sitk.Image)
18
+ moving_image (sitk.Image)
19
+ histogram_bins (int)
20
+ learning_rate (float)
21
+ iterations (int)
22
+ convergence_minimum_value (float)
23
+ convergence_window_size (int)
24
+ Returns:
25
+ sitk.Transform: The resulting affine transform (containing only rotation + translation)
26
+ """
27
+ fixed_image = sitk.Cast(fixed_image, sitk.sitkFloat32)
28
+ moving_image = sitk.Cast(moving_image, sitk.sitkFloat32)
29
+
30
+ registration_method = sitk.ImageRegistrationMethod()
31
+ registration_method.SetMetricAsMattesMutualInformation(numberOfHistogramBins=histogram_bins)
32
+ registration_method.SetInterpolator(sitk.sitkLinear)
33
+ registration_method.SetOptimizerAsGradientDescent(
34
+ learningRate=learning_rate,
35
+ numberOfIterations=iterations,
36
+ convergenceMinimumValue=convergence_minimum_value,
37
+ convergenceWindowSize=convergence_window_size,
38
+ )
39
+ registration_method.SetOptimizerScalesFromPhysicalShift()
40
+ initial_transform = sitk.CenteredTransformInitializer(
41
+ fixed_image,
42
+ moving_image,
43
+ sitk.VersorRigid3DTransform(),
44
+ sitk.CenteredTransformInitializerFilter.GEOMETRY
45
+ )
46
+ initial_transform.SetCenter((0.0, 0.0, 0.0))
47
+ registration_method.SetInitialTransform(initial_transform, inPlace=False)
48
+ final_transform = registration_method.Execute(fixed_image, moving_image)
49
+ if isinstance(final_transform, sitk.CompositeTransform):
50
+ transform = final_transform.GetNthTransform(0)
51
+ else:
52
+ transform = final_transform
53
+ affine_transform = sitk.AffineTransform(3)
54
+ affine_transform.SetMatrix(transform.GetMatrix())
55
+ affine_transform.SetTranslation(transform.GetTranslation())
56
+ return affine_transform
57
+
58
+
59
+ def deformable_registration(fixed_image, moving_image, mesh_size=8):
60
+ """
61
+ Perform deformable registration and output displacement field (numpy array with spatial information) and SimpleITK transform object.
62
+
63
+ Args:
64
+ fixed_image (sitk.Image): Fixed image (reference)
65
+ moving_image (sitk.Image): Moving image (to be registered)
66
+ mesh_size (int or list): B-spline mesh size (default 8, can be adjusted based on image size)
67
+
68
+ Returns:
69
+ dfield_transform (sitk.DisplacementFieldTransform): SimpleITK displacement transform
70
+ """
71
+ if isinstance(mesh_size, int):
72
+ mesh_size = [mesh_size] * fixed_image.GetDimension()
73
+ tx = sitk.BSplineTransformInitializer(fixed_image, mesh_size)
74
+
75
+ registration_method = sitk.ImageRegistrationMethod()
76
+ registration_method.SetMetricAsMattesMutualInformation(numberOfHistogramBins=100)
77
+ registration_method.SetOptimizerAsGradientDescent(
78
+ learningRate=1.0,
79
+ numberOfIterations=50,
80
+ convergenceMinimumValue=1e-6,
81
+ convergenceWindowSize=10
82
+ )
83
+ registration_method.SetInterpolator(sitk.sitkLinear)
84
+ registration_method.SetInitialTransform(tx, inPlace=False)
85
+ # registration_method.SetShrinkFactorsPerLevel(shrinkFactors=[4, 2, 1]) # 4 2 1
86
+ # registration_method.SetSmoothingSigmasPerLevel(smoothingSigmas=[2, 1, 0]) # 2 1 0
87
+ # registration_method.SmoothingSigmasAreSpecifiedInPhysicalUnitsOn()
88
+
89
+ outTx = registration_method.Execute(fixed_image, moving_image)
90
+
91
+ displacement_field = sitk.TransformToDisplacementField(
92
+ outTx,
93
+ sitk.sitkVectorFloat64,
94
+ fixed_image.GetSize(),
95
+ fixed_image.GetOrigin(),
96
+ fixed_image.GetSpacing(),
97
+ fixed_image.GetDirection()
98
+ )
99
+
100
+ dfield_transform = sitk.DisplacementFieldTransform(displacement_field)
101
+ return dfield_transform
102
+
103
+
104
+ def affine_to_homogeneous_matrix(transform: sitk.AffineTransform) -> np.ndarray:
105
+ """
106
+ Convert a SimpleITK AffineTransform to a 4x4 homogeneous transformation matrix.
107
+
108
+ Args:
109
+ transform (sitk.AffineTransform): The affine transform to convert.
110
+
111
+ Returns:
112
+ np.ndarray: 4x4 homogeneous matrix (dtype float64).
113
+ """
114
+ matrix3x3 = np.array(transform.GetMatrix()).reshape((3, 3))
115
+ translation = np.array(transform.GetTranslation())
116
+ hom_mat = np.eye(4)
117
+ hom_mat[:3, :3] = matrix3x3
118
+ hom_mat[:3, 3] = translation
119
+ return hom_mat
120
+
121
+
122
+ def displacement_field_to_dict(transform: sitk.DisplacementFieldTransform) -> dict:
123
+ """
124
+ Convert a SimpleITK DisplacementFieldTransform to a dictionary.
125
+ Args:
126
+ transform (sitk.DisplacementFieldTransform): The displacement field transform to convert.
127
+ Returns:
128
+ dict: Dictionary containing displacement field information.
129
+ """
130
+ displacement_field = sitk.GetArrayFromImage(transform.GetDisplacementField())
131
+ origin = transform.GetOrigin()
132
+ spacing = transform.GetSpacing()
133
+ direction = transform.GetDirection()
134
+ size = transform.GetSize()
135
+ return {
136
+ "vector_grid": displacement_field,
137
+ "origin": origin,
138
+ "spacing": spacing,
139
+ "direction": direction,
140
+ "size": size,
141
+ }
@@ -0,0 +1,46 @@
1
+ """Radiation Therapy Structure Set module"""
2
+
3
+ from .builder import create_rtstruct_dataset
4
+
5
+ from .add_new_roi import create_roi_into_rs_ds
6
+
7
+ from .make_contour_sequence import (
8
+ add_contour_sequence_from_mask3d,
9
+ add_contour_sequence_from_dcm_ctr_dict
10
+ )
11
+
12
+ from .parser import (
13
+ get_contour_dict,
14
+ get_roi_number_to_name
15
+ )
16
+
17
+ from .checker import (
18
+ is_rtstruct_matching_series,
19
+ check_rs_iod
20
+ )
21
+
22
+ from .rs_to_volume import (
23
+ rtstruct_to_mask_dict,
24
+ calc_image_series_affine_mapping,
25
+ calc_rs_affine_mapping
26
+ )
27
+
28
+
29
+ __all__ = [
30
+ 'create_rtstruct_dataset',
31
+ 'create_roi_into_rs_ds',
32
+
33
+ 'add_contour_sequence_from_mask3d',
34
+ 'add_contour_sequence_from_dcm_ctr_dict',
35
+
36
+ 'get_contour_dict',
37
+ 'get_roi_number_to_name',
38
+
39
+ 'is_rtstruct_matching_series',
40
+ 'check_rs_iod',
41
+
42
+ 'rtstruct_to_mask_dict',
43
+ 'calc_image_series_affine_mapping',
44
+ 'calc_rs_affine_mapping',
45
+
46
+ ]
@@ -0,0 +1,50 @@
1
+ from pydicom.dataset import Dataset
2
+ from pydicom.sequence import Sequence
3
+
4
+
5
+ def create_roi_into_rs_ds(
6
+ rs_ds: Dataset,
7
+ roi_color: list,
8
+ roi_number: int,
9
+ roi_name: str,
10
+ roi_description: str,
11
+ roi_interpreted_type: str = "ORGAN",
12
+ ) -> Dataset:
13
+ frame_of_ref_uid = rs_ds.FrameOfReferenceUID
14
+ rs_ds.StructureSetROISequence.append(create_empty_structure_set_roi(roi_number, roi_name, roi_description, frame_of_ref_uid))
15
+ rs_ds.ROIContourSequence.append(create_empty_roi_contour_sequence(roi_number, roi_color))
16
+ rs_ds.RTROIObservationsSequence.append(create_empty_rtroi_observation(roi_number, roi_interpreted_type))
17
+ return rs_ds
18
+
19
+ def create_empty_structure_set_roi(roi_number,
20
+ roi_name,
21
+ roi_description,
22
+ frame_of_ref_uid,
23
+ roi_generation_algorithm="AUTOMATIC",
24
+ roi_generation_description="KumaGenerated",
25
+ ) -> Dataset:
26
+ structure_set_roi = Dataset()
27
+ structure_set_roi.ROINumber = roi_number
28
+ structure_set_roi.ROIName = roi_name
29
+ structure_set_roi.ROIDescription = roi_description
30
+ structure_set_roi.ROIGenerationAlgorithm = roi_generation_algorithm
31
+ structure_set_roi.ROIGenerationDescription = roi_generation_description
32
+ structure_set_roi.ReferencedFrameOfReferenceUID = frame_of_ref_uid
33
+ return structure_set_roi
34
+
35
+ def create_empty_roi_contour_sequence(roi_number, roi_color) -> Dataset:
36
+ roi_contour_sequence = Dataset()
37
+ roi_contour_sequence.ReferencedROINumber = roi_number # also in create_structure_set_roi
38
+ roi_contour_sequence.ROIDisplayColor = roi_color
39
+ roi_contour_sequence.ContourSequence = Sequence()
40
+ return roi_contour_sequence
41
+
42
+ def create_empty_rtroi_observation(roi_number, roi_interpreted_type='ORGAN') -> Dataset:
43
+ rtroi_observation = Dataset()
44
+ rtroi_observation.ObservationNumber = roi_number
45
+ rtroi_observation.ReferencedROINumber = roi_number
46
+ rtroi_observation.ROIObservationDescription = "Type:Soft,Range:*/*,Fill:0,Opacity:0.0,Thickness:1,LineThickness:2,read-only:false"
47
+ rtroi_observation.private_creators = "higumalu"
48
+ rtroi_observation.RTROIInterpretedType = roi_interpreted_type
49
+ rtroi_observation.ROIInterpreter = ""
50
+ return rtroi_observation