Пример #1
0
    def _generate_mock_data(self, shape=None, metadata=True):
        """Generates arbitrary mock data for QDess sequence.

        Metadata values were extracted from a real qDESS sequence.
        """
        if shape is None:
            shape = (10, 10, 10)
        e1 = MedicalVolume(np.random.rand(*shape) * 80 + 0.1, affine=np.eye(4))
        e2 = MedicalVolume(np.random.rand(*shape) * 40 + 0.1, affine=np.eye(4))
        ys = [e1, e2]
        ts = [8, 42]
        if metadata:
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                for idx, (y, t) in enumerate(zip(ys, ts)):
                    y.set_metadata("EchoTime", t, force=True)
                    y.set_metadata("EchoNumber", idx + 1, force=True)
                    y.set_metadata("RepetitionTime", 25.0, force=True)
                    y.set_metadata("FlipAngle", 30.0, force=True)
                    y.set_metadata(Tag(0x001910B6), 3132.0,
                                   force=True)  # gradient time
                    y.set_metadata(Tag(0x001910B7), 1560.0,
                                   force=True)  # gradient area

        return ys, ts, None
Пример #2
0
    def test_comparison(self):
        mv1 = MedicalVolume(np.ones((10, 20, 30)), self._AFFINE)
        mv2 = MedicalVolume(2 * np.ones((10, 20, 30)), self._AFFINE)

        assert np.all((mv1 == mv1.clone()).volume)
        assert np.all((mv1 != mv2).volume)
        assert np.all((mv1 < mv2).volume)
        assert np.all((mv1 <= mv2).volume)
        assert np.all((mv2 > mv1).volume)
        assert np.all((mv2 >= mv1).volume)
Пример #3
0
    def load(self, file_path):
        """Load volume from NIfTI file path.

        A NIfTI file should only correspond to one volume.

        Args:
            file_path (str): File path to NIfTI file.

        Returns:
            MedicalVolume: Loaded volume.

        Raises:
            FileNotFoundError: If `file_path` not found.
            ValueError: If `file_path` does not end in a supported NIfTI extension.
        """
        if not os.path.isfile(file_path):
            raise FileNotFoundError("{} not found".format(file_path))

        if not self.data_format_code.is_filetype(file_path):
            raise ValueError(
                "{} must be a file with extension '.nii' or '.nii.gz'".format(
                    file_path))

        nib_img = nib.load(file_path)
        nib_img_affine = nib_img.affine
        nib_img_affine = self.__normalize_affine(nib_img_affine)

        np_img = nib_img.get_fdata()

        return MedicalVolume(np_img, nib_img_affine)
Пример #4
0
    def test_mask(self):
        x, y, b = _generate_monoexp_data((10, 10, 20))
        mask_arr = np.random.rand(*y[0].shape) > 0.5
        mask = MedicalVolume(mask_arr, y[0].affine)

        fitter = CurveFitter(monoexponential)
        popt = fitter.fit(x, y, mask=mask)[0]
        a_hat, b_hat = popt[..., 0], popt[..., 1]

        assert np.allclose(a_hat.volume[mask_arr != 0], 1.0)
        assert np.allclose(b_hat.volume[mask_arr != 0], b[mask_arr != 0])
        assert np.all(np.isnan(a_hat.volume[mask_arr == 0]))
        assert np.all(np.isnan(b_hat.volume[mask_arr == 0]))

        fitter = CurveFitter(monoexponential)
        popt = fitter.fit(x, y, mask=mask_arr)[0]
        a_hat, b_hat = popt[..., 0], popt[..., 1]

        assert np.allclose(a_hat.volume[mask_arr != 0], 1.0)
        assert np.allclose(b_hat.volume[mask_arr != 0], b[mask_arr != 0])

        with self.assertRaises(TypeError):
            fitter = CurveFitter(monoexponential)
            popt = fitter.fit(x, y, mask="foo")[0]

        with self.assertRaises(RuntimeError):
            mask_incorrect_shape = np.random.rand(5, 5, 5) > 0.5
            fitter = CurveFitter(monoexponential)
            popt = fitter.fit(x, y, mask=mask_incorrect_shape)[0]
