def test_add_complex_noise_filter_wrong_input_type_error():
    """Check a FilterInputValidationError is raised when the inputs
    to the add commplex noise filter are incorrect or missing"""
    noise_filter = AddComplexNoiseFilter()
    noise_filter.add_input("snr", 1)
    with pytest.raises(FilterInputValidationError):
        noise_filter.run()  # image not defined
    noise_filter.add_input("image", 1)
    with pytest.raises(FilterInputValidationError):
        noise_filter.run()  # image wrong type

    noise_filter = AddComplexNoiseFilter()
    noise_filter.add_input(
        "image", NumpyImageContainer(image=np.zeros(TEST_VOLUME_DIMENSIONS))
    )
    with pytest.raises(FilterInputValidationError):
        noise_filter.run()  # snr not defined
    noise_filter.add_input("snr", "str")
    with pytest.raises(FilterInputValidationError):
        noise_filter.run()  # snr wrong type

    noise_filter = AddComplexNoiseFilter()
    noise_filter.add_input(
        "image", NumpyImageContainer(image=np.zeros(TEST_VOLUME_DIMENSIONS))
    )
    noise_filter.add_input("snr", 1)
    noise_filter.add_input("reference_image", 1)
    with pytest.raises(FilterInputValidationError):
        noise_filter.run()  # reference_image wrong type
Example #2
0
def test_image_container_metadata_init():
    """ Test the metadata initialisation on the Image Container classes """
    numpy = NumpyImageContainer(image=np.ones((1, 1, 1)), affine=np.eye(4))
    assert numpy.metadata == {}
    nifti = NiftiImageContainer(
        nifti_img=nib.Nifti1Image(np.ones((1, 1, 1)), affine=np.eye(4)))
    assert nifti.metadata == {}

    numpy = NumpyImageContainer(image=np.ones((1, 1, 1)),
                                affine=np.eye(4),
                                metadata={"foo": "bar"})
    assert numpy.metadata == {"foo": "bar"}
    nifti = NiftiImageContainer(
        nifti_img=nib.Nifti1Image(np.ones((1, 1, 1)), affine=np.eye(4)),
        metadata={"bar": "foo"},
    )
    assert nifti.metadata == {"bar": "foo"}

    with pytest.raises(TypeError):
        NumpyImageContainer(image=np.ones((1, 1, 1)),
                            affine=np.eye(4),
                            metadata=1)  # non-dict
    with pytest.raises(TypeError):
        NiftiImageContainer(
            nifti_img=nib.Nifti1Image(np.ones((1, 1, 1)), affine=np.eye(4)),
            metadata="foobar",  # non-dict
        )
def casl_input_fixture() -> dict:
    """Creates test data for testing the CASL/pCASL model"""
    np.random.seed(0)
    return {
        "perfusion_rate":
        NumpyImageContainer(
            image=np.random.normal(60, 10, TEST_VOLUME_DIMENSIONS)),
        "transit_time":
        TEST_IMAGE_ONES,
        "t1_tissue":
        NumpyImageContainer(image=1.4 * np.ones(TEST_VOLUME_DIMENSIONS)),
        "m0":
        TEST_IMAGE_ONES,
        "label_type":
        CASL,
        "label_duration":
        1.8,
        "signal_time":
        3.6,
        "label_efficiency":
        0.85,
        "lambda_blood_brain":
        0.9,
        "t1_arterial_blood":
        1.6,
    }
def mock_data_fixture() -> dict:
    """ creates valid mock test data  """
    np.random.seed(0)
    return {
        "t1":
        NumpyImageContainer(
            image=np.random.normal(1.4, 0.1, TEST_VOLUME_DIMENSIONS)),
        "t2":
        NumpyImageContainer(
            image=np.random.normal(0.1, 0.01, TEST_VOLUME_DIMENSIONS)),
        "t2_star":
        NumpyImageContainer(
            image=np.random.normal(0.7, 0.01, TEST_VOLUME_DIMENSIONS)),
        "m0":
        NumpyImageContainer(
            image=np.random.normal(100, 1, TEST_VOLUME_DIMENSIONS)),
        "mag_enc":
        NumpyImageContainer(
            image=np.random.normal(1, 0.1, TEST_VOLUME_DIMENSIONS)),
        "acq_contrast":
        "ge",
        "echo_time":
        0.01,
        "repetition_time":
        1.0,
        "excitation_flip_angle":
        90.0,
    }
