From 3718dd023076752879ce7110526ac0f6da027cc4 Mon Sep 17 00:00:00 2001 From: Devguru Date: Fri, 27 Feb 2026 22:53:44 +0530 Subject: [PATCH] fix: Correct typos, wrong test URL, and replace GE tags with Siemens-specific tags --- apps/backend/tests/test_report.py | 2 +- package/src/pyaslreport/enums/__init__.py | 2 +- .../{modaliy_enum.py => modality_enum.py} | 0 package/src/pyaslreport/sequences/factory.py | 2 +- .../siemens/asl/siemens_basic_single_pld.py | 17 +++++-- .../sequences/siemens/siemens_base.py | 51 ++++++++----------- 6 files changed, 36 insertions(+), 38 deletions(-) rename package/src/pyaslreport/enums/{modaliy_enum.py => modality_enum.py} (100%) diff --git a/apps/backend/tests/test_report.py b/apps/backend/tests/test_report.py index 2dd19c8f..adf06999 100644 --- a/apps/backend/tests/test_report.py +++ b/apps/backend/tests/test_report.py @@ -23,7 +23,7 @@ def test_get_report_dicom_with_invalid_file(tmp_path): file_path.write_text("not a dicom") with open(file_path, "rb") as f: response = client.post( - "/report/process/dicom", + "/api/report/process/dicom", files={"dcm_files": ("not_a_dicom.txt", f, "text/plain")}, data={"modality": "ASL"} ) diff --git a/package/src/pyaslreport/enums/__init__.py b/package/src/pyaslreport/enums/__init__.py index 7b6db02c..8bda5003 100644 --- a/package/src/pyaslreport/enums/__init__.py +++ b/package/src/pyaslreport/enums/__init__.py @@ -1,3 +1,3 @@ -from pyaslreport.enums.modaliy_enum import ModalityTypeValues +from pyaslreport.enums.modality_enum import ModalityTypeValues __all__ = ["ModalityTypeValues"] diff --git a/package/src/pyaslreport/enums/modaliy_enum.py b/package/src/pyaslreport/enums/modality_enum.py similarity index 100% rename from package/src/pyaslreport/enums/modaliy_enum.py rename to package/src/pyaslreport/enums/modality_enum.py diff --git a/package/src/pyaslreport/sequences/factory.py b/package/src/pyaslreport/sequences/factory.py index 173185e3..1d1d1c9d 100644 --- a/package/src/pyaslreport/sequences/factory.py +++ b/package/src/pyaslreport/sequences/factory.py @@ -1,4 +1,4 @@ -from pyaslreport.enums.modaliy_enum import ModalityTypeValues +from pyaslreport.enums.modality_enum import ModalityTypeValues from pyaslreport.sequences.ge.asl import GEBasicSinglePLD, GEMultiPLD from pyaslreport.sequences.ge.dsc import GEDSCSequence from pyaslreport.sequences.siemens.asl import SiemensBasicSinglePLD diff --git a/package/src/pyaslreport/sequences/siemens/asl/siemens_basic_single_pld.py b/package/src/pyaslreport/sequences/siemens/asl/siemens_basic_single_pld.py index 7cd9a570..2cf7f9ad 100644 --- a/package/src/pyaslreport/sequences/siemens/asl/siemens_basic_single_pld.py +++ b/package/src/pyaslreport/sequences/siemens/asl/siemens_basic_single_pld.py @@ -1,5 +1,14 @@ from pyaslreport.sequences.siemens.siemens_base import SiemensBaseSequence from pyaslreport.utils import dicom_tags_utils as dcm_tags +from pydicom.tag import Tag + +# Siemens uses standard DICOM InversionTime tag for PostLabelingDelay +SIEMENS_INVERSION_TIME = Tag(0x0018, 0x0082) + +# TODO: LabelingDuration for Siemens should be extracted from the Phoenix protocol +# (private tag 0x0029,0x1020). For now, use the same tag address as GE_LABEL_DURATION +# since some Siemens sequences store it there. +SIEMENS_LABEL_DURATION = Tag(0x0043, 0x10A5) class SiemensBasicSinglePLD(SiemensBaseSequence): @classmethod @@ -15,8 +24,8 @@ def extract_bids_metadata(self): bids = self._extract_common_metadata() bids.update(self._extract_siemens_common_metadata()) d = self.dicom_header - if dcm_tags.GE_LABEL_DURATION in d: - bids["LabelingDuration"] = d.get(dcm_tags.GE_LABEL_DURATION, None).value - if dcm_tags.GE_INVERSION_TIME in d: - bids["PostLabelingDelay"] = d.get(dcm_tags.GE_INVERSION_TIME, None).value + if SIEMENS_LABEL_DURATION in d: + bids["LabelingDuration"] = d.get(SIEMENS_LABEL_DURATION, None).value + if SIEMENS_INVERSION_TIME in d: + bids["PostLabelingDelay"] = d.get(SIEMENS_INVERSION_TIME, None).value return bids \ No newline at end of file diff --git a/package/src/pyaslreport/sequences/siemens/siemens_base.py b/package/src/pyaslreport/sequences/siemens/siemens_base.py index 352dfa20..f81f2686 100644 --- a/package/src/pyaslreport/sequences/siemens/siemens_base.py +++ b/package/src/pyaslreport/sequences/siemens/siemens_base.py @@ -14,46 +14,35 @@ def is_siemens_manufacturer(cls, dicom_header): bool: True if manufacturer contains Siemens """ manufacturer = dicom_header.get(dcm_tags.MANUFACTURER, "").value.strip().upper() - return "SIEMENS" in manufacturer or "SIEMENS HEALTHCARE" in manufacturer or "SIEMENS HEALHINEERS" in manufacturer + return "SIEMENS" in manufacturer or "SIEMENS HEALTHCARE" in manufacturer or "SIEMENS HEALTHINEERS" in manufacturer def _extract_siemens_common_metadata(self) -> dict: d = self.dicom_header bids = {} - # Direct GE-specific mappings - if dcm_tags.GE_ASSET_R_FACTOR in d: - bids["AssetRFactor"] = d.get(dcm_tags.GE_ASSET_R_FACTOR, None).value - if dcm_tags.GE_EFFECTIVE_ECHO_SPACING in d: - bids["EffectiveEchoSpacing"] = d.get(dcm_tags.GE_EFFECTIVE_ECHO_SPACING, None).value - if dcm_tags.GE_ACQUISITION_MATRIX in d: - bids["AcquisitionMatrix"] = d.get(dcm_tags.GE_ACQUISITION_MATRIX, None).value - if dcm_tags.GE_NUMBER_OF_EXCITATIONS in d: - bids["TotalAcquiredPairs"] = d.get(dcm_tags.GE_NUMBER_OF_EXCITATIONS, None).value - - # Derived fields - # EffectiveEchoSpacing = EffectiveEchoSpacing * AssetRFactor * 1e-6 - if dcm_tags.GE_EFFECTIVE_ECHO_SPACING in d and dcm_tags.GE_ASSET_R_FACTOR in d: - try: - eff_echo = float(d.get(dcm_tags.GE_EFFECTIVE_ECHO_SPACING, None).value) - asset = float(d.get(dcm_tags.GE_ASSET_R_FACTOR, None).value) - bids["EffectiveEchoSpacing"] = eff_echo * asset * 1e-6 - except Exception: - pass + # Siemens-specific metadata extraction + if dcm_tags.SIEMENS_BANDWIDTH_PER_PIXEL_PHASE_ENCODING in d: + bids["BandwidthPerPixelPhaseEncode"] = d.get(dcm_tags.SIEMENS_BANDWIDTH_PER_PIXEL_PHASE_ENCODING, None).value - # TotalReadoutTime = (AcquisitionMatrix[0] - 1) * EffectiveEchoSpacing - if ( - dcm_tags.GE_ACQUISITION_MATRIX in d and - isinstance(d.get(dcm_tags.GE_ACQUISITION_MATRIX, None).value, (list, tuple)) and - len(d.get(dcm_tags.GE_ACQUISITION_MATRIX, None).value) > 0 and - dcm_tags.GE_EFFECTIVE_ECHO_SPACING in bids - ): + if dcm_tags.SIEMENS_ROWS in d and dcm_tags.SIEMENS_COLUMNS in d: + rows = d.get(dcm_tags.SIEMENS_ROWS, None).value + cols = d.get(dcm_tags.SIEMENS_COLUMNS, None).value + bids["AcquisitionMatrix"] = [rows, cols] + + if dcm_tags.SIEMENS_INPLANE_PHASE_ENCODING_DIRECTION in d: + bids["InPlanePhaseEncodingDirection"] = d.get(dcm_tags.SIEMENS_INPLANE_PHASE_ENCODING_DIRECTION, None).value + + # Derive EffectiveEchoSpacing and TotalReadoutTime from BandwidthPerPixelPhaseEncode + if dcm_tags.SIEMENS_BANDWIDTH_PER_PIXEL_PHASE_ENCODING in d and dcm_tags.SIEMENS_ROWS in d: try: - acq_matrix = d.get(dcm_tags.GE_ACQUISITION_MATRIX, None).value[0] - eff_echo = bids["EffectiveEchoSpacing"] - bids["TotalReadoutTime"] = (acq_matrix - 1) * eff_echo + bw = float(d.get(dcm_tags.SIEMENS_BANDWIDTH_PER_PIXEL_PHASE_ENCODING, None).value) + rows = int(d.get(dcm_tags.SIEMENS_ROWS, None).value) + if bw > 0: + bids["EffectiveEchoSpacing"] = 1.0 / (bw * rows) + bids["TotalReadoutTime"] = (rows - 1) * bids["EffectiveEchoSpacing"] except Exception: pass - + # MRAcquisitionType default is 3D if not present if dcm_tags.MR_ACQUISITION_TYPE in d: bids["MRAcquisitionType"] = d.get(dcm_tags.MR_ACQUISITION_TYPE, None).value