Пример #5
0
    def test_device_gpu(self):
        import cupy as cp

        mv = MedicalVolume(np.ones((10, 20, 30)), self._AFFINE)
        mv_gpu = mv.to(Device(0))

        assert mv_gpu.device == Device(0)
        assert isinstance(mv_gpu.volume, cp.ndarray)
        assert isinstance(mv_gpu.affine, np.ndarray)

        assert mv_gpu.is_same_dimensions(mv)

        assert cp.all((mv_gpu + 1).volume == 2)
        assert cp.all((mv_gpu - 1).volume == 0)
        assert cp.all((mv_gpu * 2).volume == 2)
        assert cp.all((mv_gpu / 2).volume == 0.5)
        assert cp.all((mv_gpu > 0).volume)
        assert cp.all((mv_gpu >= 0).volume)
        assert cp.all((mv_gpu < 2).volume)
        assert cp.all((mv_gpu <= 2).volume)

        ornt = tuple(x[::-1] for x in mv_gpu.orientation[::-1])
        mv2 = mv_gpu.reformat(ornt)
        assert mv2.orientation == ornt

        mv_cpu = mv_gpu.cpu()
        assert mv_cpu.device == Device(-1)
        assert mv_cpu.is_identical(mv)

        with self.assertRaises(RuntimeError):
            mv_gpu.save_volume(
                os.path.join(self._TEMP_PATH, "test_device.nii.gz"))
Пример #6
0
    def test_array_gpu(self):
        import cupy as cp

        mv = MedicalVolume(np.ones((10, 20, 30)), self._AFFINE)
        mv_gpu = mv.to(Device(0))
        data = cp.asarray(mv_gpu)
        assert cp.shares_memory(data, mv_gpu.volume)
Пример #7
0
    def test_t2_star_map(self):
        ys, _, _, _ = self._generate_mock_data()
        scan = Cones(ys)

        # No mask
        tissue = FemoralCartilage()
        map1 = scan.generate_t2_star_map(tissue,
                                         num_workers=util.num_workers())
        assert map1 is not None, "map should not be None"

        mask = MedicalVolume(np.ones(ys[0].shape), np.eye(4))

        # Use a mask
        tissue.set_mask(mask)
        map2 = scan.generate_t2_star_map(tissue,
                                         num_workers=util.num_workers())
        assert map2 is not None, "map should not be None"
        assert map1.volumetric_map.is_identical(map2.volumetric_map)

        # Use a mask as a path
        tissue = FemoralCartilage()
        mask_path = os.path.join(self.data_dirpath,
                                 "test_t2_star_map_mask.nii.gz")
        NiftiWriter().save(mask, mask_path)
        map2 = scan.generate_t2_star_map(tissue,
                                         num_workers=util.num_workers(),
                                         mask_path=mask_path)
        assert map2 is not None, "map should not be None"
        assert map1.volumetric_map.is_identical(map2.volumetric_map)