def test_add_complex_noise_filter_with_mock_data():
    """ Test the add complex noise filter with some data """
    signal_level = 100.0
    snr = 100.0
    seed = 1234
    np.random.seed(seed)
    image = np.random.normal(signal_level, 10, TEST_VOLUME_DIMENSIONS)
    reference_image = image

    np.random.seed(seed)
    image_with_noise = add_complex_noise_function(image, reference_image, snr)

    image_container = NumpyImageContainer(image=image)
    reference_container = NumpyImageContainer(image=reference_image)
    noise_filter_1 = AddComplexNoiseFilter()
    noise_filter_1.add_input("image", image_container)
    noise_filter_1.add_input("snr", snr)

    # noise filter 2, copy of noise_filter_1
    noise_filter_2 = deepcopy(noise_filter_1)

    # noise filter 3 with reference
    noise_filter_3 = deepcopy(noise_filter_2)
    noise_filter_3.add_input("reference_image", reference_container)

    noise_filter_1.run()
    # reset RNG
    np.random.seed(seed)
    noise_filter_2.run()

    # reset RNG
    np.random.seed(seed)
    noise_filter_3.run()

    # Compare output of noise_filter_1 with image_with_noise
    # seed is different so they should not be equal
    with numpy.testing.assert_raises(AssertionError):
        numpy.testing.assert_array_equal(
            noise_filter_1.outputs["image"].image, image_with_noise
        )

    # Compare output of noise_filter_2 with image_with_noise
    # seed is the same so they should be equal
    numpy.testing.assert_array_equal(
        noise_filter_2.outputs["image"].image, image_with_noise
    )

    # Compare output of noise_filter_3 with image_with_noise
    # seed is the same so they should be equal
    numpy.testing.assert_array_equal(
        noise_filter_3.outputs["image"].image, image_with_noise
    )

    # Calculate the SNR of the images using the subtraction method
    calculated_snr = simulate_dual_image_snr_measurement_function(image_container, snr)
    print(f"calculated snr = {calculated_snr}, desired snr = {snr}")
    # This should be almost equal to the desired snr
    numpy.testing.assert_array_almost_equal(calculated_snr, snr, 0)
Example #6
0
def test_numpy_image_container_time_step_seconds_setter(
    numpy_image_container: NumpyImageContainer, ):
    """ Test that the correct time step is get on a NumpyImageContainer """

    numpy_image_container.time_step_seconds = 10.0
    numpy_image_container.time_units = UNITS_MILLISECONDS

    np.testing.assert_almost_equal(numpy_image_container.time_step_seconds,
                                   10000.0)
Example #7
0
def test_image_container_metadata_get_set():
    """ Test the Image Container metadata getting and setting """
    numpy = NumpyImageContainer(image=np.ones((1, 1, 1)), affine=np.eye(4))
    assert numpy.metadata == {}
    numpy.metadata = {"one": 1, "two": 2}
    assert numpy.metadata == {"one": 1, "two": 2}
    numpy.metadata["three"] = 3
    assert numpy.metadata == {"one": 1, "two": 2, "three": 3}
    with pytest.raises(TypeError):
        numpy.metadata = "not a dict"
Example #8
0
def test_numpy_image_container_time_step_seconds_getter(
    numpy_image_container: NumpyImageContainer, ):
    """ Test that the correct time step is returned from a NumpyImageContainer """

    # NIFTI header has seconds in xytz_units
    assert numpy_image_container.time_step_seconds == 2000.0
    numpy_image_container.time_units = UNITS_MILLISECONDS
    assert numpy_image_container.time_step_seconds == 2e6
    numpy_image_container.time_units = UNITS_MICROSECONDS
    assert numpy_image_container.time_step_seconds == 2e9
