Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add image refs to ups #49

Merged
merged 4 commits into from
Jun 13, 2024
Merged
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
43 changes: 30 additions & 13 deletions tdwii_plus_examples/rtbdi_creator/form.ui
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,35 @@
<rect>
<x>40</x>
<y>430</y>
<width>359</width>
<width>491</width>
<height>151</height>
</rect>
</property>
<property name="title">
<string>UPS Customization</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="1">
<widget class="QPushButton" name="push_button_export_ups">
<property name="text">
<string>Export UPS</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="line_edit_move_scp_ae_title">
<property name="clearButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_move_ae_title">
<property name="text">
<string>Move/Retrieve AE Title</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_start_datetime">
<property name="text">
Expand Down Expand Up @@ -185,24 +206,20 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_move_ae_title">
<item row="0" column="2">
<widget class="QCheckBox" name="checkbox_patient_photo">
<property name="text">
<string>Move/Retrieve AE Title</string>
<string>Patient Photo</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="line_edit_move_scp_ae_title">
<property name="clearButtonEnabled">
<bool>false</bool>
<item row="1" column="2">
<widget class="QCheckBox" name="checkbox_setup_photos">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="push_button_export_ups">
<property name="text">
<string>Export UPS</string>
<string>Setup Photos</string>
</property>
</widget>
</item>
Expand Down
10 changes: 8 additions & 2 deletions tdwii_plus_examples/rtbdi_creator/mainbdiwidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def _bdidir_button_clicked(self):
dialog.setFileMode(QFileDialog.Directory)
dialog.setOption(QFileDialog.ShowDirsOnly, True)
dialog.setLabelText(QFileDialog.Accept, "Select")
if dialog.exec_() == QFileDialog.Accepted:
if dialog.exec() == QFileDialog.Accepted:
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addresses a deprecation warning generated by PySide6

file_name = dialog.selectedFiles()[0]
if file_name:
path = Path(file_name)
Expand Down Expand Up @@ -113,7 +113,13 @@ def _export_ups_button_clicked(self):
self._bdi_export_button_clicked()

ups = create_ups_from_plan_and_bdi(
self.plan, self.rtbdi, self.retrieve_ae_title, self.scheduled_datetime, treatment_record_ds_list
self.plan,
self.rtbdi,
self.retrieve_ae_title,
self.scheduled_datetime,
treatment_record_ds_list,
enable_photo_ref=self.ui.checkbox_patient_photo.isChecked(),
enable_setup_image_ref=self.ui.checkbox_setup_photos.isChecked(),
)
write_ups(ups, Path(self.ui.lineedit_bdidir_selector.text()))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 11.0.3, 2024-04-26T15:44:28. -->
<!-- Written by QtCreator 11.0.3, 2024-06-09T13:51:04. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
Expand Down
138 changes: 128 additions & 10 deletions tdwii_plus_examples/rtbdi_creator/rtbdi_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,17 +225,31 @@ def _delete_excess_plan_elements_from_ups(ups_ds: Dataset):


