Skip to content

Commit

Permalink
Merge branch 'main' into add_nset_support
Browse files Browse the repository at this point in the history
  • Loading branch information
sjswerdloff authored Jul 15, 2024
2 parents aebc254 + d630e68 commit 04e25c1
Show file tree
Hide file tree
Showing 5 changed files with 423 additions and 268 deletions.
2 changes: 2 additions & 0 deletions rtbdi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ qr_ae_title = "OST"
ae_title = "TMS"
export_staging_directory = "~/BDIFolder"
plan_path = "~/SamplePlanFolder"
# The AE Title for the ups scp that will respond to UPS C-FIND requests
ups_scp_ae_title = "TMS"
60 changes: 35 additions & 25 deletions tdwii_plus_examples/rtbdi_creator/form.ui
Original file line number Diff line number Diff line change
Expand Up @@ -166,30 +166,6 @@
<string>UPS Customization</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="2">
<widget class="QCheckBox" name="checkbox_patient_photo">
<property name="text">
<string>Patient Photo</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_start_datetime">
<property name="text">
<string>Scheduled DateTime</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QCheckBox" name="checkbox_setup_photos">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Setup Photos</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDateTimeEdit" name="datetime_edit_scheduled_datetime">
<property name="sizePolicy">
Expand All @@ -216,7 +192,7 @@
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="2">
<widget class="QPushButton" name="push_button_export_ups">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
Expand All @@ -229,6 +205,40 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_tms_scp_ae_title">
<property name="text">
<string>TMS AE Title</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_start_datetime">
<property name="text">
<string>Scheduled DateTime</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QCheckBox" name="checkbox_patient_photo">
<property name="text">
<string>Patient Photo</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QCheckBox" name="checkbox_setup_photos">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Setup Photos</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="line_edit_tms_scp_ae_title"/>
</item>
</layout>
</widget>
</item>
Expand Down
62 changes: 42 additions & 20 deletions tdwii_plus_examples/rtbdi_creator/mainbdiwidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
write_ups,
)

from tdwii_plus_examples.rtbdi_creator.ncreatescu import NCreateSCU
from tdwii_plus_examples.rtbdi_creator.storescu import StoreSCU