Example #9
0
def test_numpy_image_container_voxel_size_mm_setter(
    numpy_image_container: NumpyImageContainer, ):
    """ Test that the correct voxel size is set on a NumpyImageContainer """
    # voxel size is 2x2x2mm
    numpy_image_container.voxel_size_mm = [3.0, 2.0, 1.0]
    numpy_image_container.space_units = UNITS_METERS

    # Just test that we have 3x2x1m reported in mm
    np.testing.assert_array_almost_equal(numpy_image_container.voxel_size_mm,
                                         np.array([3000.0, 2000.0, 1000.0]))
Example #10
0
def test_image_container_spatial_domain_initialisation():
    """Check that passing a string not in SPATIAL_DOMAIN or INVERSE_DOMAIN to
    data_domain raises an exception ( and vice versa )"""
    with pytest.raises(ValueError):
        NumpyImageContainer(image=np.zeros((3, 3, 3)), data_domain="foobar")

    image_container = NumpyImageContainer(image=np.zeros((3, 3, 3)),
                                          data_domain=SPATIAL_DOMAIN)  # OK
    assert image_container.data_domain == SPATIAL_DOMAIN

    image_container = NumpyImageContainer(image=np.zeros((3, 3, 3)),
                                          data_domain=INVERSE_DOMAIN)  # OK
    assert image_container.data_domain == INVERSE_DOMAIN
Example #11
0
def test_numpy_image_container_voxel_size_mm_getter(
    numpy_image_container: NumpyImageContainer, ):
    """ Test that the correct voxel size is returned from a NumpyImageContainer """

    # NIFTI header has mm in xyzt_units
    np.testing.assert_array_almost_equal(numpy_image_container.voxel_size_mm,
                                         np.array([2.0, 2.0, 2.2]))
    numpy_image_container.space_units = UNITS_METERS
    np.testing.assert_array_almost_equal(numpy_image_container.voxel_size_mm,
                                         np.array([2000.0, 2000.0, 2200.0]))
    numpy_image_container.space_units = UNITS_MICRONS
    np.testing.assert_array_almost_equal(numpy_image_container.voxel_size_mm,
                                         np.array([2.0e-3, 2.0e-3, 2.2e-3]))
def test_range_exclusive_validator_image_container():
    """ Test the exclusive validator with an image container """
    validator = range_exclusive_validator(-1, 1)
    assert str(validator) == "Value(s) must be between -1 and 1 (exclusive)"

    image_container = NumpyImageContainer(
        image=np.array([[-0.5, 0.2], [0.1, -0.9]]))
    assert validator(image_container)
    image_container = NumpyImageContainer(
        image=np.array([[-1.0, 0.2], [0.1, -0.9]]))
    assert not validator(image_container)
    image_container = NumpyImageContainer(
        image=np.array([[-0.5, 0.2], [1, -0.9]]))
    assert not validator(image_container)
def test_greater_than_equal_to_validator_image_container():
    """ Test the greater than equal to validator with an image container """
    validator = greater_than_equal_to_validator(1.5)
    assert str(validator) == "Value(s) must be greater than or equal to 1.5"

    image_container = NumpyImageContainer(
        image=np.array([[-0.5, 0.2], [0.1, -0.9]]))
    assert not validator(image_container)
    image_container = NumpyImageContainer(
        image=np.array([[1.5, 2.2], [1.7, 90]]))
    assert validator(image_container)
    image_container = NumpyImageContainer(
        image=np.array([[1.51, 2.2], [1.7, 90]]))
    assert validator(image_container)
