From b3af1706b1e049fc4a12d97837f2c3f525475d3f Mon Sep 17 00:00:00 2001 From: Tom Close Date: Thu, 23 Nov 2023 09:16:56 +1100 Subject: [PATCH 01/11] tidied up __init__ --- fileformats/medimage/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fileformats/medimage/__init__.py b/fileformats/medimage/__init__.py index 7873030..11253a1 100644 --- a/fileformats/medimage/__init__.py +++ b/fileformats/medimage/__init__.py @@ -1,12 +1,13 @@ -from ._version import __version__ -from .base import MedicalImage -from fileformats.application import Dicom # imported to alias it here as well +from ._version import __version__ # noqa: F401 +from .base import MedicalImage # noqa: F401 +# import Dicom to alias to the medimage namespace it here as well +from fileformats.application import Dicom # noqa: F401 from .misc import ( # noqa: F401 Analyze, Mgh, MghGz, ) -from .nifti import ( +from .nifti import ( # noqa: F401 Nifti, Nifti1, Nifti2, @@ -14,7 +15,7 @@ NiftiX, NiftiGzX, ) -from .diffusion import ( +from .diffusion import ( # noqa: F401 DwiEncoding, Bvec, Bval, From b54e2fa811c1fe79e2e082bbd55a21631968bf1b Mon Sep 17 00:00:00 2001 From: Tom Close Date: Thu, 23 Nov 2023 09:16:56 +1100 Subject: [PATCH 02/11] added sinogram series to siemens list-mode data --- fileformats/medimage/raw/pet/siemens.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fileformats/medimage/raw/pet/siemens.py b/fileformats/medimage/raw/pet/siemens.py index ee13e1d..d91b129 100644 --- a/fileformats/medimage/raw/pet/siemens.py +++ b/fileformats/medimage/raw/pet/siemens.py @@ -1,3 +1,4 @@ +from fileformats.generic import SetOf from .base import ( PetRawData, PetListMode, @@ -34,3 +35,10 @@ class Vnd_Siemens_Biograph128Vision_Vr20b_PetNormalisation( Vnd_Siemens_Biograph128Vision_Vr20b_PetRawData, PetNormalisation ): "normalisation scan or the current cross calibration factor" + + +class Vnd_Siemens_Biograph128Vision_Vr20b_PetSinogramSeries( + SetOf[Vnd_Siemens_Biograph128Vision_Vr20b_PetSinogram], + Vnd_Siemens_Biograph128Vision_Vr20b_PetRawData, +): + "Series of sinogram images" From 48621d4ca2a0ef7bf13485e042c8631f93461408 Mon Sep 17 00:00:00 2001 From: Tom Close Date: Thu, 23 Nov 2023 09:26:43 +1100 Subject: [PATCH 03/11] sarted adding radlex-based classifiers --- fileformats/medimage/radlex/__init__.py | 6 ++ .../medimage/radlex/anatomical/__init__.py | 5 ++ .../medimage/radlex/imaging/__init__.py | 5 ++ .../medimage/radlex/imaging/modality.py | 69 +++++++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 fileformats/medimage/radlex/__init__.py create mode 100644 fileformats/medimage/radlex/anatomical/__init__.py create mode 100644 fileformats/medimage/radlex/imaging/__init__.py create mode 100644 fileformats/medimage/radlex/imaging/modality.py diff --git a/fileformats/medimage/radlex/__init__.py b/fileformats/medimage/radlex/__init__.py new file mode 100644 index 0000000..3d628c0 --- /dev/null +++ b/fileformats/medimage/radlex/__init__.py @@ -0,0 +1,6 @@ +from fileformats.core import ClassifierCategory + + +class AnatomicalEntity(ClassifierCategory): + pass + diff --git a/fileformats/medimage/radlex/anatomical/__init__.py b/fileformats/medimage/radlex/anatomical/__init__.py new file mode 100644 index 0000000..99c9ad9 --- /dev/null +++ b/fileformats/medimage/radlex/anatomical/__init__.py @@ -0,0 +1,5 @@ +from fileformats.core import ClassifierCategory + + +class AnatomicalEntity(ClassifierCategory): + pass diff --git a/fileformats/medimage/radlex/imaging/__init__.py b/fileformats/medimage/radlex/imaging/__init__.py new file mode 100644 index 0000000..507402c --- /dev/null +++ b/fileformats/medimage/radlex/imaging/__init__.py @@ -0,0 +1,5 @@ +from fileformats.core import ClassifierCategory + + +class ImagingSpecialty(ClassifierCategory): + pass diff --git a/fileformats/medimage/radlex/imaging/modality.py b/fileformats/medimage/radlex/imaging/modality.py new file mode 100644 index 0000000..519b003 --- /dev/null +++ b/fileformats/medimage/radlex/imaging/modality.py @@ -0,0 +1,69 @@ +from fileformats.core import ClassifierCategory + + +class ImagingModality(ClassifierCategory): + pass + + +class CombinedModalities(ImagingModality): + pass + + +class DualEnergyXrayAbsorptiometry(ImagingModality): + pass + + +class Fluoroscopy(ImagingModality): + pass + + +class MagneticResonanceImaging(ImagingModality): + pass + + +class MagneticResonanceSpectroscopy(ImagingModality): + pass + + +class NuclearMedicineImaging(ImagingModality): + pass + + +class PanographicRadiograph(ImagingModality): + pass + + +class ProjectionRadiography(ImagingModality): + pass + + +class Spectroscopy(ImagingModality): + pass + + +class Tomography(ImagingModality): + pass + + +class Ultrasound(ImagingModality): + pass + + +class DiffusionTensorImaging(MagneticResonanceImaging): + pass + + +class DynamicContrast(MagneticResonanceImaging): + pass + + +class EnhancedMagneticResonanceImaging(MagneticResonanceImaging): + pass + + +class FunctionalMagneticResonanceImaging(MagneticResonanceImaging): + pass + + +class MagneticResonanceAngiography(MagneticResonanceImaging): + pass From 553a086f767f92159e1f4e0a5de7b21ac1203791 Mon Sep 17 00:00:00 2001 From: Hakim Achterberg Date: Thu, 23 Nov 2023 09:26:43 +1100 Subject: [PATCH 04/11] Add more information into th emodality and add radlex links --- .../medimage/radlex/imaging/modality.py | 222 ++++++++++++++++-- 1 file changed, 197 insertions(+), 25 deletions(-) diff --git a/fileformats/medimage/radlex/imaging/modality.py b/fileformats/medimage/radlex/imaging/modality.py index 519b003..604aea9 100644 --- a/fileformats/medimage/radlex/imaging/modality.py +++ b/fileformats/medimage/radlex/imaging/modality.py @@ -2,68 +2,240 @@ class ImagingModality(ClassifierCategory): - pass + ontology_link = 'http://www.radlex.org/RID/RID10311' + description = 'Form of imaging that depends on the way the image is produced' + dicom_modality = None class CombinedModalities(ImagingModality): - pass + ontology_link = 'http://www.radlex.org/RID/RID49580' + description = 'These are cases where 2 different modalities are ' \ + 'performed in the same imaging setup without moving the ' \ + 'patient. These classes were created as sets, with the ' \ + 'individual modalities as the set members. Any reasoning ' \ + 'involving modality should accommodate this.' class DualEnergyXrayAbsorptiometry(ImagingModality): - pass + ontology_link = 'http://www.radlex.org/RID/RID10363' + description = None class Fluoroscopy(ImagingModality): - pass + ontology_link = 'http://www.radlex.org/RID/RID10361' + description = None + + +class MrFluoroscopy(Fluoroscopy): + ontology_link = 'http://www.radlex.org/RID/RID10319' + description = 'Non-invasive method of vascular imaging and determination ' \ + 'of internal anatomy without injection of contrast media or ' \ + 'radiation exposure. The technique is used especially in ' \ + 'cerebral angiography as well as for studies of other vascular ' \ + 'structures. [MeSH]' + + +class RadioFluoroscopy(Fluoroscopy): + ontology_link = 'http://www.radlex.org/RID/RID45709' + description = 'Production of an image when x-rays strike a fluorescent screen. [MeSH]' class MagneticResonanceImaging(ImagingModality): + ontology_link = 'http://www.radlex.org/RID/RID10312' + description = 'Non-invasive method of demonstrating internal anatomy ' \ + 'based on the principle that atomic nuclei in a strong ' \ + 'magnetic field absorb pulses of radiofrequency energy and ' \ + 'emit them as radiowaves which can be reconstructed into ' \ + 'computerized images. The concept includes proton spin ' \ + 'tomographic techniques. [MeSH]' + dicom_modality = 'MR' + + +class DiffusionTensorImaging(MagneticResonanceImaging): + ontology_link = 'http://www.radlex.org/RID/RID38778' + description = None + + +class DynamicContrast(MagneticResonanceImaging): + ontology_link = 'http://www.radlex.org/RID/RID49531' + description = 'An imaging method with a timed series of T1-weighted ' \ + 'images used to detect and measure signal intensity ' \ + 'change (enhancement) over time following administration ' \ + 'of intravenous contrast agent to noninvasively access ' \ + 'tissue vascular characteristics.' + + +class EnhancedMagneticResonanceImaging(MagneticResonanceImaging): pass +class FunctionalMagneticResonanceImaging(MagneticResonanceImaging): + ontology_link = 'http://www.radlex.org/RID/RID10317' + description = None + + +class MagneticResonanceAngiography(MagneticResonanceImaging): + ontology_link = 'http://www.radlex.org/RID/RID10319' + description = None + + class MagneticResonanceSpectroscopy(ImagingModality): - pass + ontology_link = 'http://www.radlex.org/RID/RID10315' + description = ' Spectroscopic method of measuring the magnetic moment of ' \ + 'elementary particles such as atomic nuclei, protons or ' \ + 'electrons. It is employed in clinical applications such as ' \ + 'nmr tomography (magnetic resonance imaging). [MeSH]' class NuclearMedicineImaging(ImagingModality): - pass - + ontology_link = 'http://www.radlex.org/RID/RID10330' + description = None + dicom_modality = 'NM' + + +class PositronEmissionTomography(NuclearMedicineImaging): + ontology_link = 'http://www.radlex.org/RID/RID10337' + description = 'An imaging technique using compounds labelled with ' \ + 'short-lived positron-emitting radionuclides (such as ' \ + 'carbon-11, nitrogen-13, oxygen-15 and fluorine-18) to ' \ + 'measure cell metabolism. It has been useful in study of ' \ + 'soft tissues such as cancer; cardiovascular system; and ' \ + 'brain. SPECT is closely related to PET, but uses isotopes ' \ + 'with longer half-lives and resolution is lower. [MeSH]' + dicom_modality = 'PT' class PanographicRadiograph(ImagingModality): - pass + ontology_link = 'http://www.radlex.org/RID/RID10360' + description = None + dicom_modality = 'PX' class ProjectionRadiography(ImagingModality): - pass + ontology_link = 'http://www.radlex.org/RID/RID10345' + description = 'Examination of any part of the body for diagnostic purposes ' \ + 'by means of roentgen rays, recording the image on a ' \ + 'sensitized surface (such as photographic film). [MeSH]' -class Spectroscopy(ImagingModality): - pass +class ComputedRadiography(ProjectionRadiography): + ontology_link = 'http://www.radlex.org/RID/RID10349' + dicom_modality = 'CR' + description = None -class Tomography(ImagingModality): - pass +class DigitalRadiography(ProjectionRadiography): + ontology_link = 'http://www.radlex.org/RID/RID10351' + dicom_modality = 'DR' + description = None -class Ultrasound(ImagingModality): - pass +class DualEnergySubtractionRadiograpgy(ProjectionRadiography): + ontology_link = 'http://www.radlex.org/RID/RID10356' + description = None -class DiffusionTensorImaging(MagneticResonanceImaging): - pass +class Mammography(ProjectionRadiography): + ontology_link = 'http://www.radlex.org/RID/RID10357' + description = None -class DynamicContrast(MagneticResonanceImaging): - pass +class ScreenFilmRadiography(ProjectionRadiography): + ontology_link = 'http://www.radlex.org/RID/RID10353' + description = 'Conventional radiography' + dicom_modality = 'RG' +class Stereoscopy(ProjectionRadiography): + ontology_link = 'http://www.radlex.org/RID/RID50131' + description = None -class EnhancedMagneticResonanceImaging(MagneticResonanceImaging): - pass +class StereotacticRadiography(ProjectionRadiography): + ontology_link = 'http://www.radlex.org/RID/RID50260' + description = None -class FunctionalMagneticResonanceImaging(MagneticResonanceImaging): - pass +class Spectroscopy(ImagingModality): + ontology_link = 'http://www.radlex.org/RID/RID10377' + description = 'The measurement of the amplitude of the components of a ' \ + 'complex waveform throughout the frequency range of the ' \ + 'waveform. (McGraw-Hill Dictionary of Scientific and ' \ + 'Technical Terms, 4th ed) [MeSH]' -class MagneticResonanceAngiography(MagneticResonanceImaging): - pass + +class Tomography(ImagingModality): + ontology_link = 'http://www.radlex.org/RID/RID28840' + description = None + + +class ComputedTomography(Tomography): + ontology_link = 'http://www.radlex.org/RID/RID10321' + description = 'Tomography using x-ray transmission and a computer algorithm ' \ + 'to reconstruct the image. [MeSH]' + dicom_modality = 'CT' + + +class Ultrasound(ImagingModality): + ontology_link = 'http://www.radlex.org/RID/RID10326' + description = 'The visualization of deep structures of the body by ' \ + 'recording the reflections of echoes of pulses of ultrasonic ' \ + 'waves directed into the tissues. Use of ultrasound for ' \ + 'imaging or diagnostic purposes employs frequencies ranging ' \ + 'from 1.6 to 10 megahertz. [MeSH]' + dicom_modality = 'US' + + +# Some extra abbreviations +MRI = MagneticResonanceImaging +PET = PositronEmissionTomography + +# DICOM abbreviations, taken from: https://dicom.nema.org/medical/dicom/current/output/chtml/part16/sect_CID_29.html +CR = ComputedRadiography +CT = ComputedTomography +DX = DigitalRadiography +MG = Mammography +MR = MagneticResonanceImaging +NM = NuclearMedicineImaging +PT = PositronEmissionTomography +PX = PanographicRadiograph +RF = RadioFluoroscopy +RG = ScreenFilmRadiography +US = Ultrasound +# AR = Autorefraction +# BI = Biomagnetic Imaging +# BMD = Bone Mineral Densitometry +# EPS = Cardiac Electrophysiology +# DMS = Dermoscopy +# DG = Diaphanography +# ECG = Electrocardiography +# EEG = Electroencephalography +# EMG = Electromyography +# EOG = Electrooculography +# ES = Endoscopy +# XC = External-camera Photography +# GM = General Microscopy +# HD = Hemodynamic Waveform +# IO = Intra-oral Radiography +# IVOCT = Intravascular Optical Coherence Tomography +# IVUS = Intravascular Ultrasound +# KER = Keratometry +# LS = Laser Scan +# LEN = Lensometry +# OAM = Ophthalmic Axial Measurements +# OPM = Ophthalmic Mapping +# OP = Ophthalmic Photography +# OPT = Ophthalmic Tomography +# OPTBSV = Ophthalmic Tomography B-scan Volume Analysis +# OPTENF = Ophthalmic Tomography En Face +# OPV = Ophthalmic Visual Field +# OCT = Optical Coherence Tomography +# OSS = Optical Surface Scanner +# PA = Photoacoustic +# POS = Position Sensor +# RESP = Respiratory Waveform +# RTIMAGE = RT Image +# SM = Slide Microscopy +# SRF = Subjective Refraction +# TG = Thermography +# BDUS = Ultrasound Bone Densitometry +# VA = Visual Acuity +# XA = X-Ray Angiography From 83bc36b52e7b3419227b41d61bc8e418c93fc8e3 Mon Sep 17 00:00:00 2001 From: Tom Close Date: Thu, 23 Nov 2023 14:54:52 +1100 Subject: [PATCH 05/11] updated nifti gen data so that it uses gen_filename --- extras/fileformats/extras/medimage/nifti.py | 24 +++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/extras/fileformats/extras/medimage/nifti.py b/extras/fileformats/extras/medimage/nifti.py index 608871b..4160d4f 100644 --- a/extras/fileformats/extras/medimage/nifti.py +++ b/extras/fileformats/extras/medimage/nifti.py @@ -2,6 +2,7 @@ import typing as ty import nibabel from fileformats.core import FileSet +from fileformats.core.utils import gen_filename from fileformats.medimage import MedicalImage, Nifti, NiftiGz, Nifti1, NiftiGzX, NiftiX import medimages4tests.dummy.nifti @@ -29,23 +30,34 @@ def nifti_dims(nifti: Nifti): @FileSet.generate_sample_data.register -def nifti_generate_sample_data(nifti: Nifti1, dest_dir: Path, seed: int, stem: ty.Optional[str]): - return medimages4tests.dummy.nifti.get_image(out_file=dest_dir / "nifti.nii") +def nifti_generate_sample_data( + nifti: Nifti1, dest_dir: Path, seed: int, stem: ty.Optional[str] +): + return medimages4tests.dummy.nifti.get_image( + out_file=dest_dir / gen_filename(seed, file_type=Nifti1, stem=stem) + ) @FileSet.generate_sample_data.register -def nifti_gz_generate_sample_data(nifti: NiftiGz, dest_dir: Path, seed: int, stem: ty.Optional[str]): +def nifti_gz_generate_sample_data( + nifti: NiftiGz, dest_dir: Path, seed: int, stem: ty.Optional[str] +): return medimages4tests.dummy.nifti.get_image( - out_file=dest_dir / "nifti.nii.gz", compressed=True + out_file=dest_dir / gen_filename(seed, file_type=NiftiGz, stem=stem), + compressed=True, ) @FileSet.generate_sample_data.register -def nifti_gz_x_generate_sample_data(nifti: NiftiGzX, dest_dir: Path, seed: int, stem: ty.Optional[str]): +def nifti_gz_x_generate_sample_data( + nifti: NiftiGzX, dest_dir: Path, seed: int, stem: ty.Optional[str] +): return medimages4tests.mri.neuro.t1w.get_image() @FileSet.generate_sample_data.register -def nifti_x_generate_sample_data(nifti: NiftiX, dest_dir: Path, seed: int, stem: ty.Optional[str]): +def nifti_x_generate_sample_data( + nifti: NiftiX, dest_dir: Path, seed: int, stem: ty.Optional[str] +): nifti_gz_x = NiftiGzX(medimages4tests.mri.neuro.t1w.get_image()) return NiftiX.convert(nifti_gz_x) From 811e155f1504e020adc0777d5f5f978181476f5c Mon Sep 17 00:00:00 2001 From: Tom Close Date: Fri, 24 Nov 2023 13:15:26 +1100 Subject: [PATCH 06/11] added gifti fileformat --- fileformats/medimage/surface.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 fileformats/medimage/surface.py diff --git a/fileformats/medimage/surface.py b/fileformats/medimage/surface.py new file mode 100644 index 0000000..3c6e39f --- /dev/null +++ b/fileformats/medimage/surface.py @@ -0,0 +1,13 @@ +import typing as ty +from fileformats.core import FileSet +from fileformats.application import Xml + + +class SurfaceMesh(FileSet): + + iana_mime: ty.Optional[str] = None + + +class Gifti(SurfaceMesh, Xml): + + ext = ".gii" From 4386a174a83033831a998d561cc1254eea650a46 Mon Sep 17 00:00:00 2001 From: Tom Close Date: Fri, 24 Nov 2023 13:19:08 +1100 Subject: [PATCH 07/11] imported gifti into package root --- fileformats/medimage/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fileformats/medimage/__init__.py b/fileformats/medimage/__init__.py index 11253a1..0dace6b 100644 --- a/fileformats/medimage/__init__.py +++ b/fileformats/medimage/__init__.py @@ -43,3 +43,6 @@ Vnd_Siemens_Biograph128Vision_Vr20b_PetCountRate, Vnd_Siemens_Biograph128Vision_Vr20b_PetNormalisation, ) +from .surface import ( + Gifti # noqa: F401 +) From 374d35c073f3eb3b2cb646a7c2e27983a8f749df Mon Sep 17 00:00:00 2001 From: Tom Close Date: Tue, 28 Nov 2023 22:15:09 +1100 Subject: [PATCH 08/11] added extras version to gitignore --- .gitignore | 1 + extras/fileformats/extras/medimage/_version.py | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 extras/fileformats/extras/medimage/_version.py diff --git a/.gitignore b/.gitignore index dea060a..a444de5 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ __pycache__ ~* *.venv /fileformats/medimage/_version.py +/extras/fileformats/extras/medimage/_version.py diff --git a/extras/fileformats/extras/medimage/_version.py b/extras/fileformats/extras/medimage/_version.py deleted file mode 100644 index 7519301..0000000 --- a/extras/fileformats/extras/medimage/_version.py +++ /dev/null @@ -1,4 +0,0 @@ -# file generated by setuptools_scm -# don't change, don't track in version control -__version__ = version = '0.1.6.dev12+ga52eb33' -__version_tuple__ = version_tuple = (0, 1, 6, 'dev12', 'ga52eb33') From 33795f807b4ddd8c77f51dbf2d2222afe69f60d2 Mon Sep 17 00:00:00 2001 From: Tom Close Date: Wed, 6 Dec 2023 13:52:42 +1100 Subject: [PATCH 09/11] updated extras signatures to match hooks --- extras/fileformats/extras/medimage/dicom.py | 4 ++-- extras/fileformats/extras/medimage/nifti.py | 8 ++++---- fileformats/medimage/dicom.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/extras/fileformats/extras/medimage/dicom.py b/extras/fileformats/extras/medimage/dicom.py index 98167d5..484e42d 100644 --- a/extras/fileformats/extras/medimage/dicom.py +++ b/extras/fileformats/extras/medimage/dicom.py @@ -42,7 +42,7 @@ def dicom_series_number(collection: DicomCollection): @FileSet.generate_sample_data.register -def dicom_dir_generate_sample_data(dcmdir: DicomDir, dest_dir: Path, seed: ty.Union[int, Random], stem: ty.Optional[str]): +def dicom_dir_generate_sample_data(dcmdir: DicomDir, dest_dir: Path, seed: ty.Union[int, Random], stem: ty.Optional[str]) -> ty.Iterable[Path]: dcm_dir = medimages4tests.dummy.dicom.mri.t1w.siemens.skyra.syngo_d13c.get_image() # Set series number to random value to make it different if isinstance(seed, Random): @@ -60,7 +60,7 @@ def dicom_dir_generate_sample_data(dcmdir: DicomDir, dest_dir: Path, seed: ty.Un @FileSet.generate_sample_data.register -def dicom_set_generate_sample_data(dcm_series: DicomSeries, dest_dir: Path, seed: int, stem: ty.Optional[str]): +def dicom_set_generate_sample_data(dcm_series: DicomSeries, dest_dir: Path, seed: int, stem: ty.Optional[str]) -> ty.Iterable[Path]: rng = Random(seed) dicom_dir = dicom_dir_generate_sample_data(dcm_series, dest_dir=mkdtemp(), seed=rng, stem=None)[0] stem = gen_filename(rng, stem=stem) diff --git a/extras/fileformats/extras/medimage/nifti.py b/extras/fileformats/extras/medimage/nifti.py index 4160d4f..5aaff4a 100644 --- a/extras/fileformats/extras/medimage/nifti.py +++ b/extras/fileformats/extras/medimage/nifti.py @@ -32,7 +32,7 @@ def nifti_dims(nifti: Nifti): @FileSet.generate_sample_data.register def nifti_generate_sample_data( nifti: Nifti1, dest_dir: Path, seed: int, stem: ty.Optional[str] -): +) -> ty.Iterable[Path]: return medimages4tests.dummy.nifti.get_image( out_file=dest_dir / gen_filename(seed, file_type=Nifti1, stem=stem) ) @@ -41,7 +41,7 @@ def nifti_generate_sample_data( @FileSet.generate_sample_data.register def nifti_gz_generate_sample_data( nifti: NiftiGz, dest_dir: Path, seed: int, stem: ty.Optional[str] -): +) -> ty.Iterable[Path]: return medimages4tests.dummy.nifti.get_image( out_file=dest_dir / gen_filename(seed, file_type=NiftiGz, stem=stem), compressed=True, @@ -51,13 +51,13 @@ def nifti_gz_generate_sample_data( @FileSet.generate_sample_data.register def nifti_gz_x_generate_sample_data( nifti: NiftiGzX, dest_dir: Path, seed: int, stem: ty.Optional[str] -): +) -> ty.Iterable[Path]: return medimages4tests.mri.neuro.t1w.get_image() @FileSet.generate_sample_data.register def nifti_x_generate_sample_data( nifti: NiftiX, dest_dir: Path, seed: int, stem: ty.Optional[str] -): +) -> ty.Iterable[Path]: nifti_gz_x = NiftiGzX(medimages4tests.mri.neuro.t1w.get_image()) return NiftiX.convert(nifti_gz_x) diff --git a/fileformats/medimage/dicom.py b/fileformats/medimage/dicom.py index 509b03e..7ba930f 100644 --- a/fileformats/medimage/dicom.py +++ b/fileformats/medimage/dicom.py @@ -51,7 +51,7 @@ def from_paths( @FileSet.read_metadata.register -def dicom_collection_read_metadata(collection: DicomCollection) -> ty.Dict[str, ty.Any]: +def dicom_collection_read_metadata(collection: DicomCollection) -> ty.Mapping[str, ty.Any]: # Collated DICOM headers across series collated = copy(collection.contents[0].metadata) for i, dicom in enumerate(collection.contents[1:], start=1): From cdbe8bb7f612872846f5ea930420170eebef619e Mon Sep 17 00:00:00 2001 From: Tom Close Date: Wed, 6 Dec 2023 14:50:57 +1100 Subject: [PATCH 10/11] fixed up extras hooks signatures --- extras/fileformats/extras/medimage/dicom.py | 10 +++++----- extras/fileformats/extras/medimage/nifti.py | 9 +++++---- fileformats/medimage/base.py | 4 ++-- fileformats/medimage/dicom.py | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/extras/fileformats/extras/medimage/dicom.py b/extras/fileformats/extras/medimage/dicom.py index 484e42d..2996735 100644 --- a/extras/fileformats/extras/medimage/dicom.py +++ b/extras/fileformats/extras/medimage/dicom.py @@ -19,14 +19,14 @@ def dicom_read_array(collection: DicomCollection): @MedicalImage.vox_sizes.register -def dicom_vox_sizes(collection: DicomCollection): +def dicom_vox_sizes(collection: DicomCollection) -> ty.Tuple[float, float, float]: return tuple( collection.metadata["PixelSpacing"] + [collection.metadata["SliceThickness"]] ) @MedicalImage.dims.register -def dicom_dims(collection: DicomCollection): +def dicom_dims(collection: DicomCollection) -> ty.Tuple[int, int, int]: return tuple( ( collection.metadata["Rows"], @@ -37,12 +37,12 @@ def dicom_dims(collection: DicomCollection): @DicomCollection.series_number.register -def dicom_series_number(collection: DicomCollection): +def dicom_series_number(collection: DicomCollection) -> str: return str(collection.metadata["SeriesNumber"]) @FileSet.generate_sample_data.register -def dicom_dir_generate_sample_data(dcmdir: DicomDir, dest_dir: Path, seed: ty.Union[int, Random], stem: ty.Optional[str]) -> ty.Iterable[Path]: +def dicom_dir_generate_sample_data(dcmdir: DicomDir, dest_dir: Path, seed: ty.Union[int, Random] = 0, stem: ty.Optional[str] = None) -> ty.Iterable[Path]: dcm_dir = medimages4tests.dummy.dicom.mri.t1w.siemens.skyra.syngo_d13c.get_image() # Set series number to random value to make it different if isinstance(seed, Random): @@ -60,7 +60,7 @@ def dicom_dir_generate_sample_data(dcmdir: DicomDir, dest_dir: Path, seed: ty.Un @FileSet.generate_sample_data.register -def dicom_set_generate_sample_data(dcm_series: DicomSeries, dest_dir: Path, seed: int, stem: ty.Optional[str]) -> ty.Iterable[Path]: +def dicom_set_generate_sample_data(dcm_series: DicomSeries, dest_dir: Path, seed: ty.Union[int, Random] = 0, stem: ty.Optional[str] = None) -> ty.Iterable[Path]: rng = Random(seed) dicom_dir = dicom_dir_generate_sample_data(dcm_series, dest_dir=mkdtemp(), seed=rng, stem=None)[0] stem = gen_filename(rng, stem=stem) diff --git a/extras/fileformats/extras/medimage/nifti.py b/extras/fileformats/extras/medimage/nifti.py index 5aaff4a..cf43b68 100644 --- a/extras/fileformats/extras/medimage/nifti.py +++ b/extras/fileformats/extras/medimage/nifti.py @@ -1,5 +1,6 @@ from pathlib import Path import typing as ty +from random import Random import nibabel from fileformats.core import FileSet from fileformats.core.utils import gen_filename @@ -31,7 +32,7 @@ def nifti_dims(nifti: Nifti): @FileSet.generate_sample_data.register def nifti_generate_sample_data( - nifti: Nifti1, dest_dir: Path, seed: int, stem: ty.Optional[str] + nifti: Nifti1, dest_dir: Path, seed: ty.Union[int, Random] = 0, stem: ty.Optional[str] = None ) -> ty.Iterable[Path]: return medimages4tests.dummy.nifti.get_image( out_file=dest_dir / gen_filename(seed, file_type=Nifti1, stem=stem) @@ -40,7 +41,7 @@ def nifti_generate_sample_data( @FileSet.generate_sample_data.register def nifti_gz_generate_sample_data( - nifti: NiftiGz, dest_dir: Path, seed: int, stem: ty.Optional[str] + nifti: NiftiGz, dest_dir: Path, seed: ty.Union[int, Random] = 0, stem: ty.Optional[str] = None ) -> ty.Iterable[Path]: return medimages4tests.dummy.nifti.get_image( out_file=dest_dir / gen_filename(seed, file_type=NiftiGz, stem=stem), @@ -50,14 +51,14 @@ def nifti_gz_generate_sample_data( @FileSet.generate_sample_data.register def nifti_gz_x_generate_sample_data( - nifti: NiftiGzX, dest_dir: Path, seed: int, stem: ty.Optional[str] + nifti: NiftiGzX, dest_dir: Path, seed: ty.Union[int, Random] = 0, stem: ty.Optional[str] = None ) -> ty.Iterable[Path]: return medimages4tests.mri.neuro.t1w.get_image() @FileSet.generate_sample_data.register def nifti_x_generate_sample_data( - nifti: NiftiX, dest_dir: Path, seed: int, stem: ty.Optional[str] + nifti: NiftiX, dest_dir: Path, seed: ty.Union[int, Random] = 0, stem: ty.Optional[str] = None ) -> ty.Iterable[Path]: nifti_gz_x = NiftiGzX(medimages4tests.mri.neuro.t1w.get_image()) return NiftiX.convert(nifti_gz_x) diff --git a/fileformats/medimage/base.py b/fileformats/medimage/base.py index b72869e..bc620ec 100644 --- a/fileformats/medimage/base.py +++ b/fileformats/medimage/base.py @@ -26,11 +26,11 @@ def read_array(self): raise NotImplementedError @hook.extra - def vox_sizes(self) -> ty.Tuple[float]: + def vox_sizes(self) -> ty.Tuple[float, float, float]: """The length of the voxels along each dimension""" raise NotImplementedError @hook.extra - def dims(self) -> ty.Tuple[int]: + def dims(self) -> ty.Tuple[int, int, int]: """The dimensions of the image""" raise NotImplementedError diff --git a/fileformats/medimage/dicom.py b/fileformats/medimage/dicom.py index 7ba930f..70bf677 100644 --- a/fileformats/medimage/dicom.py +++ b/fileformats/medimage/dicom.py @@ -26,7 +26,7 @@ def __len__(self): return len(self.contents) @hook.extra - def series_number(self): + def series_number(self) -> str: raise NotImplementedError @cached_property From 9a707fbbab77e4dd88dbdb8933cbaa1f11203f3f Mon Sep 17 00:00:00 2001 From: Tom Close Date: Wed, 6 Dec 2023 16:17:17 +1100 Subject: [PATCH 11/11] debugged extras hooks signatures --- extras/fileformats/extras/medimage/dicom.py | 2 +- extras/fileformats/extras/medimage/diffusion.py | 6 +++--- extras/fileformats/extras/medimage/nifti.py | 13 +++++++------ fileformats/medimage/base.py | 2 +- fileformats/medimage/diffusion.py | 10 +++++----- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/extras/fileformats/extras/medimage/dicom.py b/extras/fileformats/extras/medimage/dicom.py index 2996735..e97bee3 100644 --- a/extras/fileformats/extras/medimage/dicom.py +++ b/extras/fileformats/extras/medimage/dicom.py @@ -11,7 +11,7 @@ @MedicalImage.read_array.register -def dicom_read_array(collection: DicomCollection): +def dicom_read_array(collection: DicomCollection) -> np.ndarray: image_stack = [] for dcm_file in collection.contents: image_stack.append(pydicom.dcmread(dcm_file).pixel_array) diff --git a/extras/fileformats/extras/medimage/diffusion.py b/extras/fileformats/extras/medimage/diffusion.py index 0b8399f..9267153 100644 --- a/extras/fileformats/extras/medimage/diffusion.py +++ b/extras/fileformats/extras/medimage/diffusion.py @@ -1,14 +1,14 @@ import numpy as np -from fileformats.medimage.diffusion import DwiEncoding, Bval, Bvec +from fileformats.medimage import DwiEncoding, Bval, Bvec @Bval.read_array.register -def bval_read_array(bval: Bval): +def bval_read_array(bval: Bval) -> np.ndarray: # noqa return np.asarray([float(ln) for ln in bval.read_contents().split()]) @DwiEncoding.read_array.register -def bvec_read_array(bvec: Bvec): +def bvec_read_array(bvec: Bvec) -> np.ndarray: # noqa bvals = bvec.b_values_file.read_array() directions = np.asarray( [[float(x) for x in ln.split()] for ln in bvec.read_contents().splitlines()] diff --git a/extras/fileformats/extras/medimage/nifti.py b/extras/fileformats/extras/medimage/nifti.py index cf43b68..bb40982 100644 --- a/extras/fileformats/extras/medimage/nifti.py +++ b/extras/fileformats/extras/medimage/nifti.py @@ -2,6 +2,7 @@ import typing as ty from random import Random import nibabel +import numpy as np from fileformats.core import FileSet from fileformats.core.utils import gen_filename from fileformats.medimage import MedicalImage, Nifti, NiftiGz, Nifti1, NiftiGzX, NiftiX @@ -9,25 +10,25 @@ @FileSet.read_metadata.register -def nifti_read_metadata(nifti: Nifti): +def nifti_read_metadata(nifti: Nifti) -> ty.Mapping[str, ty.Any]: return dict(nibabel.load(nifti.fspath).header) @MedicalImage.read_array.register -def nifti_data_array(nifti: Nifti): +def nifti_data_array(nifti: Nifti) -> np.ndarray: # noqa return nibabel.load(nifti.fspath).get_data() @MedicalImage.vox_sizes.register -def nifti_vox_sizes(nifti: Nifti): +def nifti_vox_sizes(nifti: Nifti) -> ty.Tuple[float, float, float]: # FIXME: This won't work for 4-D files - return nifti.metadata["pixdim"][1:4] + return tuple(float(d) for d in nifti.metadata["pixdim"][1:4]) @MedicalImage.dims.register -def nifti_dims(nifti: Nifti): +def nifti_dims(nifti: Nifti) -> ty.Tuple[int, int, int]: # FIXME: This won't work for 4-D files - return nifti.metadata["dim"][1:4] + return tuple(int(d) for d in nifti.metadata["dim"][1:4]) @FileSet.generate_sample_data.register diff --git a/fileformats/medimage/base.py b/fileformats/medimage/base.py index bc620ec..5526058 100644 --- a/fileformats/medimage/base.py +++ b/fileformats/medimage/base.py @@ -19,7 +19,7 @@ class MedicalImage(FileSet): binary = True @hook.extra - def read_array(self): + def read_array(self) -> "numpy.ndarray": # noqa """ Returns the binary data of the image in a numpy array """ diff --git a/fileformats/medimage/diffusion.py b/fileformats/medimage/diffusion.py index 849532f..0ddc89a 100644 --- a/fileformats/medimage/diffusion.py +++ b/fileformats/medimage/diffusion.py @@ -10,21 +10,21 @@ class DwiEncoding(File): iana_mime: ty.Optional[str] = None @hook.extra - def read_array(self): + def read_array(self) -> "numpy.ndarray": # noqa "Both the gradient direction and weighting combined into a single Nx4 array" raise NotImplementedError @property - def array(self): + def array(self) -> "numpy.ndarray": # noqa return self.read_array() @property - def directions(self): + def directions(self) -> "numpy.ndarray": # noqa "gradient direction and weighting combined into a single Nx4 array" return self.array[:, :3] @property - def b_values(self): + def b_values(self) -> "numpy.ndarray": # noqa "the b-value weighting" return self.array[:, 3] @@ -34,7 +34,7 @@ class Bval(File): ext = ".bval" @hook.extra - def read_array(self): + def read_array(self) -> "numpy.ndarray": # noqa raise NotImplementedError