def populate_new_fields(apps, schema_editor): Series = apps.get_model("django_dicom", "Series") for series in Series.objects.all(): header = DicomHeader(str(series.image_set.first().dcm)) series.pulse_sequence_name = header.get((0x0019, 0x109C)) series.sequence_name = header.get((0x0018, 0x0024)) series.save()
def test_detect_sequence_with_unknown_modality_returns_none(self): header = Header(TEST_IMAGE_PATH) header.raw.Modality = "UNKNOWN" with warnings.catch_warnings(): warnings.simplefilter("ignore") result = header.detect_sequence() self.assertIsNone(result)
def get_or_create_from_dcm(self, path: Path, autoremove: bool = True) -> Tuple: """ Gets or creates an :class:`~django_dicom.models.image.Image` instance based on the contents of the provided *.dcm* path. Parameters ---------- path : :class:`pathlib.Path` Local *.dcm* file path autoremove : bool, optional Whether to remove the local copy of the *.dcm* file under MEDIA_ROOT if creation fails, by default True Returns ------- Tuple[Image, bool] image, created """ header = DicomHeader(path) uid = header.get("SOPInstanceUID") try: existing = self.get(uid=uid) except ObjectDoesNotExist: new_instance = self.create_from_dcm(path, autoremove=autoremove) return new_instance, True else: return existing, False
def setUpClass(cls): cls.csa_parser = CsaParser() # Read a sample header cls.header = Header(TEST_EP2D_IMAGE_PATH) csa_series_info_tag = SIEMENS_PRIVATE_TAGS["CSASeriesHeaderInfo"] raw_csa_header = cls.header.get(csa_series_info_tag, parsed=False) csa_header = CsaHeader(raw_csa_header) cls.parsed = csa_header.parse()
def scan_subject_to_session(apps, schema_editor): Subject = get_subject_model() Scan = apps.get_model("django_mri", "Scan") Session = apps.get_model("django_mri", "Session") for scan in Scan.objects.all(): subject = Subject.objects.filter( id_number=scan.dicom.patient.uid ).first() first_image = scan.dicom.image_set.first().dcm.path header = DicomHeader(first_image) date = header.get("StudyDate") or header.get("SeriesDate") try: time = header.get("StudyTime") except ValueError: time = datetime.time() else: if not time: time = datetime.time() session_time = datetime.datetime.combine(date, time) if not subject: session = Session.objects.create(time=session_time) scan.session_id = session.id else: session = subject.mri_session_set.filter( time=session_time ).first() if not session: session = Session.objects.create( subject_id=subject.id, time=session_time ) else: existing_scan_number = Scan.objects.filter( session_id=session.id, number=scan.number ) if existing_scan_number: session = Session.objects.create( subject_id=subject.id, time=session_time ) scan.session_id = session.id scan.save()
def __init__(self, raw): """ The Image class should be initialized with either a string or a :class:`~pathlib.Path` instance representing the path of a .dcm file. Another option is to initialize it with a :class:`~pydicom.FileDataset` instance, however, in that case make sure that the `stop_before_pixels` parameter is set to False, otherwise reading pydicom's `pixel_array` will fail. Parameters ---------- raw : str, pathlib.Path, or pydicom.FileDataset A single DICOM image. parser : type, optional An object with a public `parse()` method that may be used to parse data elements, by default Parser. """ self.raw = read_file(raw, read_data=True) self.header = Header(self.raw) self.warnings = [] self._data = self.read_raw_data()
class Image: """ This class represents a single DICOM image (i.e. `.dcm` file) and provides unified access to it's header information and data. """ def __init__(self, raw): """ The Image class should be initialized with either a string or a :class:`~pathlib.Path` instance representing the path of a .dcm file. Another option is to initialize it with a :class:`~pydicom.FileDataset` instance, however, in that case make sure that the `stop_before_pixels` parameter is set to False, otherwise reading pydicom's `pixel_array` will fail. Parameters ---------- raw : str, pathlib.Path, or pydicom.FileDataset A single DICOM image. parser : type, optional An object with a public `parse()` method that may be used to parse data elements, by default Parser. """ self.raw = read_file(raw, read_data=True) self.header = Header(self.raw) self.warnings = [] self._data = self.read_raw_data() def read_raw_data(self) -> np.ndarray: """ Reads the pixel array data as returned by pydicom. Returns ------- np.ndarray Pixel array data """ try: return self.raw.pixel_array except (AttributeError, ValueError) as exception: warning = messages.DATA_READ_FAILURE.format(exception=exception) warnings.warn(warning) if warning not in self.warnings: self.warnings.append(warning) def fix_data(self) -> np.ndarray: """ Applies any required transformation to the data. Returns ------- np.ndarray Pixel array data """ if self.is_mosaic: mosaic = Mosaic(self._data, self.header) return mosaic.fold() return self._data def get_default_relative_path(self) -> Path: patient_uid = self.header.get("PatientID") series_uid = self.header.get("SeriesInstanceUID") name = str(self.header.get("InstanceNumber", 0)) + ".dcm" return Path(patient_uid, series_uid, name) @property def is_mosaic(self) -> bool: """ Checks whether a 3D volume is encoded as a 2D Mosaic. For more information, see the :class:`~dicom_parser.utils.siemens.mosaic.Mosaic` class. Returns ------- bool Whether the image is a mosaic encoded volume """ return "MOSAIC" in self.header.get("ImageType") @property def is_fmri(self) -> bool: """ Returns True for fMRI images according to their header information. Returns ------- bool Whether this image represents fMRI data. """ return self.header.detected_sequence == "fMRI" @property def data(self) -> np.ndarray: """ Returns the pixel data array after having applied any required transformations. Returns ------- np.ndarray Pixel data array. """ return self.fix_data() @property def default_relative_path(self) -> Path: return self.get_default_relative_path()
def import_path( self, path: Path, progressbar: bool = True, report: bool = True, persistent: bool = True, force_patient_uid: bool = False, ) -> QuerySet: """ Iterates the given directory tree and imports any *.dcm* files found within it. Parameters ---------- path : :class:`pathlib.Path` Base path for recursive *.dcm* import progressbar : bool, optional Whether to display a progressbar or not, by default True report : bool, optional Whether to print out a summary report when finished or not, by default True persistent : bool, optional Whether to continue and raise a warning or to raise an exception when failing to read a DICOM file's header force_patient_uid : bool, optional If patient UID for an existing image doesn't match the DB value, change the DB value to match that of the imported image Returns ------- :class:`~django.db.models.query.QuerySet` The created :class:`~django_dicom.models.image.Image` instances """ # Create an iterator iterator = Path(path).rglob("*.dcm") if progressbar: # Create a progressbar wrapped iterator using tqdm iterator = create_progressbar(iterator, unit="image") if report: counter = {"created": 0, "existing": 0} # Keep a list of all the created images' primary keys created_ids = [] # Keep a list of patient UID mismatches to log patient_uid_mismatch = [] for dcm_path in iterator: # Atomic image import # For more information see: # https://docs.djangoproject.com/en/3.0/topics/db/transactions/#controlling-transactions-explicitly with transaction.atomic(): try: image, created = self.get_or_create_from_dcm( dcm_path, autoremove=True) except InvalidDicomError as e: if persistent: IMPORT_LOGGER.warning(e) continue else: raise if report: counter_key = "created" if created else "existing" counter[counter_key] += 1 if created: created_ids.append(image.id) else: if image.patient.uid not in patient_uid_mismatch: # Validate patient UID for existing images header = DicomHeader(dcm_path) patient_uid = header.get("PatientID") if patient_uid != image.patient.uid: # Log patient UID mismatch image_uid = header.get("SOPInstanceUID") message = PATIENT_UID_MISMATCH.format( image_uid=image_uid, db_value=image.patient.uid, patient_uid=patient_uid, ) IMPORT_LOGGER.warning(message) patient_uid_mismatch.append(image.patient.uid) if report: self.report_import_path_results(path, counter) return self.filter(id__in=created_ids)
def test_incorrect_raw_input_raises_type_error(self): bad_inputs = 6, 4.2, ("/some/path", ), ["/another/path"], None for bad_input in bad_inputs: with self.assertRaises(TypeError): Header(bad_input)
def test_instantiation_with_path_instance(self): path = Path(TEST_IMAGE_PATH) header = Header(path) self.assertIsInstance(header.raw, pydicom.FileDataset)
def test_instantiation_with_string_path(self): header = Header(TEST_IMAGE_PATH) self.assertIsInstance(header.raw, pydicom.FileDataset)
def setUpClass(cls): cls.raw = pydicom.dcmread(TEST_IMAGE_PATH, stop_before_pixels=True) cls.header = Header(cls.raw)
def test_get_with_a_missing_csa_header_returns_none(self): header = Header(TEST_GE_LOCALIZER_PATH) result = header.get("CSASeriesHeaderInfo") self.assertIsNone(result)
def test_get_with_a_csa_header_returns_dict(self): header = Header(TEST_IMAGE_PATH) result = header.get("CSASeriesHeaderInfo") self.assertIsInstance(result, dict)