Skip to content

Commit

Permalink
Merge pull request #20 from ArcanaFramework/brainhack2023
Browse files Browse the repository at this point in the history
Started to add in radlex classifiers and fixed up extras hooks
  • Loading branch information
tclose authored Dec 6, 2023
2 parents 6bd5e9b + 9a707fb commit cf31c7a
Show file tree
Hide file tree
Showing 15 changed files with 333 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ __pycache__
~*
*.venv
/fileformats/medimage/_version.py
/extras/fileformats/extras/medimage/_version.py
4 changes: 0 additions & 4 deletions extras/fileformats/extras/medimage/_version.py

This file was deleted.

12 changes: 6 additions & 6 deletions extras/fileformats/extras/medimage/dicom.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@


@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)
return np.asarray(image_stack)


@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"],
Expand All @@ -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]):
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):
Expand All @@ -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: 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)
Expand Down
6 changes: 3 additions & 3 deletions extras/fileformats/extras/medimage/diffusion.py
Original file line number Diff line number Diff line change
@@ -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()]
Expand Down
38 changes: 26 additions & 12 deletions extras/fileformats/extras/medimage/nifti.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,65 @@
from pathlib import Path
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
import medimages4tests.dummy.nifti


@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
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: 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)
)


@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: ty.Union[int, Random] = 0, stem: ty.Optional[str] = None
) -> ty.Iterable[Path]:
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: 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]):
def nifti_x_generate_sample_data(
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)
14 changes: 9 additions & 5 deletions fileformats/medimage/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
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,
NiftiGz,
NiftiX,
NiftiGzX,
)
from .diffusion import (
from .diffusion import ( # noqa: F401
DwiEncoding,
Bvec,
Bval,
Expand Down Expand Up @@ -42,3 +43,6 @@
Vnd_Siemens_Biograph128Vision_Vr20b_PetCountRate,
Vnd_Siemens_Biograph128Vision_Vr20b_PetNormalisation,
)
from .surface import (
Gifti # noqa: F401
)
6 changes: 3 additions & 3 deletions fileformats/medimage/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ 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
"""
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
4 changes: 2 additions & 2 deletions fileformats/medimage/dicom.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down
10 changes: 5 additions & 5 deletions fileformats/medimage/diffusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -34,7 +34,7 @@ class Bval(File):
ext = ".bval"

@hook.extra
def read_array(self):
def read_array(self) -> "numpy.ndarray": # noqa
raise NotImplementedError


Expand Down
6 changes: 6 additions & 0 deletions fileformats/medimage/radlex/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from fileformats.core import ClassifierCategory


class AnatomicalEntity(ClassifierCategory):
pass

5 changes: 5 additions & 0 deletions fileformats/medimage/radlex/anatomical/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from fileformats.core import ClassifierCategory


class AnatomicalEntity(ClassifierCategory):
pass
5 changes: 5 additions & 0 deletions fileformats/medimage/radlex/imaging/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from fileformats.core import ClassifierCategory


class ImagingSpecialty(ClassifierCategory):
pass
Loading

0 comments on commit cf31c7a

Please sign in to comment.