Пример #8
0
    def test_metadata(self):
        field, field_val = "EchoTime", 4.0

        volume = np.random.rand(10, 20, 30, 40)
        headers = ututils.build_dummy_headers(volume.shape[2:],
                                              {field: field_val})
        mv_no_headers = MedicalVolume(volume, self._AFFINE)
        mv = MedicalVolume(volume, self._AFFINE, headers=headers)

        assert mv_no_headers.headers() is None
        assert mv_no_headers.headers(flatten=True) is None

        with self.assertRaises(ValueError):
            mv.get_metadata("foobar")
        assert mv.get_metadata("foobar", default=0) == 0

        echo_time = mv.get_metadata(field)
        assert echo_time == field_val

        new_val = 5.0
        mv2 = mv.clone(headers=True)
        mv2.set_metadata(field, new_val)
        assert mv.get_metadata(field, type(field_val)) == field_val
        assert mv2.get_metadata(field, type(new_val)) == new_val
        for h in mv2.headers(flatten=True):
            assert h[field].value == new_val

        new_val = 6.0
        mv2 = mv.clone(headers=True)
        mv2[..., 1].set_metadata(field, new_val)
        assert mv2[..., 0].get_metadata(field) == field_val
        assert mv2[..., 1].get_metadata(field) == new_val
        headers = mv2.headers()
        for h in headers[..., 0].flatten():
            assert h[field].value == field_val
        for h in headers[..., 1].flatten():
            assert h[field].value == new_val

        # Set metadata when volume has no headers.
        mv_nh = MedicalVolume(volume, self._AFFINE, headers=None)
        with self.assertRaises(ValueError):
            mv_nh.set_metadata("EchoTime", 40.0)
        with self.assertWarns(UserWarning):
            mv_nh.set_metadata("EchoTime", 40.0, force=True)
        assert mv_nh._headers.shape == (1, ) * len(mv_nh.shape)
        assert mv_nh.get_metadata("EchoTime") == 40.0
        assert mv_nh[:1, :2, :3]._headers.shape == (1, ) * len(mv_nh.shape)
Пример #9
0
    def test_slice_with_headers(self):
        vol = np.stack([np.ones((10, 20, 30)), 2 * np.ones((10, 20, 30))],
                       axis=-1)
        headers = np.stack(
            [
                ututils.build_dummy_headers(vol.shape[2], {"EchoTime": 2}),
                ututils.build_dummy_headers(vol.shape[2], {"EchoTime": 10}),
            ],
            axis=-1,
        )
        mv = MedicalVolume(vol, self._AFFINE, headers=headers)

        mv2 = mv[..., 0]
        assert mv2._headers.shape == (1, 1, 30)
        for h in mv2.headers(flatten=True):
            assert h["EchoTime"].value == 2

        mv2 = mv[..., 1]
        assert mv2._headers.shape == (1, 1, 30)
        for h in mv2.headers(flatten=True):
            assert h["EchoTime"].value == 10

        mv2 = mv[:10, :5, 8:10, :1]
        assert mv2._headers.shape == (1, 1, 2, 1)

        mv2 = mv[:10]
        assert mv2._headers.shape == (1, 1, 30, 2)
        mv2 = mv[:, :10]
        assert mv2._headers.shape == (1, 1, 30, 2)

        mv2 = mv[..., 0:1]
        assert mv2._headers.shape == (1, 1, 30, 1)

        vol = np.stack([np.ones((10, 20, 30)), 2 * np.ones((10, 20, 30))],
                       axis=-1)
        headers = ututils.build_dummy_headers(vol.shape[2],
                                              {"EchoTime": 2})[..., np.newaxis]
        mv = MedicalVolume(vol, self._AFFINE, headers=headers)
        mv1 = mv[..., 0]
        mv2 = mv[..., 1]
        assert mv1._headers.shape == (1, 1, 30)
        assert mv2._headers.shape == (1, 1, 30)
        for h1, h2 in zip(mv1.headers(flatten=True),
                          mv2.headers(flatten=True)):
            assert id(h1) == id(h2)
Пример #10
0
    def test_dtype(self):
        vol = np.ones((10, 20, 30))
        mv = MedicalVolume(vol, self._AFFINE)

        assert mv.volume.dtype == vol.dtype

        mv2 = mv.astype("int32")
        assert id(mv) == id(mv2)
        assert mv2.volume.dtype == np.int32
Пример #11
0
    def test_basic(self):
        vol = np.zeros((10, 10, 10))
        mv = MedicalVolume(vol, self._AFFINE)
        with self.assertRaises(TypeError):
            _ = T2(vol)

        qv = T2(mv)
        qv.add_additional_volume("r2", mv + 1)
        assert np.all(qv.additional_volumes["r2"] == mv + 1)