Example #14
0
def test_numpy_image_container_clone():
    """ Check that the numpy image container is cloned correctly """
    # Use none of the default parameters
    image_container = NumpyImageContainer(
        image=np.ones(shape=(3, 4, 5)),
        affine=np.eye(4) * 2,
        space_units=UNITS_METERS,
        time_units=UNITS_MICROSECONDS,
        voxel_size=np.array([1, 2, 3]),
        time_step=0.5,
        data_domain=INVERSE_DOMAIN,
    )

    cloned_image_container = image_container.clone()
    general_image_container_clone_tests(image_container,
                                        cloned_image_container)
def test_isinstance_validator():
    """ Test the isinstance_validator """
    validator = isinstance_validator(str)
    assert str(validator) == "Value must be of type str"
    assert validator("foo")
    assert not validator([])
    assert not validator({})
    assert not validator(1)
    assert not validator(2.0)

    validator = isinstance_validator(int)
    assert str(validator) == "Value must be of type int"
    assert validator(1)
    assert not validator("foo")
    assert not validator(1.0)
    assert not validator([])
    assert not validator({})

    validator = isinstance_validator(BaseImageContainer)
    assert str(validator) == "Value must be of type BaseImageContainer"
    image_container = NumpyImageContainer(
        image=np.array([[-0.5, 0.2], [0.1, -0.9]]))
    assert validator(image_container)
    assert not validator("foo")
    assert not validator(1)
    assert not validator([])

    validator = isinstance_validator((float, int))
    assert str(validator) == "Value must be of type float or int"
    assert validator(1.0)
    assert validator(2)
    assert not validator([1])
    assert not validator([1, 2])
    assert not validator("foo")
    assert not validator(["foo", "bar"])
Example #16
0
def test_numpy_image_container_image_properties(
    numpy_image_container: NumpyImageContainer, ):
    """ Test the numpy image container image setter/getter """
    new_image = np.ones(shape=(4, 4, 4)) * 10
    numpy_image_container.image = new_image
    numpy.testing.assert_array_equal(numpy_image_container.image, new_image)

    assert numpy_image_container.image.shape == (4, 4, 4)
Example #17
0
def test_image_container_unexpected_arguments():
    """ Check that passing unexpected arguments raises an error """
    with pytest.raises(TypeError):
        NumpyImageContainer(image=np.zeros((3, 3, 3)), unexpected="test")
    with pytest.raises(TypeError):
        NiftiImageContainer(nib.Nifti1Pair(np.zeros((3, 3, 3)),
                                           affine=np.eye(4)),
                            unexpected="test")
def complex_image_container_function() -> NumpyImageContainer:
    """ Creates a NumpyImageContainer with mock real data """
    signal_level = 100.0
    np.random.seed(0)
    image = np.random.normal(
        signal_level / np.sqrt(2), 10,
        (32, 32, 32)) + 1j * np.random.normal(signal_level / np.sqrt(2), 10,
                                              (32, 32, 32))
    return NumpyImageContainer(image=image)
def test_mri_signal_timecourse(
    t1: float,
    t2: float,
    m0: float,
    t2_star: float,
    acq_contrast: str,
    echo_time: float,
    repetition_time: float,
    expected: float,
):
    """Tests the MriSignalFilter with timecourse data that is generated at multiple echo times

    Args:
        t1 (float): longitudinal relaxation time, s
        t2 (float): transverse relaxation time, s
        m0 (float): equilibrium magnetisation
        t2_star (float): transverse relaxation time inc. time invariant fields, s
        acq_contrast (str): signal model to use: 'ge' or 'se'
        echo_time (float): array of echo times, s
        repetition_time (float): repeat time, s
        expected (float): Array of expected values that the MriSignalFilter should generate
        Should be the same size and shape as 'echo_time'
    """
    mri_signal_timecourse = np.ndarray(echo_time.shape)
    for idx, te in np.ndenumerate(echo_time):
        params = {
            "t1": NumpyImageContainer(image=np.full((1, 1, 1), t1)),
            "t2": NumpyImageContainer(image=np.full((1, 1, 1), t2)),
            "t2_star": NumpyImageContainer(image=np.full((1, 1, 1), t2_star)),
            "m0": NumpyImageContainer(image=np.full((1, 1, 1), m0)),
            "mag_enc": NumpyImageContainer(image=np.zeros((1, 1, 1))),
            "acq_contrast": acq_contrast,
            "echo_time": te,
            "repetition_time": repetition_time,
            "excitation_flip_angle": 90.0,
        }

        mri_signal_filter = MriSignalFilter()
        mri_signal_filter = add_multiple_inputs_to_filter(
            mri_signal_filter, params)
        mri_signal_filter.run()
        mri_signal_timecourse[idx] = mri_signal_filter.outputs["image"].image
    # arrays should be equal to 9 decimal places
    numpy.testing.assert_array_almost_equal(mri_signal_timecourse, expected, 9)