def create_ups_from_plan_and_bdi(
plan: Dataset, bdi: Dataset, retrieve_ae_title: str, scheduled_datetime: datetime, treatment_records: List[Dataset]
plan: Dataset,
bdi: Dataset,
retrieve_ae_title: str,
scheduled_datetime: datetime,
treatment_records: List[Dataset],
enable_photo_ref=True,
enable_setup_image_ref=True,
) -> Dataset:
"""Build up the UPS

Args:
plan (Dataset): _description_
bdi (Dataset): _description_
retrieve_ae_title (str): _description_
scheduled_datetime (datetime): _description_
plan (Dataset): The RT Plan or RT Ion Plan that the UPS is based on
bdi (Dataset): The RT Beams Delivery Instruction that the UPS is based on
retrieve_ae_title (str): The AE Title of the OST (from which references will be retrieved)
scheduled_datetime (datetime): The datetime to schedule the UPS for
treatment_records (List): list of datasets representing the treatment records previously delivered for the fraction
enable_photo_ref (bool): whether to include a reference to a patient photo in the InputInformation Sequence
if that is present in the plan
enable_setup_image_ref: whether to include references to setup images (photos) in the InputInformationSequence
if they are present in the plan

"""
ups_ds = pydicom.Dataset()
# enable_photo_ref = True;
# enable_setup_image_ref = True;
for elem in plan:
if elem.tag < 0x0010FFFF:
ups_ds[elem.tag] = deepcopy(elem)
Expand All @@ -254,13 +268,50 @@ def create_ups_from_plan_and_bdi(
ups_ds.ScheduledWorkitemCodeSequence = scheduled_work_item_code_sequence
plan_reference_item = _create_referenced_instances_and_access_item(plan, retrieve_ae_title)
bdi_reference_item = _create_referenced_instances_and_access_item(bdi, retrieve_ae_title)

# because the Reference Patient Photo Sequence already contains Study and Series UID information... it isn't
# necessary to load the patient photo to get that information.
patient_photo_reference_items = []
if enable_photo_ref and "ReferencedPatientPhotoSequence" in plan:
for patient_photo_seq_item in plan.ReferencedPatientPhotoSequence:
patient_photo_reference_item = _create_referenced_instances_and_access_item(
patient_photo_seq_item, retrieve_ae_title
)
patient_photo_reference_items.append(patient_photo_reference_item)

# Search for the Study and Series Instance UIDs for the Setup images in the Common Instance Reference module
# If they are available, build up the reference for later inclusion in the InputInformationSequence
# If not... don't attempt to include them.
patient_setup_image_items = []
if enable_setup_image_ref and "ReferencedSetupImageSequence" in plan.PatientSetupSequence[0]:
for setup_image_seq_item in plan.PatientSetupSequence[0].ReferencedSetupImageSequence:
ref_study_uid, ref_series_uid = _get_study_and_series_for_referenced_instance(
plan, setup_image_seq_item.ReferencedSOPInstanceUID
)
if ref_study_uid is not None and ref_series_uid is not None:
setup_image_reference_item = _create_referenced_instances_and_access_item(
setup_image_seq_item,
retrieve_ae_title,
study_instance_uid=ref_study_uid,
series_instance_uid=ref_series_uid,
)
patient_setup_image_items.append(setup_image_reference_item)

treatment_record_reference_items = []
for treatment_rec in treatment_records:
treatment_rec_ref_item = _create_referenced_instances_and_access_item(treatment_rec, retrieve_ae_title)
treatment_record_reference_items.append(treatment_rec_ref_item)

list_of_reference_items = []
list_of_reference_items.append(plan_reference_item)
list_of_reference_items.append(bdi_reference_item)

if len(patient_photo_reference_items) > 0:
list_of_reference_items += patient_photo_reference_items

if len(patient_setup_image_items) > 0:
list_of_reference_items += patient_setup_image_items

treatment_delivery_type = "TREATMENT"
if len(treatment_record_reference_items) > 0:
list_of_reference_items += treatment_record_reference_items
Expand Down Expand Up @@ -347,14 +398,38 @@ def _datetime_to_dicom_time(dt: datetime) -> str:
return dicom_time


def _create_referenced_instances_and_access_item(input_ds: Dataset, retrieve_ae_title: str) -> Dataset:
def _create_referenced_instances_and_access_item(
input_ds: Dataset, retrieve_ae_title: str, study_instance_uid: str = "", series_instance_uid: str = ""
) -> Dataset:
ref_instance_seq_item = pydicom.Dataset()
ref_instance_seq_item.TypeOfInstances = "DICOM"
ref_instance_seq_item.StudyInstanceUID = input_ds.StudyInstanceUID
ref_instance_seq_item.SeriesInstanceUID = input_ds.SeriesInstanceUID
if "StudyInstanceUID" in input_ds:
ref_instance_seq_item.StudyInstanceUID = input_ds.StudyInstanceUID
elif len(study_instance_uid) > 0:
ref_instance_seq_item.StudyInstanceUID = study_instance_uid
else:
raise KeyError("Missing Study Instance UID")

if "SeriesInstanceUID" in input_ds:
ref_instance_seq_item.SeriesInstanceUID = input_ds.SeriesInstanceUID
elif len(series_instance_uid) > 0:
ref_instance_seq_item.SeriesInstanceUID = series_instance_uid
else:
raise KeyError("Missing Series Instance UID")

ref_sop_seq_item = pydicom.Dataset()
ref_sop_seq_item.ReferencedSOPClassUID = input_ds.SOPClassUID
ref_sop_seq_item.ReferencedSOPInstanceUID = input_ds.SOPInstanceUID
if "SOPClassUID" in input_ds:
ref_sop_seq_item.ReferencedSOPClassUID = input_ds.SOPClassUID
ref_sop_seq_item.ReferencedSOPInstanceUID = input_ds.SOPInstanceUID
elif "ReferencedSOPSequence" in input_ds: # a referenced Photo sequence was passed in, e.g. for Patient Photo
ref_sop_seq_item.ReferencedSOPClassUID = input_ds.ReferencedSOPSequence[0].ReferencedSOPClassUID
ref_sop_seq_item.ReferencedSOPInstanceUID = input_ds.ReferencedSOPSequence[0].ReferencedSOPInstanceUID
elif "ReferencedSOPClassUID" in input_ds: # a bare referenced SOP Sequence was passed in, e.g. for Setup images
ref_sop_seq_item.ReferencedSOPClassUID = input_ds.ReferencedSOPClassUID
ref_sop_seq_item.ReferencedSOPInstanceUID = input_ds.ReferencedSOPInstanceUID
else:
raise KeyError("Neither SOPClassUID, ReferencedSOPClassUID, nor ReferencedSOPSequence are present in input dataset")

ref_instance_seq_item.ReferencedSOPSequence = pydicom.Sequence([ref_sop_seq_item])
dicom_retrieval_seq_item = pydicom.Dataset()
dicom_retrieval_seq_item.RetrieveAETitle = retrieve_ae_title
Expand All @@ -381,6 +456,49 @@ def _create_scheduled_station_name_code_sequence_item(plan: Dataset) -> Dataset:
return code_seq_item


def _get_study_and_series_for_referenced_instance(plan: Dataset, instance_uid: str) -> tuple[str, str]:
"""Searches Common Instance Reference Module for matching instance_uid

Args:
plan (Dataset): The plan containing the instance reference, e.g. setup images/photos, DRRs
instance_uid (str): The string representation of the SOP Instance UID whose Study and Series UID are desired

Returns:
tuple[str, str]: Study UID and Series UID as a tuple, with individual entries set to None if not found
"""
study_series_tuple = (None, None)
series_uid = None
study_uid = None
if "ReferencedSeriesSequence" in plan:
for series_seq_item in plan.ReferencedSeriesSequence:
for ref_instance_seq_item in series_seq_item:
if str(ref_instance_seq_item.ReferencedSOPInstanceUID) == instance_uid:
series_uid = str(series_seq_item.SeriesInstanceUID)
break
#
#
#
if series_uid is not None:
study_uid = plan.StudyInstanceUID
else:
# it's not in the same study as the plan.
if "StudiesContainingOtherReferencedInstancesSequence" in plan:
for study_seq_item in plan.StudiesContainingOtherReferencedInstancesSequence:
for series_seq_item in plan.ReferencedSeriesSequence:
for ref_instance_seq_item in series_seq_item:
if str(ref_instance_seq_item.ReferencedSOPInstanceUID) == instance_uid:
series_uid = str(series_seq_item.SeriesInstanceUID)
study_uid = str(study_seq_item.StudyInstanceUID)
break
#
#
#
#
study_series_tuple = (study_uid, series_uid)

return study_series_tuple


def main(args):
"""test harness for rtbdi factory

Expand Down
45 changes: 29 additions & 16 deletions tdwii_plus_examples/rtbdi_creator/ui_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'form.ui'
##
## Created by: Qt User Interface Compiler version 6.6.0
## Created by: Qt User Interface Compiler version 6.6.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
Expand Down Expand Up @@ -145,9 +145,25 @@ def setupUi(self, MainBDIWidget):

self.group_box_ups = QGroupBox(MainBDIWidget)
self.group_box_ups.setObjectName("group_box_ups")
self.group_box_ups.setGeometry(QRect(40, 430, 359, 151))
self.group_box_ups.setGeometry(QRect(40, 430, 491, 151))
self.gridLayout = QGridLayout(self.group_box_ups)
self.gridLayout.setObjectName("gridLayout")
self.push_button_export_ups = QPushButton(self.group_box_ups)
self.push_button_export_ups.setObjectName("push_button_export_ups")

self.gridLayout.addWidget(self.push_button_export_ups, 2, 1, 1, 1)

self.line_edit_move_scp_ae_title = QLineEdit(self.group_box_ups)
self.line_edit_move_scp_ae_title.setObjectName("line_edit_move_scp_ae_title")
self.line_edit_move_scp_ae_title.setClearButtonEnabled(False)

self.gridLayout.addWidget(self.line_edit_move_scp_ae_title, 0, 1, 1, 1)

self.label_move_ae_title = QLabel(self.group_box_ups)
self.label_move_ae_title.setObjectName("label_move_ae_title")

self.gridLayout.addWidget(self.label_move_ae_title, 0, 0, 1, 1)

self.label_start_datetime = QLabel(self.group_box_ups)
self.label_start_datetime.setObjectName("label_start_datetime")

Expand All @@ -160,21 +176,16 @@ def setupUi(self, MainBDIWidget):

self.gridLayout.addWidget(self.datetime_edit_scheduled_datetime, 1, 1, 1, 1)

self.label_move_ae_title = QLabel(self.group_box_ups)
self.label_move_ae_title.setObjectName("label_move_ae_title")

self.gridLayout.addWidget(self.label_move_ae_title, 0, 0, 1, 1)

self.line_edit_move_scp_ae_title = QLineEdit(self.group_box_ups)
self.line_edit_move_scp_ae_title.setObjectName("line_edit_move_scp_ae_title")
self.line_edit_move_scp_ae_title.setClearButtonEnabled(False)
self.checkbox_patient_photo = QCheckBox(self.group_box_ups)
self.checkbox_patient_photo.setObjectName("checkbox_patient_photo")

self.gridLayout.addWidget(self.line_edit_move_scp_ae_title, 0, 1, 1, 1)
self.gridLayout.addWidget(self.checkbox_patient_photo, 0, 2, 1, 1)

self.push_button_export_ups = QPushButton(self.group_box_ups)
self.push_button_export_ups.setObjectName("push_button_export_ups")
self.checkbox_setup_photos = QCheckBox(self.group_box_ups)
self.checkbox_setup_photos.setObjectName("checkbox_setup_photos")
self.checkbox_setup_photos.setEnabled(True)

self.gridLayout.addWidget(self.push_button_export_ups, 2, 1, 1, 1)
self.gridLayout.addWidget(self.checkbox_setup_photos, 1, 2, 1, 1)

self.retranslateUi(MainBDIWidget)

Expand Down Expand Up @@ -219,11 +230,13 @@ def retranslateUi(self, MainBDIWidget):
self.checkbox_custom_bdi_filename.setText(QCoreApplication.translate("MainBDIWidget", "Custom BDI filename", None))
self.push_button_export_bdi.setText(QCoreApplication.translate("MainBDIWidget", "Export BDI", None))
self.group_box_ups.setTitle(QCoreApplication.translate("MainBDIWidget", "UPS Customization", None))
self.push_button_export_ups.setText(QCoreApplication.translate("MainBDIWidget", "Export UPS", None))
self.label_move_ae_title.setText(QCoreApplication.translate("MainBDIWidget", "Move/Retrieve AE Title", None))
self.label_start_datetime.setText(QCoreApplication.translate("MainBDIWidget", "Scheduled DateTime", None))
self.datetime_edit_scheduled_datetime.setDisplayFormat(
QCoreApplication.translate("MainBDIWidget", "dd/MM/yyyy h:mm AP", None)
)
self.label_move_ae_title.setText(QCoreApplication.translate("MainBDIWidget", "Move/Retrieve AE Title", None))
self.push_button_export_ups.setText(QCoreApplication.translate("MainBDIWidget", "Export UPS", None))
self.checkbox_patient_photo.setText(QCoreApplication.translate("MainBDIWidget", "Patient Photo", None))
self.checkbox_setup_photos.setText(QCoreApplication.translate("MainBDIWidget", "Setup Photos", None))

# retranslateUi
Loading