Пример #12
0
    def test_spacing(self):
        """Test affine matrix with pixel spacing."""
        ornt = ("AP", "SI", "RL")

        spacing = np.random.rand(3) + 0.1  # avoid pixel spacing of 0
        affine = to_affine(ornt, spacing)
        mv = MedicalVolume(np.ones((10, 20, 30)), affine)
        assert mv.orientation == ornt
        assert np.all(np.asarray(mv.pixel_spacing) == spacing)
        assert np.all(np.asarray(mv.scanner_origin) == 0)

        spacing = np.random.rand(1) + 0.1  # avoid pixel spacing of 0
        expected_spacing = np.asarray(list(spacing) + [1.0, 1.0])
        affine = to_affine(ornt, spacing)
        mv = MedicalVolume(np.ones((10, 20, 30)), affine)
        assert mv.orientation == ornt
        assert np.all(np.asarray(mv.pixel_spacing) == expected_spacing)
        assert np.all(np.asarray(mv.scanner_origin) == 0)
Пример #13
0
    def test_to_device(self):
        arr = np.ones((3, 3, 3))
        mv = MedicalVolume(arr, affine=np.eye(4))

        arr2 = to_device(arr, -1)
        assert get_device(arr2) == cpu_device

        mv2 = to_device(mv, -1)
        assert get_device(mv2) == cpu_device
Пример #14
0
def _generate_translated_vols(n=3):
    """Generate mock data that is translated diagonally by 1 pixel."""
    mvs = []
    affine = to_affine(("SI", "AP"), (0.3, 0.3, 0.5))
    for offset in range(n):
        arr = np.zeros((250, 250, 10))
        arr[15 + offset : 35 + offset, 15 + offset : 35 + offset] = 1
        mvs.append(MedicalVolume(arr, affine))
    return mvs
Пример #15
0
    def test_origin(self):
        """Test affine matrix with scanner origin."""
        ornt = ("AP", "SI", "RL")

        origin = np.random.rand(3)
        affine = to_affine(ornt, spacing=None, origin=origin)
        mv = MedicalVolume(np.ones((10, 20, 30)), affine)
        assert mv.orientation == ornt
        assert np.all(np.asarray(mv.pixel_spacing) == 1)
        assert np.all(np.asarray(mv.scanner_origin) == origin)

        origin = np.random.rand(1)
        expected_origin = np.asarray(list(origin) + [0.0, 0.0])
        affine = to_affine(ornt, spacing=None, origin=origin)
        mv = MedicalVolume(np.ones((10, 20, 30)), affine)
        assert mv.orientation == ornt
        assert np.all(np.asarray(mv.pixel_spacing) == 1)
        assert np.all(np.asarray(mv.scanner_origin) == expected_origin)
Пример #16
0
 def test_special_affine(self):
     """Test creation of affine matrix for special cases."""
     # Patient orientation (useful for xray data).
     header = ututils.build_dummy_headers(
         1, fields={"PatientOrientation": ["P", "F"], "PixelSpacing": [0.2, 0.5]}
     )
     affine = to_RAS_affine(header)
     mv = MedicalVolume(np.ones((10, 20, 30)), affine=affine)
     assert mv.orientation == ("SI", "AP", "LR")
     assert mv.pixel_spacing == (0.5, 0.2, 1.0)
     assert mv.scanner_origin == (0.0, 0.0, 0.0)
Пример #17
0
    def test_slice(self):
        mv = MedicalVolume(np.ones((10, 20, 30)), self._AFFINE)
        with self.assertRaises(IndexError):
            mv[4]
        mv_slice = mv[4:5]
        assert mv_slice.shape == (1, 20, 30)

        mv = MedicalVolume(np.ones((10, 20, 30)), self._AFFINE)
        mv[:5, ...] = 2
        assert np.all(mv._volume[:5, ...] == 2) & np.all(mv._volume[5:,
                                                                    ...] == 1)
        assert np.all(mv[:5, ...].volume == 2)

        mv = MedicalVolume(np.ones((10, 20, 30)), self._AFFINE)
        mv2 = mv[:5, ...].clone()
        mv2 += 2
        mv[:5, ...] = mv2
        assert np.all(mv._volume[:5, ...] == 3) & np.all(mv._volume[5:,
                                                                    ...] == 1)
        assert np.all(mv[:5, ...].volume == 3)