def test_gkm_timecourse(
    f: float,
    delta_t: float,
    timepoints: np.array,
    label_type: str,
    tau: float,
    alpha: str,
    expected: np.ndarray,
):
    # pylint: disable=too-many-arguments
    """Tests the GkmFilter with timecourse data that is generated at multiple 'signal_time's.

    Arguments:
        f (float): Perfusion Rate, ml/100g/min
        delta_t (float): Bolus arrival time/transit time, seconds
        timepoints (np.array): Array of time points that the signal is generated at, seconds
        label_type (str): GKM model to use: 'PASL' or 'CASL'/'pCASL'
        tau (float): Label duration, seconds
        alpha (str): Label efficiency, 0 to 1
        expected (np.ndarray): Array of expected values that the GkmFilter should generate. Should
        be the same size and shape as `timepoints`.
    """
    delta_m_timecourse = np.ndarray(timepoints.shape)
    for idx, t in np.ndenumerate(timepoints):
        params = {
            "perfusion_rate":
            NumpyImageContainer(image=f * np.ones((1, 1, 1))),
            "transit_time":
            NumpyImageContainer(image=delta_t * np.ones((1, 1, 1))),
            "t1_tissue": NumpyImageContainer(image=1.4 * np.ones((1, 1, 1))),
            "m0": 1.0,
            "label_type": label_type,
            "label_duration": tau,
            "signal_time": t,
            "label_efficiency": alpha,
            "lambda_blood_brain": 0.9,
            "t1_arterial_blood": 1.6,
        }
        gkm_filter = GkmFilter()
        gkm_filter = add_multiple_inputs_to_filter(gkm_filter, params)
        gkm_filter.run()
        delta_m_timecourse[idx] = gkm_filter.outputs["delta_m"].image
    # arrays should be equal to 9 decimal places
    numpy.testing.assert_array_almost_equal(delta_m_timecourse, expected, 10)
def test_gkm_filter_pasl(pasl_input):
    """Test the GkmFilter for Pulsed ASL"""
    gkm_filter = GkmFilter()
    gkm_filter = add_multiple_inputs_to_filter(gkm_filter, pasl_input)
    gkm_filter.run()

    delta_m = gkm_pasl_function(pasl_input)
    numpy.testing.assert_array_equal(delta_m,
                                     gkm_filter.outputs["delta_m"].image)

    # Set 'signal_time' to be less than the transit time so that the bolus
    # has not arrived yet
    pasl_input["signal_time"] = 0.5
    assert (pasl_input["signal_time"] < pasl_input["transit_time"].image).all()
    gkm_filter = GkmFilter()
    gkm_filter = add_multiple_inputs_to_filter(gkm_filter, pasl_input)
    gkm_filter.run()

    # check the m0 is added to the metadata, as all values of m0 for the image are the same
    gkm_filter.outputs["delta_m"].metadata["m0"] = 1.0
    # 'delta_m' should be all zero
    numpy.testing.assert_array_equal(
        gkm_filter.outputs["delta_m"].image,
        np.zeros(gkm_filter.outputs["delta_m"].shape),
    )

    # create input images with some zeros in to test that divide-by-zero is not encountered at
    # runtime
    image_with_some_zeros = numpy.concatenate((np.ones(
        (32, 32, 16)), np.zeros((32, 32, 16))),
                                              axis=2)
    pasl_input["perfusion_rate"] = NumpyImageContainer(image=60 *
                                                       image_with_some_zeros)
    pasl_input["transit_time"] = NumpyImageContainer(
        image=image_with_some_zeros)
    pasl_input["m0"] = NumpyImageContainer(image=image_with_some_zeros)
    pasl_input["t1_tissue"] = NumpyImageContainer(image=1.4 *
                                                  image_with_some_zeros)
    pasl_input["lambda_blood_brain"] = 0.0
    pasl_input["t1_arterial_blood"] = 0.0

    gkm_filter = GkmFilter()
    gkm_filter = add_multiple_inputs_to_filter(gkm_filter, pasl_input)
    gkm_filter.run()