class MainBDIWidget(QWidget):
"""Main UI for Creating an RT BDI based on an RT (Ion) Plan
Expand Down Expand Up @@ -65,9 +68,8 @@ def __init__(self, parent=None):
self.fraction_number = 1
self.retrieve_ae_title = ""
self.scheduled_datetime = datetime.now
self.ae_title = "TMS"
self.ae_title = "TMS" # as an SCU... maybe should use a different AE Title?
config_file = "rtbdi.toml"

# TODO: command line argument specifying a different config file
try:
with open(config_file, "rb") as f:
Expand All @@ -78,11 +80,14 @@ def __init__(self, parent=None):
export_staging_directory = Path(default_dict["export_staging_directory"]).expanduser()
self.ui.lineedit_bdidir_selector.setText(str(export_staging_directory))
if "qr_ae_title" in default_dict:
self.ui.line_edit_move_scp_ae_title.setText(toml_dict["DEFAULT"]["qr_ae_title"])
self.ui.line_edit_move_scp_ae_title.setText(default_dict["qr_ae_title"])
if "ae_title" in default_dict:
self.ae_title = default_dict["ae_title"]
if "plan_path" in default_dict:
self.plan_path = str(Path(default_dict["plan_path"]).expanduser())
if "ups_scp_ae_title" in default_dict:
self.ui.line_edit_tms_scp_ae_title.setText(default_dict["ups_scp_ae_title"])

else:
logging.warning("No [DEFAULT] section in toml config file")

Expand All @@ -109,25 +114,10 @@ def _store_plan_button_clicked(self):
iods = list()
iods.append(plan)
success = store_scu.store(iods=iods)
self._store_outcome_message(success=success)
self._command_outcome_message(success=success, command_name="C-STORE")

return

def _store_outcome_message(self, success: bool, text: str = None):
# apparently a known defect, see:
# https://stackoverflow.com/questions/76869543/why-cant-i-change-the-window-icon-on-a-qmessagebox-with-seticon-in-pyside6
app.setAttribute(Qt.ApplicationAttribute.AA_DontUseNativeDialogs, True)
if not success:
dlg = QMessageBox(self)
dlg.setIcon(QMessageBox.Warning)
dlg.setText("C-STORE failed. Please check log for more information")
else:
dlg = QMessageBox(self)
dlg.setIcon(QMessageBox.Information)
dlg.setText("C-STORE succeeded")
dlg.exec()
app.setAttribute(Qt.ApplicationAttribute.AA_DontUseNativeDialogs, False)

@Slot()
def _bdidir_button_clicked(self):
dialog = QFileDialog(self, "BDI Export Dir")
Expand Down Expand Up @@ -165,7 +155,7 @@ def _bdi_export_button_clicked(self):
store_scu = StoreSCU(self.ae_title, self.ui.line_edit_move_scp_ae_title.text())
iods = [rtbdi]
success = store_scu.store(iods=iods)
self._store_outcome_message(success=success)
self._command_outcome_message(success=success, command_name="C-STORE")

@Slot()
def _export_ups_button_clicked(self):
Expand All @@ -188,6 +178,38 @@ def _export_ups_button_clicked(self):
enable_setup_image_ref=self.ui.checkbox_setup_photos.isChecked(),
)
write_ups(ups, Path(self.ui.lineedit_bdidir_selector.text()))
tms_ae_title = self.ui.line_edit_tms_scp_ae_title.text()
if tms_ae_title is None or str(tms_ae_title.strip()) == 0:
logging.warning("No TMS AE Title specified, will not attempt an N-CREATE")
else:
ncreate_scu = NCreateSCU(self.ae_title, tms_ae_title.strip())
ups_list = list()
ups_list.append(ups)
success = ncreate_scu.create_ups(iods=ups_list)
self._command_outcome_message(success=success, command_name="N-CREATE")

def _command_outcome_message(self, success: bool, command_name: str, text: str = None):
# apparently a known defect, see:
# https://stackoverflow.com/questions/76869543/why-cant-i-change-the-window-icon-on-a-qmessagebox-with-seticon-in-pyside6
app.setAttribute(Qt.ApplicationAttribute.AA_DontUseNativeDialogs, True)
if not success:
dlg = QMessageBox(self)
dlg.setIcon(QMessageBox.Warning)
if text is None:
msg_text = " failed. Please check log for more information"
else:
msg_text = text
dlg.setText(command_name + msg_text)
else:
dlg = QMessageBox(self)
dlg.setIcon(QMessageBox.Information)
if text is None:
msg_text = " succeeded"
else:
msg_text = text
dlg.setText(command_name + msg_text)
dlg.exec()
app.setAttribute(Qt.ApplicationAttribute.AA_DontUseNativeDialogs, False)

def _validate_treatment_records(self, treatment_record_ds_list):
"""Confirms that the treatment records reference the plan
Expand Down
62 changes: 62 additions & 0 deletions tdwii_plus_examples/rtbdi_creator/ncreatescu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# StoreSCU.py
import logging

from pydicom import Dataset

# from pydicom.errors import InvalidDicomError
# from pydicom.uid import UID
from pynetdicom import AE # , Association, UnifiedProcedurePresentationContexts
from pynetdicom.presentation import build_context

from tdwii_plus_examples import tdwii_config

# from typing import Optional, Tuple


# from pynetdicom.sop_class import UnifiedProcedureStepPush, UPSGlobalSubscriptionInstance
# from pynetdicom import ALL_TRANSFER_SYNTAXES, AllStoragePresentationContexts, evt


class NCreateSCU:
def __init__(
self,
sending_ae_title: str,
receiving_ae_title: str,
):
self.calling_ae_title = sending_ae_title
self.receiving_ae_title = receiving_ae_title
tdwii_config.load_ae_config()

def create_ups(self, iods: list[Dataset], receiving_ae_title: str = None) -> bool:
ae = AE(ae_title=self.calling_ae_title)
dest_ae_title = self.receiving_ae_title
if receiving_ae_title is not None:
dest_ae_title = receiving_ae_title
contexts_in_iods = [build_context(x.SOPClassUID) for x in iods]
assoc = ae.associate(
tdwii_config.known_ae_ipaddr[dest_ae_title],
tdwii_config.known_ae_port[dest_ae_title],
contexts=contexts_in_iods,
ae_title=dest_ae_title,
)
success = False
if assoc.is_established:
msg_id = 0
success = True
for iod in iods:
msg_id += 1
response, _ = assoc.send_n_create(
iod, iod.SOPClassUID, iod.SOPInstanceUID, msg_id=msg_id, meta_uid=iod.SOPClassUID
)

if response is None or response.Status != 0x0000:
success = False
error_msg = "No response at all from {dest_ae_title} to N-CREATE-RQ"
if response is not None:
error_msg = f"Failed with status {response.Status} \
when trying to issue n-create for {iod.SOPInstanceUID} \
to {dest_ae_title}"
logging.error(error_msg)
break
assoc.release()
return success
Loading

0 comments on commit 04e25c1

Please sign in to comment.