Пример #18
0
    def test_reformat_header(self):
        volume = np.random.rand(10, 20, 30, 40)
        headers = ututils.build_dummy_headers(volume.shape[2:])
        mv = MedicalVolume(volume, self._AFFINE, headers=headers)
        new_orientation = tuple(x[::-1] for x in mv.orientation[::-1])

        mv2 = mv.reformat(new_orientation)
        assert mv2._headers.shape == (30, 1, 1, 40)

        mv2 = mv.clone()
        mv2.reformat(new_orientation, inplace=True)
        assert mv2._headers.shape == (30, 1, 1, 40)

        volume = np.random.rand(10, 20, 30, 40)
        headers = ututils.build_dummy_headers((volume.shape[2], 1))
        mv = MedicalVolume(volume, self._AFFINE, headers=headers)
        new_orientation = tuple(x[::-1] for x in mv.orientation[::-1])

        mv2 = mv.reformat(new_orientation)
        assert mv2._headers.shape == (30, 1, 1, 1)
Пример #19
0
    def test_clone(self):
        mv = MedicalVolume(np.random.rand(10, 20, 30), self._AFFINE)
        mv2 = mv.clone()
        assert mv.is_identical(mv2)  # expected identical volumes

        mv = MedicalVolume(
            np.random.rand(10, 20, 30),
            self._AFFINE,
            headers=ututils.build_dummy_headers((1, 1, 30)),
        )
        mv2 = mv.clone(headers=False)
        assert mv.is_identical(mv2)  # expected identical volumes
        assert id(mv.headers(flatten=True)[0]) == id(
            mv2.headers(flatten=True)
            [0]), "headers not cloned, expected same memory address"

        mv3 = mv.clone(headers=True)
        assert mv.is_identical(mv3)  # expected identical volumes
        assert id(mv.headers(flatten=True)[0]) != id(
            mv3.headers(flatten=True)
            [0]), "headers cloned, expected different memory address"
Пример #20
0
def _generate_linear_data(shape=None, x=None, a=None):
    """Generate sample linear data.
    ``a`` is randomly generated in interval [0.1, 1.1).
    """
    if a is None:
        a = np.random.rand(*shape) + 0.1
    else:
        shape = a.shape
    if x is None:
        x = np.asarray([0.5, 1.0, 2.0, 4.0])
    y = [MedicalVolume(_linear(t, a), affine=np.eye(4)) for t in x]
    return x, y, a
Пример #21
0
    def test_to_sitk(self):
        mv = MedicalVolume(np.random.rand(10, 20, 30), self._AFFINE)
        filepath = os.path.join(ututils.TEMP_PATH, "med_vol_to_sitk.nii.gz")
        NiftiWriter().save(mv, filepath)

        expected = sitk.ReadImage(filepath)

        nr = NiftiReader()
        mv = nr.load(filepath)
        img = mv.to_sitk()

        assert np.allclose(sitk.GetArrayViewFromImage(img),
                           sitk.GetArrayViewFromImage(expected))
        assert img.GetSize() == mv.shape
        assert np.allclose(img.GetOrigin(), expected.GetOrigin())
        assert img.GetSpacing() == img.GetSpacing()
        assert img.GetDirection() == expected.GetDirection()

        mv = MedicalVolume(np.zeros((10, 20, 1, 3)), affine=self._AFFINE)
        img = mv.to_sitk(vdim=-1)
        assert np.all(sitk.GetArrayViewFromImage(img) == 0)
        assert img.GetSize() == (10, 20, 1)
Пример #22
0
    def test_complex(self):
        ornt = ("AP", "SI", "RL")
        spacing = (0.5, 0.7)
        origin = (100, -54)

        expected_spacing = np.asarray(list(spacing) + [1.0])
        expected_origin = np.asarray(list(origin) + [0.0])

        affine = to_affine(ornt, spacing=spacing, origin=origin)
        mv = MedicalVolume(np.ones((10, 20, 30)), affine)
        assert mv.orientation == ornt
        assert np.all(np.asarray(mv.pixel_spacing) == expected_spacing)
        assert np.all(np.asarray(mv.scanner_origin) == expected_origin)
