diff --git a/fileformats/medimage/__init__.py b/fileformats/medimage/__init__.py index 5c9f7c9..7873030 100644 --- a/fileformats/medimage/__init__.py +++ b/fileformats/medimage/__init__.py @@ -30,9 +30,15 @@ # SiemensDicomDir, ) from .raw import ( # noqa: F401 - ListMode, Kspace, - TwixVb, - # CustomKspace, Rda, + PetListMode, + PetSinogram, + PetCountRate, + PetNormalisation, + Vnd_Siemens_Biograph128Vision_Vr20b_PetRawData, + Vnd_Siemens_Biograph128Vision_Vr20b_PetListMode, + Vnd_Siemens_Biograph128Vision_Vr20b_PetSinogram, + Vnd_Siemens_Biograph128Vision_Vr20b_PetCountRate, + Vnd_Siemens_Biograph128Vision_Vr20b_PetNormalisation, ) diff --git a/fileformats/medimage/dicom.py b/fileformats/medimage/dicom.py index 7231f3b..e57bd03 100644 --- a/fileformats/medimage/dicom.py +++ b/fileformats/medimage/dicom.py @@ -39,7 +39,6 @@ class DicomDir(DicomCollection, DirectoryContaining[Dicom]): class DicomSeries(DicomCollection, SetOf[Dicom]): - @classmethod def from_paths( cls, fspaths: ty.Iterable[Path], common_ok: bool = False @@ -47,7 +46,7 @@ def from_paths( dicoms, remaining = Dicom.from_paths(fspaths, common_ok=common_ok) series_dict = defaultdict(list) for dicom in dicoms: - series_dict[dicom["SeriesNumber"]].append(dicom) + series_dict[(str(dicom["StudyInstanceUID"]), str(dicom["SeriesNumber"]))].append(dicom) return set([cls(s) for s in series_dict.values()]), remaining @@ -55,12 +54,16 @@ def from_paths( def dicom_collection_read_metadata(collection: DicomCollection) -> ty.Dict[str, ty.Any]: # Collated DICOM headers across series collated = copy(collection.contents[0].metadata) - if len(collection.contents) > 1: - for key, val in collection.contents[1].metadata.items(): - if val != collated[key]: # Turn field into list - collated[key] = [collated[key], val] - for dicom in collection.contents[2:]: + for i, dicom in enumerate(collection.contents[1:], start=1): for key, val in dicom.metadata.items(): if val != collated[key]: + # Check whether the value is the same as the values in the previous + # images in the series + if ( + not isinstance(collated[key], list) + or isinstance(val, list) + and not isinstance(collated[key][0], list) + ): + collated[key] = [collated[key]] * i + [val] collated[key].append(val) return collated diff --git a/fileformats/medimage/raw.py b/fileformats/medimage/raw.py deleted file mode 100644 index 32d3e38..0000000 --- a/fileformats/medimage/raw.py +++ /dev/null @@ -1,62 +0,0 @@ -from fileformats.generic import File - - -class ListMode(File): - - ext = ".bf" - binary = True - - -class Kspace(File): - - binary = True - iana_mime = None - - -class TwixVb(Kspace): - """The format that k-space data is saved in from Siemens scanners - with system version vB to (at least) vE""" - - ext = ".dat" - - -# class CustomKspace(Kspace): -# """A custom format for saving k-space data in binary amd JSON files. - -# Binary files -# ------------ -# primary : 5-d matrix -# Data from "data" scan organised in the following dimension order: -# channel, freq-encode, phase-encode, partition-encode (slice), echoes -# reference : 5-d matrix -# Data from calibration scan organised in the same dimension order as -# primary scan - -# JSON side-car -# ------------- -# dims : 3-tuple(int) -# The dimensions of the image in freq, phase, partition (slice) order -# voxel_size : 3-tuple(float) -# Size of the voxels in same order as dims -# num_channels : int -# Number of channels in the k-space -# num_echos : int -# Number of echoes in the acquisition -# T E : tuple(float) -# The echo times -# B0_strength : float -# Strength of the B0 field -# B0_dir : 3-tuple(float) -# Direction of the B0 field -# larmor_freq : float -# The central larmor row_frequency of the scanner""" - -# ext = ".ks" -# side_cars = ("ref", "json") - - -class Rda(File): - """MRS format""" - - ext = ".rda" - binary = True diff --git a/fileformats/medimage/raw/__init__.py b/fileformats/medimage/raw/__init__.py new file mode 100644 index 0000000..ac2e3b4 --- /dev/null +++ b/fileformats/medimage/raw/__init__.py @@ -0,0 +1,16 @@ +from .mri import ( # noqa: F401 + Kspace, + Rda, +) +from .pet import ( + PetRawData, + PetListMode, + PetSinogram, + PetCountRate, + PetNormalisation, + Vnd_Siemens_Biograph128Vision_Vr20b_PetRawData, + Vnd_Siemens_Biograph128Vision_Vr20b_PetListMode, + Vnd_Siemens_Biograph128Vision_Vr20b_PetSinogram, + Vnd_Siemens_Biograph128Vision_Vr20b_PetCountRate, + Vnd_Siemens_Biograph128Vision_Vr20b_PetNormalisation, +) diff --git a/fileformats/medimage/raw/mri/__init__.py b/fileformats/medimage/raw/mri/__init__.py new file mode 100644 index 0000000..dd71cba --- /dev/null +++ b/fileformats/medimage/raw/mri/__init__.py @@ -0,0 +1,14 @@ +from fileformats.generic import File + + +class Kspace(File): + + binary = True + iana_mime = None + + +class Rda(File): + """MRS format""" + + ext = ".rda" + binary = True diff --git a/fileformats/medimage/raw/pet/__init__.py b/fileformats/medimage/raw/pet/__init__.py new file mode 100644 index 0000000..14b8f70 --- /dev/null +++ b/fileformats/medimage/raw/pet/__init__.py @@ -0,0 +1,14 @@ +from .base import ( + PetRawData, + PetListMode, + PetSinogram, + PetCountRate, + PetNormalisation, +) +from .siemens import ( + Vnd_Siemens_Biograph128Vision_Vr20b_PetRawData, + Vnd_Siemens_Biograph128Vision_Vr20b_PetListMode, + Vnd_Siemens_Biograph128Vision_Vr20b_PetSinogram, + Vnd_Siemens_Biograph128Vision_Vr20b_PetCountRate, + Vnd_Siemens_Biograph128Vision_Vr20b_PetNormalisation, +) diff --git a/fileformats/medimage/raw/pet/base.py b/fileformats/medimage/raw/pet/base.py new file mode 100644 index 0000000..024248d --- /dev/null +++ b/fileformats/medimage/raw/pet/base.py @@ -0,0 +1,27 @@ +from fileformats.generic import File + + +class PetRawData(File): + + binary = True + iana_mime = None + + +class PetListMode(PetRawData): + "raw projection data" + iana_mime = None + + +class PetSinogram(PetRawData): + "histogrammed projection data in a reconstruction-friendly format" + iana_mime = None + + +class PetCountRate(PetRawData): + "number of prompt/random/single events per unit time" + iana_mime = None + + +class PetNormalisation(PetRawData): + "normalisation scan or the current cross calibration factor" + iana_mime = None diff --git a/fileformats/medimage/raw/pet/siemens.py b/fileformats/medimage/raw/pet/siemens.py new file mode 100644 index 0000000..ee13e1d --- /dev/null +++ b/fileformats/medimage/raw/pet/siemens.py @@ -0,0 +1,36 @@ +from .base import ( + PetRawData, + PetListMode, + PetSinogram, + PetCountRate, + PetNormalisation, +) + + +class Vnd_Siemens_Biograph128Vision_Vr20b_PetRawData(PetRawData): + iana_mime = None + ext = ".ptd" + + +class Vnd_Siemens_Biograph128Vision_Vr20b_PetListMode( + Vnd_Siemens_Biograph128Vision_Vr20b_PetRawData, PetListMode +): + pass + + +class Vnd_Siemens_Biograph128Vision_Vr20b_PetSinogram( + Vnd_Siemens_Biograph128Vision_Vr20b_PetRawData, PetSinogram +): + "histogrammed projection data in a reconstruction-friendly format" + + +class Vnd_Siemens_Biograph128Vision_Vr20b_PetCountRate( + Vnd_Siemens_Biograph128Vision_Vr20b_PetRawData, PetCountRate +): + "number of prompt/random/single events per unit time" + + +class Vnd_Siemens_Biograph128Vision_Vr20b_PetNormalisation( + Vnd_Siemens_Biograph128Vision_Vr20b_PetRawData, PetNormalisation +): + "normalisation scan or the current cross calibration factor"