From 3820989d237d629d08bf9f9094a069ad4422491c Mon Sep 17 00:00:00 2001 From: Rhianna Brown Date: Wed, 24 May 2023 11:11:34 +1000 Subject: [PATCH 1/6] Code for converting MR images --- pydicer/constants.py | 1 + pydicer/convert/data.py | 29 +++++++++++++++++++++++++++++ pydicer/preprocess/data.py | 3 ++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/pydicer/constants.py b/pydicer/constants.py index 27046d8..2bee78e 100644 --- a/pydicer/constants.py +++ b/pydicer/constants.py @@ -3,6 +3,7 @@ RT_PLAN_STORAGE_UID = "1.2.840.10008.5.1.4.1.1.481.5" CT_IMAGE_STORAGE_UID = "1.2.840.10008.5.1.4.1.1.2" PET_IMAGE_STORAGE_UID = "1.2.840.10008.5.1.4.1.1.128" +MR_IMAGE_STORAGE_UID = "1.2.840.10008.5.1.4.1.1.4" PYDICER_DIR_NAME = ".pydicer" CONVERTED_DIR_NAME = "data" diff --git a/pydicer/convert/data.py b/pydicer/convert/data.py index eda57ad..1045964 100644 --- a/pydicer/convert/data.py +++ b/pydicer/convert/data.py @@ -26,6 +26,7 @@ RT_STRUCTURE_STORAGE_UID, CT_IMAGE_STORAGE_UID, PET_IMAGE_STORAGE_UID, + MR_IMAGE_STORAGE_UID ) logger = logging.getLogger(__name__) @@ -360,6 +361,34 @@ def convert(self, patient=None, force=True): entry["path"] = str(output_dir.relative_to(self.working_directory)) self.add_entry(entry) + + elif sop_class_uid == MR_IMAGE_STORAGE_UID: + # TODO Handle inconsistent slice spacing + if INTERPOLATE_MISSING_DATA: + series_files = handle_missing_slice(df_files) + else: + raise ValueError("Slice Locations are not evenly spaced") + + series_files = [str(f) for f in series_files] + series = sitk.ReadImage(series_files) + + output_dir = patient_directory.joinpath("images", sop_instance_hash) + output_dir.mkdir(exist_ok=True, parents=True) + + nifti_file = output_dir.joinpath("MR.nii.gz") + sitk.WriteImage(series, str(nifti_file)) + logger.debug("Writing MR Image Series to: %s", nifti_file) + + json_file = output_dir.joinpath("metadata.json") + convert_dicom_headers( + series_files[0], + str(nifti_file.relative_to(self.output_directory)), + json_file, + ) + + entry["path"] = str(output_dir.relative_to(self.working_directory)) + + self.add_entry(entry) elif sop_class_uid == RT_STRUCTURE_STORAGE_UID: diff --git a/pydicer/preprocess/data.py b/pydicer/preprocess/data.py index ef23b09..b37c5fb 100644 --- a/pydicer/preprocess/data.py +++ b/pydicer/preprocess/data.py @@ -15,6 +15,7 @@ RT_PLAN_STORAGE_UID, RT_STRUCTURE_STORAGE_UID, CT_IMAGE_STORAGE_UID, + MR_IMAGE_STORAGE_UID ) from pydicer.quarantine.treat import copy_file_to_quarantine from pydicer.utils import read_preprocessed_data @@ -169,7 +170,7 @@ def preprocess(self, input_directory, force=True): except AttributeError: logger.warning("Unable to determine Reference Series UID") - elif dicom_type_uid in (CT_IMAGE_STORAGE_UID, PET_IMAGE_STORAGE_UID): + elif dicom_type_uid in (CT_IMAGE_STORAGE_UID, PET_IMAGE_STORAGE_UID, MR_IMAGE_STORAGE_UID): image_position = np.array(ds.ImagePositionPatient, dtype=float) image_orientation = np.array(ds.ImageOrientationPatient, dtype=float) From 73e55027623c916d6073b60299d1f8e30420ea6f Mon Sep 17 00:00:00 2001 From: Rhianna Brown Date: Wed, 6 Sep 2023 16:38:04 +1000 Subject: [PATCH 2/6] Support for MR --- pydicer/convert/data.py | 2 +- pydicer/preprocess/data.py | 7 ++--- pydicer/visualise/data.py | 53 +++++++++++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/pydicer/convert/data.py b/pydicer/convert/data.py index 47f7537..0a2af36 100644 --- a/pydicer/convert/data.py +++ b/pydicer/convert/data.py @@ -397,7 +397,7 @@ def convert(self, patient=None, force=True): elif sop_class_uid == MR_IMAGE_STORAGE_UID: # TODO Handle inconsistent slice spacing - if INTERPOLATE_MISSING_DATA: + if config.get_config("interp_missing_slices"): series_files = handle_missing_slice(df_files) else: raise ValueError("Slice Locations are not evenly spaced") diff --git a/pydicer/preprocess/data.py b/pydicer/preprocess/data.py index 68ec698..b9edf99 100644 --- a/pydicer/preprocess/data.py +++ b/pydicer/preprocess/data.py @@ -112,7 +112,7 @@ def scan_file(self, file): except AttributeError: logger.warning("Unable to determine Reference Series UID") - elif dicom_type_uid in (CT_IMAGE_STORAGE_UID, PET_IMAGE_STORAGE_UID): + elif dicom_type_uid in (CT_IMAGE_STORAGE_UID, PET_IMAGE_STORAGE_UID, MR_IMAGE_STORAGE_UID): image_position = np.array(ds.ImagePositionPatient, dtype=float) image_orientation = np.array(ds.ImageOrientationPatient, dtype=float) @@ -293,8 +293,9 @@ def preprocess(self, input_directory, force=True): raise ValueError( f"Could not determine DICOM type {ds.Modality} {dicom_type_uid}." ) - logger.info("Found %d files to scan", len(files)) - + logger.info("Found %d files to scan", len(files)) + except Exception as E: + print(e) result_list = [] for f in get_iterator(files, unit="files", name="preprocess"): diff --git a/pydicer/visualise/data.py b/pydicer/visualise/data.py index 6ebcbe2..cc3d374 100644 --- a/pydicer/visualise/data.py +++ b/pydicer/visualise/data.py @@ -51,7 +51,7 @@ def visualise(self, dataset_name=CONVERTED_DIR_NAME, patient=None, force=True): join_working_directory=True, ) - visualise_modalities = ["CT", "RTSTRUCT", "RTDOSE"] + visualise_modalities = ["CT", "MR", "RTSTRUCT", "RTDOSE"] df_process = df_process[df_process.modality.isin(visualise_modalities)] for _, row in get_iterator( @@ -110,6 +110,57 @@ def visualise(self, dataset_name=CONVERTED_DIR_NAME, patient=None, force=True): patient_logger.eval_module_process("visualise", row.hashed_uid) logger.debug("Created CT visualisation: %s", vis_filename) + if row.modality == "MR": + img_path = Path(row.path) + vis_filename = img_path.joinpath("MR.png") + + if vis_filename.exists() and not force: + logger.info("Visualisation already exists at %s", vis_filename) + continue + + img = sitk.ReadImage(str(img_path.joinpath(f"{row.modality}.nii.gz"))) + + vis = ImageVisualiser(img) + fig = vis.show() + # load meta data from json file + ds_dict = load_object_metadata(row) + # deal with missing value in study description + if "StudyDescription" not in ds_dict: + ds_dict.StudyDescription = "NaN" + # choose axis one + # (this is the top-right box that is blank) + ax = fig.axes[1] + + # choose a sensible font size + # this will depend on the figure size you set + fs = 9 + + # insert metadata information + ax.text( + x=0.02, + y=0.90, + s=f"Patient ID: {ds_dict.PatientID}\n" + f"Series Instance UID: \n" + f"{ds_dict.SeriesInstanceUID}\n" + f"Study Description: {ds_dict.StudyDescription}\n" + f"Study Date: {ds_dict.StudyDate}", + color="black", + ha="left", + va="top", + size=fs, + wrap=True, + bbox={"boxstyle": "square", "fc": "w", "ec": "r"}, + ) + + fig.savefig( + vis_filename, + dpi=fig.dpi, + ) + plt.close(fig) + + patient_logger.eval_module_process("visualise", row.hashed_uid) + logger.debug("Created MR visualisation: %s", vis_filename) + # Visualise the structures on top of their linked image if row.modality == "RTSTRUCT": From f1da0048384b09979e70be37e0d4f103bcecfe02 Mon Sep 17 00:00:00 2001 From: Phillip Chlap Date: Wed, 13 Dec 2023 10:11:45 +1100 Subject: [PATCH 3/6] Remove redundant code --- pydicer/preprocess/data.py | 89 -------------------------------------- 1 file changed, 89 deletions(-) diff --git a/pydicer/preprocess/data.py b/pydicer/preprocess/data.py index c57c4c4..c7a18fe 100644 --- a/pydicer/preprocess/data.py +++ b/pydicer/preprocess/data.py @@ -218,95 +218,6 @@ def preprocess(self, input_directory, force=True): files = [f for f in files if str(f) not in files_already_scanned] - for file in files: - ds = pydicom.read_file(file, force=True) - - try: - dicom_type_uid = ds.SOPClassUID - - res_dict = { - "patient_id": ds.PatientID, - "study_uid": ds.StudyInstanceUID, - "series_uid": ds.SeriesInstanceUID, - "modality": ds.Modality, - "sop_class_uid": dicom_type_uid, - "sop_instance_uid": ds.SOPInstanceUID, - "file_path": str(file), - } - - if "FrameOfReferenceUID" in ds: - res_dict["for_uid"] = ds.FrameOfReferenceUID - - if dicom_type_uid == RT_STRUCTURE_STORAGE_UID: - try: - referenced_series_uid = ( - ds.ReferencedFrameOfReferenceSequence[0] - .RTReferencedStudySequence[0] - .RTReferencedSeriesSequence[0] - .SeriesInstanceUID - ) - res_dict["referenced_uid"] = referenced_series_uid - except AttributeError: - logger.warning("Unable to determine Reference Series UID") - - try: - # Check other tags for a linked DICOM - # e.g. ds.ReferencedFrameOfReferenceSequence[0].FrameOfReferenceUID - # Potentially, we should check each referenced - referenced_frame_of_reference_uid = ( - ds.ReferencedFrameOfReferenceSequence[0].FrameOfReferenceUID - ) - res_dict[ - "referenced_for_uid" - ] = referenced_frame_of_reference_uid - except AttributeError: - logger.warning( - "Unable to determine Referenced Frame of Reference UID" - ) - - elif dicom_type_uid == RT_PLAN_STORAGE_UID: - try: - referenced_sop_instance_uid = ds.ReferencedStructureSetSequence[ - 0 - ].ReferencedSOPInstanceUID - res_dict["referenced_uid"] = referenced_sop_instance_uid - except AttributeError: - logger.warning("Unable to determine Reference Series UID") - - elif dicom_type_uid == RT_DOSE_STORAGE_UID: - try: - referenced_sop_instance_uid = ds.ReferencedRTPlanSequence[ - 0 - ].ReferencedSOPInstanceUID - res_dict["referenced_uid"] = referenced_sop_instance_uid - except AttributeError: - logger.warning("Unable to determine Reference Series UID") - - elif dicom_type_uid in ( - CT_IMAGE_STORAGE_UID, - PET_IMAGE_STORAGE_UID, - MR_IMAGE_STORAGE_UID, - ): - image_position = np.array(ds.ImagePositionPatient, dtype=float) - image_orientation = np.array( - ds.ImageOrientationPatient, dtype=float - ) - - image_plane_normal = np.cross( - image_orientation[:3], image_orientation[3:] - ) - - slice_location = (image_position * image_plane_normal)[2] - - res_dict["slice_location"] = slice_location - - else: - raise ValueError( - f"Could not determine DICOM type {ds.Modality} {dicom_type_uid}." - ) - logger.info("Found %d files to scan", len(files)) - except Exception as E: - print(e) result_list = [] for f in get_iterator(files, unit="files", name="preprocess"): From d675d409368d4fa0328e462b41708ef296bede4e Mon Sep 17 00:00:00 2001 From: Phillip Chlap Date: Wed, 13 Dec 2023 10:14:00 +1100 Subject: [PATCH 4/6] Fix problem in example --- examples/ObjectGeneration.ipynb | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/examples/ObjectGeneration.ipynb b/examples/ObjectGeneration.ipynb index da5c449..6f3d84b 100644 --- a/examples/ObjectGeneration.ipynb +++ b/examples/ObjectGeneration.ipynb @@ -69,14 +69,11 @@ "df_doses = df[df[\"modality\"] == \"RTDOSE\"]\n", "\n", "for _, dose_row in df_doses.iterrows():\n", - " \n", " if \"EQD2_ab\" in dose_row.hashed_uid:\n", " # This is an already scaled dose\n", " continue\n", - " \n", - " df_linked_plan = df[\n", - " df[\"sop_instance_uid\"] == dose_row.referenced_sop_instance_uid\n", - " ]\n", + "\n", + " df_linked_plan = df[df[\"sop_instance_uid\"] == dose_row.referenced_sop_instance_uid]\n", "\n", " linked_plan = df_linked_plan.iloc[0]\n", " ds_plan = load_object_metadata(linked_plan)\n", @@ -85,30 +82,26 @@ " fractions = int(ds_plan.FractionGroupSequence[0].NumberOfFractionsPlanned)\n", "\n", " print(f\"{dose_row.patient_id} has {fractions} fractions\")\n", - " \n", + "\n", " # Load the dose grid\n", " dose_path = Path(dose_row.path).joinpath(\"RTDOSE.nii.gz\")\n", " dose = sitk.ReadImage(str(dose_path))\n", " dose = sitk.Cast(dose, sitk.sitkFloat64)\n", "\n", " dose_id = f\"{dose_row.hashed_uid}_EQD2_ab{alpha_beta}\"\n", - " \n", + "\n", " if len(df_doses[df_doses.hashed_uid == dose_id]) > 0:\n", " print(f\"Already converted dose for {dose_id}\")\n", " continue\n", - " \n", + "\n", " # Apply the EQD2 correction\n", - " eqd2_dose = dose * (((dose/actual_frac) + alpha_beta) / (2 + alpha_beta))\n", - " \n", + " eqd2_dose = dose * (((dose / fractions) + alpha_beta) / (2 + alpha_beta))\n", + "\n", " # Save off the new dose grid\n", " try:\n", " print(f\"Saving dose grid with ID: {dose_id}\")\n", " pydicer.add_dose_object(\n", - " eqd2_dose,\n", - " dose_id,\n", - " dose_row.patient_id,\n", - " linked_plan,\n", - " dose_row.for_uid\n", + " eqd2_dose, dose_id, dose_row.patient_id, linked_plan, dose_row.for_uid\n", " )\n", " except SystemError:\n", " print(f\"Dose object {dose_id} already exists!\")" From 1a4b5b3adf8fe8b78904bdfeafca430dd479805a Mon Sep 17 00:00:00 2001 From: Phillip Chlap Date: Wed, 13 Dec 2023 10:18:08 +1100 Subject: [PATCH 5/6] Fix linting error --- pydicer/convert/data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydicer/convert/data.py b/pydicer/convert/data.py index ecf3944..4fb507d 100644 --- a/pydicer/convert/data.py +++ b/pydicer/convert/data.py @@ -26,7 +26,7 @@ RT_STRUCTURE_STORAGE_UID, CT_IMAGE_STORAGE_UID, PET_IMAGE_STORAGE_UID, - MR_IMAGE_STORAGE_UID + MR_IMAGE_STORAGE_UID, ) from pydicer.logger import PatientLogger @@ -412,7 +412,7 @@ def convert(self, patient=None, force=True): entry["path"] = str(output_dir.relative_to(self.working_directory)) self.add_entry(entry) - + elif sop_class_uid == MR_IMAGE_STORAGE_UID: # TODO Handle inconsistent slice spacing if config.get_config("interp_missing_slices"): From 61702fa276e6f67d93f746b5b64222f03766af5e Mon Sep 17 00:00:00 2001 From: Phillip Chlap Date: Wed, 13 Dec 2023 10:22:23 +1100 Subject: [PATCH 6/6] Fix linting recommendation --- pydicer/visualise/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydicer/visualise/data.py b/pydicer/visualise/data.py index 2f41dff..28f1ee3 100644 --- a/pydicer/visualise/data.py +++ b/pydicer/visualise/data.py @@ -139,7 +139,7 @@ def visualise(self, dataset_name=CONVERTED_DIR_NAME, patient=None, force=True): patient_logger.eval_module_process("visualise", row.hashed_uid) logger.debug("Created CT visualisation: %s", vis_filename) - if row.modality == "MR" or row.modality == "PT": + if row.modality in ("MR", "PT"): img_path = Path(row.path) vis_filename = img_path.joinpath(f"{row.modality}.png")