Пример #23
0
def generate_monoexp_data(shape=None, x=None, a=1.0, b=None):
    """Generate sample monoexponetial data.
    ``a=1.0``, ``b`` is randomly generated in interval [0.1, 1.1).

    The equation is :math:`y =  a * \\exp (b*x)`.
    """
    if b is None:
        b = np.random.rand(*shape) + 0.1
    else:
        shape = b.shape
    if x is None:
        x = np.asarray([0.5, 1.0, 2.0, 4.0])
    y = [MedicalVolume(monoexponential(t, a, b), affine=np.eye(4)) for t in x]
    return x, y, a, b
Пример #24
0
    def test_basic(self):
        """Basic transposes and flips in RAS+ coordinate system."""
        orientations = [
            ("LR", "PA", "IS"),  # standard RAS+
            ("RL", "AP", "SI"),  # flipped
            ("IS", "LR", "PA"),  # transposed
            ("AP", "SI", "RL"),  # transposed + flipped
        ]

        for ornt in orientations:
            affine = to_affine(ornt)
            mv = MedicalVolume(np.ones((10, 20, 30)), affine)
            assert mv.orientation == ornt
            assert np.all(np.asarray(mv.pixel_spacing) == 1)
            assert np.all(np.asarray(mv.scanner_origin) == 0)
Пример #25
0
    def test_save(self):
        filepath = get_testdata_file("MR_small.dcm")
        dr = DicomReader(group_by=None)
        mv_base = dr.load(filepath)[0]

        out_dir = os.path.join(self.data_dirpath, "test_save_sort_by")
        dw = DicomWriter()
        dw.save(mv_base, out_dir, sort_by="InstanceNumber")
        mv2 = dr.load(filepath)[0]
        assert mv2.is_identical(mv_base)

        out_dir = os.path.join(self.data_dirpath, "test_save_no_headers")
        mv = MedicalVolume(np.ones((10, 10, 10)), np.eye(4))
        dw = DicomWriter()
        with self.assertRaises(ValueError):
            dw.save(mv, out_dir)
Пример #26
0
def _generate_affine(shape=None, x=None, a=None, b=1.0, as_med_vol=False):
    """Generate data of the form :math:`y = a*x + b`."""
    if a is None:
        a = np.random.rand(*shape) + 0.1
    else:
        shape = a.shape
    if x is None:
        x = np.asarray([0.5, 1.0, 2.0, 4.0])
    if b is None:
        b = np.random.rand(*shape)

    if as_med_vol:
        y = [MedicalVolume(a * t + b, affine=np.eye(4)) for t in x]
    else:
        y = [a * t + b for t in x]
    return x, y, a, b
Пример #27
0
    def __dilate_mask__(
        self,
        mask_path: str,
        temp_path: str,
        dil_rate: float = preferences.mask_dilation_rate,
        dil_threshold: float = preferences.mask_dilation_threshold,
    ):
        """Dilate mask using gaussian blur and write to disk to use with Elastix.

        Args:
            mask_path (str | MedicalVolume): File path for mask or mask to use to use as
                focus points for registration. Mask must be binary.
            temp_path (str): Directory path to store temporary data.
            dil_rate (`float`, optional): Dilation rate (sigma).
                Defaults to ``preferences.mask_dilation_rate``.
            dil_threshold (`float`, optional): Threshold to binarize dilated mask.
                Must be between [0, 1]. Defaults to ``preferences.mask_dilation_threshold``.

        Returns:
            str: File path of dilated mask.

        Raises:
            FileNotFoundError: If `mask_path` not valid file.
            ValueError: If `dil_threshold` not in range [0, 1].
        """

        if dil_threshold < 0 or dil_threshold > 1:
            raise ValueError("'dil_threshold' must be in range [0, 1]")

        if isinstance(mask_path, MedicalVolume):
            mask = mask_path
        elif os.path.isfile(mask_path):
            mask = fio_utils.generic_load(mask_path, expected_num_volumes=1)
        else:
            raise FileNotFoundError("File {} not found".format(mask_path))

        dilated_mask = (sni.gaussian_filter(np.asarray(mask.volume,
                                                       dtype=np.float32),
                                            sigma=dil_rate) > dil_threshold)
        fixed_mask = np.asarray(dilated_mask, dtype=np.int8)
        fixed_mask_filepath = os.path.join(io_utils.mkdirs(temp_path),
                                           "dilated-mask.nii.gz")

        dilated_mask_volume = MedicalVolume(fixed_mask, affine=mask.affine)
        dilated_mask_volume.save_volume(fixed_mask_filepath)

        return fixed_mask_filepath