Example #22
0
def test_numpy_to_nifti(numpy_image_container: NumpyImageContainer):
    """ Check the as_nifti functionality works correctly on a nifti container """
    for new_image_container in [
            numpy_image_container.as_numpy(),
            numpy_image_container.as_nifti(),
    ]:
        np.testing.assert_array_equal(new_image_container.image,
                                      numpy_image_container.image)
        np.testing.assert_array_equal(new_image_container.affine,
                                      numpy_image_container.affine)
        assert new_image_container.data_domain == numpy_image_container.data_domain
        assert new_image_container.image_type == numpy_image_container.image_type
        assert new_image_container.metadata == numpy_image_container.metadata
        assert new_image_container.space_units == numpy_image_container.space_units
        assert new_image_container.time_units == numpy_image_container.time_units
        np.testing.assert_array_equal(new_image_container.voxel_size_mm,
                                      numpy_image_container.voxel_size_mm)
        assert (new_image_container.time_step_seconds ==
                numpy_image_container.time_step_seconds)
def test_fourier_filters_fft_validation():
    """Check that running an fft on an INVERSE_DOMAIN image raises a
    FilterInputValidationError"""
    image_data = np.random.normal(0, 1, TEST_VOLUME_DIMENSIONS)
    image_container = NumpyImageContainer(image=image_data,
                                          data_domain=INVERSE_DOMAIN)

    fft_filter = FftFilter()
    fft_filter.add_input("image", image_container)
    with pytest.raises(FilterInputValidationError):
        fft_filter.run()
Example #24
0
def test_invert_image_filter_with_numpy():
    """ Test the invert image filter works correctly with NumpyImageContainer"""
    invert_image_filter = InvertImageFilter()
    array = np.ones(shape=(3, 3, 3, 1), dtype=np.float32)

    nifti_image_container = NumpyImageContainer(image=array)

    invert_image_filter.add_input("image", nifti_image_container)
    invert_image_filter.run()

    assert_array_equal(invert_image_filter.outputs["image"].image, -array)
def test_add_noise_filter_validate_inputs():
    """Check a FilterInputValidationError is raised when the inputs
    to the add commplex noise filter are incorrect or missing"""
    noise_filter = AddNoiseFilter()
    noise_filter.add_input("snr", 1)
    with pytest.raises(FilterInputValidationError):
        noise_filter.run()  # image not defined
    noise_filter.add_input("image", 1)
    with pytest.raises(FilterInputValidationError):
        noise_filter.run()  # image wrong type

    noise_filter = AddNoiseFilter()
    noise_filter.add_input("image",
                           NumpyImageContainer(image=np.zeros((32, 32, 32))))
    with pytest.raises(FilterInputValidationError):
        noise_filter.run()  # snr not defined
    noise_filter.add_input("snr", "str")
    with pytest.raises(FilterInputValidationError):
        noise_filter.run()  # snr wrong type

    noise_filter = AddNoiseFilter()
    noise_filter.add_input("image",
                           NumpyImageContainer(image=np.zeros((32, 32, 32))))
    noise_filter.add_input("snr", 1)
    noise_filter.add_input("reference_image", 1)
    with pytest.raises(FilterInputValidationError):
        noise_filter.run()  # reference_image wrong type

    noise_filter = AddNoiseFilter()
    noise_filter.add_input("image",
                           NumpyImageContainer(image=np.zeros((32, 32, 32))))
    noise_filter.add_input("snr", 1)
    noise_filter.add_input("reference_image",
                           NumpyImageContainer(image=np.zeros((32, 32, 31))))
    with pytest.raises(FilterInputValidationError):
        noise_filter.run()  # reference_image wrong shape
