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 3 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
42 changes: 28 additions & 14 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,17 @@
</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>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="line_edit_move_scp_ae_title">
<property name="clearButtonEnabled">
<bool>false</bool>
<string>Patient Photo</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="push_button_export_ups">
<item row="1" column="2">
<widget class="QCheckBox" name="checkbox_setup_photos">
<property name="text">
<string>Export UPS</string>
<string>Setup Photos</string>
</property>
</widget>
</item>
Expand Down
8 changes: 7 additions & 1 deletion tdwii_plus_examples/rtbdi_creator/mainbdiwidget.py
Original file line number Diff line number Diff line change
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
90 changes: 80 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,45 @@ 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)

# This makes the assumption that the Study and Series Instance UIDs for the Setup images are the same as the plan
Copy link
Collaborator

Choose a reason for hiding this comment

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

with different modalities for the RT Plan and the RT Images, this can actually never be right.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Excellent point, re Series Instance UID.
That makes for an interesting issue for referenced objects that only have instance level references... The scheduler needs to retrieve the object using a relational query so it can construct the navigational reference.
I'll back out the setup image part for now and address those and RT Image references in a separate PR.

Thank you for the useful feedback.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You are welcome. But I am not aware that any archive has implemented relational queries. So, how is this supposed to work in general for UPSs when there is only the Instance Information?
For most of the composite IODs there is the Common Instance Reference Module that provides the hierarchical information for the plain instance references in an instance...

Copy link
Owner Author

Choose a reason for hiding this comment

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

Updated to search in the Common Instance Reference Module (available in RT (Ion) Plan), and if the Study and Series UID for those referenced instances are not found, avoid encoding in the Input Information sequence.

# That's not necessarily a safe assumption. It would be better to load the Setup images and pass those in.
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:
setup_image_reference_item = _create_referenced_instances_and_access_item(
setup_image_seq_item,
retrieve_ae_title,
study_instance_uid=plan.StudyInstanceUID,
series_instance_uid=plan.SeriesInstanceUID,
)
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 +393,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 Down
44 changes: 28 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,15 @@ 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.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 +229,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