Пример #28
0
    def test_hdf5(self):
        shape = (10, 20, 30)
        volume = np.reshape(list(range(np.product(shape))), shape)
        hdf5_file = os.path.join(self._TEMP_PATH, "unittest.h5")

        with h5py.File(hdf5_file, "w") as f:
            f.create_dataset("volume", data=volume)
        f = h5py.File(hdf5_file, "r")

        mv = MedicalVolume(f["volume"], np.eye(4))
        assert mv.device == Device("cpu")
        assert mv.dtype == f["volume"].dtype

        mv2 = mv[:, :, :1]
        assert np.all(mv2.volume == volume[:, :, :1])
        assert mv2.device == Device("cpu")
        assert mv2.dtype == volume.dtype
Пример #29
0
def stack(xs, axis: int = -1):
    """Stack medical images across non-spatial dimensions.

    Images will be auto-oriented to the orientation of the first medical volume.

    Args:
        xs (array-like[MedicalVolume]): 1D array-like of aligned medical images to stack.
        axis (int, optional): Axis to stack along.

    Returns:
        MedicalVolume: The stacked medical image.

    Note:
        Unlike NumPy, the default stacking axis is ``-1``.

    Note:
        Headers are not set unless all inputs have headers of the same
        shape. This functionality may change in the future.

    Examples:
        >>> mv = dm.MedicalVolume([[[0,1,2,3]]], affine=np.eye(4))
        >>> np.stack([mv, mv], axis=-1)  # Stacks along last axis.
        MedicalVolume(volume=[[[[0 0], [1 1], [2 2], [3 3]]]])
    """
    if not isinstance(axis, int):
        raise TypeError(f"'{type(axis)}' cannot be interpreted as int")

    xs = [x.reformat(xs[0].orientation) for x in xs]
    affine = xs[0].affine
    for x in xs[1:]:
        assert x.is_same_dimensions(xs[0], err=True)
    try:
        axis = _to_positive_axis(axis, len(xs[0].shape), grow=True, invalid_axis="spatial")
    except ValueError:
        raise ValueError(f"Cannot stack across spatial dimension (axis={axis})")
    assert axis >= 0

    vol = np.stack([x.volume for x in xs], axis=axis)
    headers = [x.headers() for x in xs]
    if any(x is None for x in headers):
        headers = None
    else:
        headers = np.stack(headers, axis=axis)

    return MedicalVolume(vol, affine, headers=headers)
Пример #30
0
    def test_save_load_data(self):
        out_dir = os.path.join(ututils.TEMP_PATH, "quant_val_save")
        vol = np.zeros((10, 10, 10))
        mv = MedicalVolume(vol, self._AFFINE)

        qv = T2(mv)
        qv.add_additional_volume("r2", mv + 1)

        qv.save_data(out_dir, ImageDataFormat.nifti)
        assert os.path.isfile(os.path.join(out_dir, "t2", "t2.nii.gz"))
        assert os.path.isfile(os.path.join(out_dir, "t2", "t2-r2.nii.gz"))

        with self.assertWarns(UserWarning):
            qv.save_data(out_dir, ImageDataFormat.dicom)

        qv2 = T2()
        qv2.load_data(out_dir)
        assert qv2.volumetric_map.is_identical(qv.volumetric_map)