Example #26
0
def numpy_image_container() -> NumpyImageContainer:
    """ Creates and returns a NumpyImageContainer for testing """
    return NumpyImageContainer(
        image=np.ones((2, 3, 4, 5, 6)),
        affine=np.array([
            [-2.0, 0.0, 0.0, 117.86],
            [-0.0, 1.97, -0.36, -35.72],
            [0.0, 0.32, 2.17, -7.25],
            [0.0, 0.0, 0.0, 1.0],
        ]),
        space_units=UNITS_MILLIMETERS,
        time_units=UNITS_SECONDS,
        voxel_size=[2.0, 2.0, 2.2],
        time_step=2000.0,
    )
def test_add_complex_noise_filter_snr_zero():
    """ Checks that the output image is equal to the input image when snr=0 """
    signal_level = 100.0
    np.random.seed(0)
    image = np.random.normal(signal_level, 10, (32, 32, 32))
    image_container = NumpyImageContainer(image=image)

    # calculate using the filter
    add_complex_noise_filter = AddComplexNoiseFilter()
    add_complex_noise_filter.add_input("image", image_container)
    add_complex_noise_filter.add_input("snr", 0.0)
    add_complex_noise_filter.run()

    # image_with_noise and image_with_noise_container.image should be equal
    numpy.testing.assert_array_equal(
        image_container.image, add_complex_noise_filter.outputs["image"].image
    )
def test_fourier_filters_with_mock_data():
    """Test the fft filter with some data + its discrete fourier transform"""
    # Create a 3D numpy image of normally distributed noise
    # fft to obtain k-space data, then ifft that to go back
    # to the image
    image_data = np.random.normal(0, 1, TEST_VOLUME_DIMENSIONS)
    kspace_data = np.fft.fftn(image_data)
    inverse_transformed_image_data = np.fft.ifftn(kspace_data)
    image_container = NumpyImageContainer(image=image_data)

    fft_filter = FftFilter()
    fft_filter.add_input("image", image_container)
    ifft_filter = IfftFilter()
    ifft_filter.add_parent_filter(parent=fft_filter)

    # Should run without error
    ifft_filter.run()

    # Check that the output of the fft_filter is in the INVERSE_DOMAIN
    assert fft_filter.outputs["image"].data_domain == INVERSE_DOMAIN

    # Check the output image is labelled as COMPLEX
    assert fft_filter.outputs["image"].image_type == COMPLEX_IMAGE_TYPE

    # Compare the fft_filter output image with kspace_data
    numpy.testing.assert_array_equal(fft_filter.outputs["image"].image,
                                     kspace_data)

    # Check that the output of the ifft_filter is in the SPATIAL_DOMAIN
    assert ifft_filter.outputs["image"].data_domain == SPATIAL_DOMAIN

    # Check the output image is labelled as COMPLEX
    assert ifft_filter.outputs["image"].image_type == COMPLEX_IMAGE_TYPE

    # Compare the ifft_filter_output image with inverse_transformed_image_data
    numpy.testing.assert_array_equal(ifft_filter.outputs["image"].image,
                                     inverse_transformed_image_data)
def test_mri_signal_timecourse_inversion_recovery(
    t1: float,
    t2: float,
    m0: float,
    t2_star: float,
    echo_time: float,
    repetition_time: float,
    flip_angle: float,
    inversion_angle: float,
    inversion_time: float,
    expected: float,
):
    """Tests the MriSignalFilter inversion recovery signal over a range of TI's.

    :param t1: longitudinal relaxation time, s
    :type t1: float
    :param t2: transverse relaxation time, s
    :type t2: float
    :param m0: equilibrium magnetisation
    :type m0: float
    :param t2_star: transverse relaxation time inc. time invariant fields, s
    :type t2_star: float


    :param echo_time: the echo time, s
    :type echo_time: float
    :param repetition_time: the repetition time, s
    :type repetition_time: float
    :param flip_angle: the excitation pulse flip angle, degrees
    :type flip_angle: float
    :param inversion_angle: the inversion pulse flip angle, degrees
    :type inversion_angle: float
    :param inversion_time: array of durations between the inversion pulse and excitation pulse, s
    :type inversion_time: float
    :param expected: array of expected valuesm, same length as `inversion_time`
    :type expected: float

    """

    mri_signal_timecourse = np.ndarray(inversion_time.shape)
    for idx, ti in np.ndenumerate(inversion_time):
        params = {
            "t1": NumpyImageContainer(image=np.full((1, 1, 1), t1)),
            "t2": NumpyImageContainer(image=np.full((1, 1, 1), t2)),
            "t2_star": NumpyImageContainer(image=np.full((1, 1, 1), t2_star)),
            "m0": NumpyImageContainer(image=np.full((1, 1, 1), m0)),
            "acq_contrast": "ir",
            "echo_time": echo_time,
            "repetition_time": repetition_time,
            "excitation_flip_angle": flip_angle,
            "inversion_flip_angle": inversion_angle,
            "inversion_time": ti,
        }

        mri_signal_filter = MriSignalFilter()
        mri_signal_filter = add_multiple_inputs_to_filter(
            mri_signal_filter, params)
        mri_signal_filter.run()
        mri_signal_timecourse[idx] = mri_signal_filter.outputs["image"].image
    # arrays should be equal to 9 decimal places
    numpy.testing.assert_array_almost_equal(mri_signal_timecourse, expected, 9)
"""GkmFilter tests"""
# pylint: disable=duplicate-code

from copy import deepcopy
import pytest
import numpy as np
import numpy.testing
from asldro.containers.image import BaseImageContainer, NumpyImageContainer
from asldro.filters.basefilter import BaseFilter, FilterInputValidationError
from asldro.filters.gkm_filter import GkmFilter

TEST_VOLUME_DIMENSIONS = (32, 32, 32)
TEST_IMAGE_ONES = NumpyImageContainer(image=np.ones(TEST_VOLUME_DIMENSIONS))
TEST_IMAGE_101 = NumpyImageContainer(image=101 *
                                     np.ones(TEST_VOLUME_DIMENSIONS))
TEST_IMAGE_NEG = NumpyImageContainer(image=-1.0 *
                                     np.ones(TEST_VOLUME_DIMENSIONS))
TEST_IMAGE_SMALL = NumpyImageContainer(image=np.ones((8, 8, 8)))

CASL = "CASL"
PCASL = "pCASL"
PASL = "PASL"

# test data dictionary, [0] in each tuple passes, after that should fail validation
TEST_DATA_DICT_PASL_M0_IM = {
    "perfusion_rate": (TEST_IMAGE_ONES, TEST_IMAGE_NEG, TEST_IMAGE_SMALL),
    "transit_time": (TEST_IMAGE_ONES, TEST_IMAGE_NEG, TEST_IMAGE_SMALL),
    "m0": (TEST_IMAGE_ONES, TEST_IMAGE_NEG, TEST_IMAGE_SMALL),
    "t1_tissue":
    (TEST_IMAGE_ONES, TEST_IMAGE_NEG, TEST_IMAGE_SMALL, TEST_IMAGE_101),
    "label_type": ("pasl", "PSL", "str"),