# Generated by CodiumAI

import numpy as np
import re

import wizard
from wizard import DataCube

import pytest

def create_test_cube(shape=(3, 4, 4)):
    cube = np.random.rand(*shape)  # Generate a random cube
    return DataCube(cube=cube, wavelengths=np.arange(shape[0]))


class TestDataCube:

    #  DataCube can be initialized with cube, wavelengths, name, and notation.
    def test_initialize_with_parameters(self):
        cube = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
        wavelengths = np.array([400, 500])
        name = "Cube1"
        notation = "nm"
    
        data_cube = DataCube(cube=cube, wavelengths=wavelengths, name=name, notation=notation)
    
        assert data_cube.cube is not None
        assert data_cube.wavelengths is not None
        assert data_cube.name == name
        assert data_cube.notation == notation

    #  DataCube can be concatenated with another DataCube using the '+' operator.
    def test_concatenate_datacubes(self):
        cube1 = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
        wavelengths1 = np.array([400, 500])
        name1 = "Cube1"
        notation1 = "A"
    
        cube2 = np.array([[[9, 10], [11, 12]], [[13, 14], [15, 16]]])
        wavelengths2 = np.array([600, 700])
        name2 = "Cube2"
        notation2 = "B"
    
        data_cube1 = DataCube(cube=cube1, wavelengths=wavelengths1, name=name1, notation=notation1)
        data_cube2 = DataCube(cube=cube2, wavelengths=wavelengths2, name=name2, notation=notation2)
    
        result = data_cube1 + data_cube2
    
        assert result.cube is not None
        assert result.wavelengths is not None
        assert result.name == name1
        assert result.notation == notation1

    #  DataCube's shape can be accessed.
    def test_access_shape(self):
        cube = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
        data_cube = DataCube(cube=cube)
    
        shape = data_cube.shape
    
        assert shape == (2, 2, 2)

    #  DataCube's length can be accessed.
    def test_access_length(self):
        cube = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
        data_cube = DataCube(cube=cube)
    
        length = len(data_cube)
    
        assert length == 2

    #  DataCube's layers can be accessed using the [] operator.
    def test_access_layers(self):
        cube = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
        data_cube = DataCube(cube=cube)
    
        layer = data_cube[0]
    
        assert np.array_equal(layer, np.array([[1, 2], [3, 4]]))

    #  DataCube can be initialized with no parameters.
    def test_initialize_with_no_parameters(self):
        data_cube = DataCube()
    
        assert data_cube.cube is None
        assert data_cube.wavelengths is None
        assert data_cube.name is None
        assert data_cube.notation is None

    #  DataCube can be initialized with a cube but no wavelengths.
    def test_initialize_with_cube_no_wavelengths(self):
        cube = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
        data_cube = DataCube(cube=cube)
    
        assert data_cube.cube is not None
        assert data_cube.wavelengths is not None

    #  DataCube can be concatenated with another DataCube with different x and y shapes.
    def test_concatenate_datacubes_different_shapes(self):
        cube1 = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
        wavelengths1 = np.array([400, 500])
        name1 = "Cube1"
        notation1 = "A"
    
        cube2 = np.array([[[9, 10, 11], [12, 13, 14]], [[15, 16, 17], [18, 19, 20]]])
        wavelengths2 = np.array([600, 700])
        name2 = "Cube2"
        notation2 = "B"
    
        data_cube1 = DataCube(cube=cube1, wavelengths=wavelengths1, name=name1, notation=notation1)
        data_cube2 = DataCube(cube=cube2, wavelengths=wavelengths2, name=name2, notation=notation2)
    
        with pytest.raises(ValueError):
            result = data_cube1 + data_cube2

    # Initializes a DataCube instance with all parameters.
    def test_all_parameters(self):
        cube = np.zeros((10, 100, 100))
        wavelengths = [400, 410, 420, 430, 440, 450, 460, 470, 480, 490]
        name = "Example DataCube"
        notation = "wavelengths"
        record = True

        data_cube = DataCube(cube=cube, wavelengths=wavelengths, name=name, notation=notation, record=record)

        assert data_cube.name == name
        assert data_cube.shape == cube.shape
        assert data_cube.dim is None
        assert np.array_equal(data_cube.wavelengths, np.array(wavelengths))
        assert np.array_equal(data_cube.cube, cube)
        assert data_cube.notation == notation
        assert data_cube.record is True

    # Add a datacube and an int and check if the ValueError gets raised.
    def test_add_with_int_raises_valueerror(self):
        cube1 = DataCube(cube=np.ones((3, 2, 2)))
        cube2 = 5
        with pytest.raises(ValueError):
            cube1 + cube2

    # Adding two DataCubes with same shape and one of them has empty wavelengths should return a new DataCube with
    # concatenated cube and None wavelengths, and a warning message
    def test_same_shape_one_empty_wavelengths(self):
        dc1 = DataCube(cube=np.ones((10, 100, 100)), wavelengths=np.arange(10), name="dc1")
        dc2 = DataCube(cube=np.ones((10, 100, 100)), wavelengths=None, name="dc2")
        dc2.wavelengths = None

        with pytest.warns(UserWarning):
            result = dc1 + dc2

        assert isinstance(result, DataCube)
        assert result.cube.shape == (20, 100, 100)
        assert result.cube.shape[0] == len(result.wavelengths)

    #  Set a valid 3D numpy array to the cube attribute
    def test_set_valid_3d_array(self):
        cube_data = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9]]])
        dc = DataCube()
        dc.set_cube(cube_data)
        assert np.array_equal(dc.cube, cube_data)

    #  Set a valid 4D numpy array to the cube attribute
    def test_set_valid_4d_array(self):
        cube_data = np.array([[[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]])
        dc = DataCube()
        dc.set_cube(cube_data)
        assert np.array_equal(dc.cube, cube_data)

    #  Set a valid 2D numpy array to the cube attribute
    def test_set_valid_2d_array(self):
        cube_data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
        dc = DataCube()
        dc.set_cube(cube_data)
        expected_cube = np.zeros(shape=(1, cube_data.shape[0], cube_data.shape[1]), dtype=cube_data.dtype)
        expected_cube[0] = cube_data
        assert np.array_equal(dc.cube, expected_cube)

    #  Set a valid 3D numpy array to the cube attribute with the same shape as an existing cube
    def test_set_valid_3d_array_same_shape(self):
        existing_cube = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9]]])
        dc = DataCube(cube=existing_cube)
        cube_data = np.array([[[10, 11, 12], [13, 14, 15], [16, 17, 18]]])
        dc.set_cube(cube_data)
        assert np.array_equal(dc.cube, cube_data)

    #  Set a valid 4D numpy array to the cube attribute with the same shape as an existing cube
    def test_set_valid_4d_array_same_shape(self):
        existing_cube = np.array([[[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]])
        dc = DataCube(cube=existing_cube)
        cube_data = np.array([[[[10, 11, 12], [13, 14, 15], [16, 17, 18]]]])
        dc.set_cube(cube_data)
        assert np.array_equal(dc.cube, cube_data)

    #  Set an empty numpy array to the cube attribute
    def test_set_empty_array(self):
        cube_data = np.array([])
        dc = DataCube()
        with pytest.raises(AttributeError):
            dc.set_cube(cube_data)
        assert np.array_equal(dc.cube, None)

    #  Set a list to the cube attribute
    def test_set_list(self):
        cube_data = [[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]
        dc = DataCube()
        dc.set_cube(cube_data)
        assert np.array_equal(dc.cube, np.array(cube_data))

    # test __iter__ and __next__
    def test_returns_next_item_and_wavelength(self):
        dc = DataCube(cube=np.random.rand(10, 100, 100), wavelengths=np.arange(10))
        iterator = iter(dc)
        next_value = next(iterator)
        assert isinstance(next_value, tuple)
        assert len(next_value) == 2
        assert isinstance(next_value[0], np.ndarray)

    # Should return a string with the name and shape of the DataCube
    def test_return_string_with_name_and_shape(self):
        dc = DataCube(cube=np.ones((3, 4, 5)), wavelengths=[400, 500, 600], name="Example", notation='nm')
        expected_output = ("Name: Example\n"
                           "Shape: (3, 4, 5)\n"
                           "Wavelengths:\n"
                           "\tLen: 3\n"
                           "\tFrom: 400\n"
                           "\tTo: 600\n"
                           "Notaion: nm"
                          )

        assert dc.__str__() == expected_output



    #  Method sets wavelength data when given a valid 1D numpy array.
    def test_valid_wavelengths_array(self):
        wavelengths = np.array([400, 450, 500, 550, 600])
        dc = DataCube()
        dc.set_wavelengths(wavelengths)
        assert np.array_equal(dc.wavelengths, wavelengths)

    #  Method sets wavelength data when given an empty 1D numpy array.
    def test_empty_wavelengths_array(self):
        wavelengths = np.array([])
        dc = DataCube()
        dc.set_wavelengths(wavelengths)
        assert np.array_equal(dc.wavelengths, wavelengths)

    #  Method raises AttributeError when given a non-convertible object.
    def test_non_convertible_object(self):
        wavelengths = "invalid"
        dc = DataCube()
        with pytest.raises(AttributeError):
            dc.set_wavelengths(wavelengths)

    #  Method raises AttributeError when given a numpy array with ndim != 1.
    def test_ndim_not_1(self):
        wavelengths = np.array([[400, 450, 500], [550, 600, 650]])
        dc = DataCube()
        with pytest.raises(AttributeError):
            dc.set_wavelengths(wavelengths)

    #  Method raises AttributeError when given a numpy array with ndim == 0.
    def test_ndim_0(self):
        wavelengths = np.array(400)
        dc = DataCube()
        with pytest.raises(AttributeError):
            dc.set_wavelengths(wavelengths)

    # tets add function
    def test_add_with_none(self):
        dc1 = DataCube()
        dc2 = DataCube()
        with pytest.raises(ValueError):
            dc3 = dc1 + dc2

    # test set method
    def test_setitem(self):
        cube = np.random.rand(10, 100, 100)
        dc = DataCube(cube=cube)
        a = np.random.rand(100, 100)
        dc[3] = a
        assert np.array_equal(dc.cube[3], a)

    # Test itter method
    def test_next(self):
        idx = 0
        dc = DataCube(cube=np.random.rand(5,10,10))
        for idx, i in enumerate(dc):
            pass
        assert dc.shape[0] == idx + 1

    # Resizing a cube with valid x_new and y_new values and linear interpolation
    def test_resize_linear_down(self):
        cube = np.random.rand(10, 100, 100)
        dc = DataCube(cube=cube)
        dc.resize(50, 50, interpolation='linear')
        assert dc.cube.shape == (10, 50, 50)

    # Resizing a cube with valid x_new and y_new values and nearest interpolation
    def test_valid_resize_nearest_interpolation(self):
        cube = np.random.rand(10, 100, 100)
        dc = DataCube(cube=cube)
        dc.resize(50, 50, interpolation='nearest')
        assert dc.cube.shape == (10, 50, 50)

    # Resizing a cube with x_new and y_new values
    def test_resize_linear(self):
        cube = np.random.rand(10, 100, 100)
        dc = DataCube(cube=cube)
        dc.resize(200, 200, interpolation='linear')
        assert dc.cube.shape == (10, 200, 200)

    # Resizing a cube with x_new and y_new values
    def test_resize_nearest(self):
        cube = np.random.rand(10, 100, 100)
        dc = DataCube(cube=cube)
        dc.resize(200, 200, interpolation='nearest')
        assert dc.cube.shape == (10, 200, 200)

    # Resizing a cube with x_new and y_new values
    def test_resize_area(self):
        cube = np.random.rand(10, 100, 100)
        dc = DataCube(cube=cube)
        dc.resize(200, 200, interpolation='area')
        assert dc.cube.shape == (10, 200, 200)

    # Resizing a cube with x_new and y_new values
    def test_resize_cubic(self):
        cube = np.random.rand(10, 100, 100)
        dc = DataCube(cube=cube)
        dc.resize(200, 200, interpolation='cubic')
        assert dc.cube.shape == (10, 200, 200)

    # Resizing a cube with x_new and y_new values
    def test_resize_lanczos(self):
        cube = np.random.rand(10, 100, 100)
        dc = DataCube(cube=cube)
        dc.resize(200, 200, interpolation='lanczos')
        assert dc.cube.shape == (10, 200, 200)

    def test_resize_unknow_interpolatio_method(self):
        dc = DataCube(cube=np.random.rand(10,10,10))
        with pytest.raises(ValueError):
            dc.resize(200,200, interpolation='test')

    def test_set_wavelengths_with_non_array(self):
        dc = DataCube(cube=np.random.rand(10,10,10))
        dc.set_wavelengths([0,1,2,3,4,5,6,7,8,9])

    def test_set_cube_with_non_array_and_get_errer(self):
        dc = DataCube()
        with pytest.raises(AttributeError):
            dc.set_cube('hi')


    # Line 242: Test set_name with valid and invalid inputs
    def test_set_name(self):
        data_cube = DataCube()
        valid_name = "ValidName"
        data_cube.set_name(valid_name)
        assert data_cube.name == valid_name

        with pytest.raises(AttributeError):
            data_cube.set_name(123)  # Non-string input

    # Lines 261-273: Test set_wavelengths with valid and invalid data
    def test_set_wavelengths(self):
        data_cube = DataCube()

        # Valid 1D array
        valid_wavelengths = np.array([400, 500, 600])
        data_cube.set_wavelengths(valid_wavelengths)
        assert np.array_equal(data_cube.wavelengths, valid_wavelengths)

        # Valid list
        valid_wavelengths_list = [400, 500, 600]
        data_cube.set_wavelengths(valid_wavelengths_list)
        assert np.array_equal(data_cube.wavelengths, np.array(valid_wavelengths_list))

        # Invalid 2D array
        invalid_wavelengths = np.array([[400, 500], [600, 700]])
        with pytest.raises(AttributeError):
            data_cube.set_wavelengths(invalid_wavelengths)

    # Line 280: Test set_cube with valid 3D and 2D arrays, and invalid input
    def test_set_cube(self):
        data_cube = DataCube()

        # Valid 3D array
        valid_cube_3d = np.random.rand(5, 10, 10)
        data_cube.set_cube(valid_cube_3d)
        assert data_cube.cube.shape == (5, 10, 10)

        # Valid 2D array which should be converted to 3D
        valid_cube_2d = np.random.rand(10, 10)
        data_cube.set_cube(valid_cube_2d)
        assert data_cube.cube.shape == (1, 10, 10)

        # Invalid array (not 2D, 3D, or 4D)
        invalid_cube = np.random.rand(1, 10, 10, 10, 10)
        with pytest.raises(AttributeError):
            data_cube.set_cube(invalid_cube)

    # Lines 288-291: Test _set_cube_shape method
    def test_set_cube_shape(self):
        data_cube = DataCube()
        cube = np.random.rand(3, 10, 10)
        data_cube.set_cube(cube)
        assert data_cube.shape == (3, 10, 10)

    # Lines 341-342: Test set_notation with valid input
    def test_set_notation(self):
        data_cube = DataCube()
        notation = "nm"
        data_cube.set_notation(notation)
        assert data_cube.notation == notation

    # Line 370: Test start_recording method
    def test_start_recording(self, mocker):
        data_cube = DataCube()
        mocker.patch("wizard._utils.tracker.TrackExecutionMeta.start_recording")
        data_cube.start_recording()
        assert data_cube.record is True

    # Lines 379-380: Test stop_recording method
    def test_stop_recording(self, mocker):
        data_cube = DataCube()
        mocker.patch("wizard._utils.tracker.TrackExecutionMeta.stop_recording")
        data_cube.stop_recording()
        assert data_cube.record is False

    # Lines 397-410: Test save_template with valid and invalid filenames
    def test_save_template(self, mocker):
        data_cube = DataCube()
        mock_yaml_dump = mocker.patch("yaml.dump")
        mock_open = mocker.patch("builtins.open", mocker.mock_open())

        # Valid filename
        filename = "template"
        data_cube.save_template(filename)
        mock_open.assert_called_with(filename+'.yaml', 'w')
        assert mock_yaml_dump.called

        # Invalid filename: None
        with pytest.raises(AttributeError):
            data_cube.save_template(None)

        # Invalid filename: non-string type
        with pytest.raises(AttributeError):
            data_cube.save_template(123)

    # Lines 431-440: Test _map_args_to_kwargs with sample function and args
    def test_map_args_to_kwargs(self):
        data_cube = DataCube()

        # Define a sample function to test argument mapping
        def sample_func(a, b, c=None):
            pass

        # Test positional and keyword argument mapping
        args = (1, 2)
        kwargs = {"c": 3}
        mapped_kwargs = data_cube._map_args_to_kwargs(sample_func, args, kwargs)
        assert mapped_kwargs == {"a": 1, "b": 2, "c": 3}

    # Lines 491-497: Test execute_template with mocked method
    def test_execute_template(self, mocker):
        data_cube = DataCube()
        mock_method = mocker.patch.object(data_cube, "set_name")

        template_data = [{"method": "set_name", "kwargs": {"name": "Cube1"}}]

        # Mock yaml.safe_load to return template_data
        mocker.patch("yaml.safe_load", return_value=template_data)
        mock_open = mocker.patch("builtins.open", mocker.mock_open(read_data="template_data"))

        data_cube.execute_template("template.yml")
        mock_open.assert_called_with("template.yml", 'rb')
        mock_method.assert_called_once_with(name="Cube1")

    def test_custom_read(self):
        # Create a DataCube instance
        data_cube = DataCube()

        # Define a mock reading function
        def mock_reading_func(*args, **kwargs):
            cube_data = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
            wavelengths = [400, 500]
            notation = "nm"
            name = "TestCube"
            return DataCube(cube=cube_data, wavelengths=wavelengths, name=name, notation=notation)

        # Set the custom reading function
        data_cube.set_custom_reader(mock_reading_func)

        # Invoke custom_read, which should use mock_reading_func
        data_cube.custom_read()

        # Assertions to verify that attributes were set correctly
        assert np.array_equal(data_cube.cube, np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]))
        assert np.array_equal(data_cube.wavelengths, np.array([400, 500]))
        assert data_cube.notation == "nm"
        assert data_cube.name == "TestCube"

        # Remove the reading_func and check for NotImplementedError
        del data_cube.reading_func
        error_message = "You need to implement a `custom_read` method and set it with `set_custom_read(fuc)."
        with pytest.raises(NotImplementedError, match=re.escape(error_message)):
            data_cube.custom_read()

    def test_set_cube_with_wrong_cube_data(self):

        dc = DataCube()

        with pytest.raises(ValueError):
            dc.set_cube([[3123,123123], '134'])
 
    def test_record_start_stop(self):
        dc = DataCube(record=True)
        assert dc.record == True
        dc.stop_recording()
        assert dc.record == False
        dc.start_recording()
        assert dc.record == True


class TestDataCubeOps:

    def test_remove_spikes(self):
        dc = create_test_cube()
        dc.cube[0, 1, 1] = 1000  # Insert a spike
        threshold = 10
        dc.remove_spikes(threshold=threshold, window=3)

        assert dc is not None
        assert dc.cube.shape == dc.cube.shape
        assert dc.cube[0, 1, 1] != 1000




    def test_resize(self):
        dc = create_test_cube(shape=(3, 10, 10))
        x_new, y_new = 5, 5

        dc.resize(x_new, y_new, interpolation='linear')

        assert dc.cube.shape[1] == x_new
        assert dc.cube.shape[2] == y_new

    def test_resize_invalid_interpolation(self):
        dc = create_test_cube(shape=(3, 10, 10))
        with pytest.raises(ValueError):
            dc.resize(5, 5, interpolation='invalid_method')

    def test_baseline_als(self):
        dc = create_test_cube()
        dc.baseline_als(lam=1000000, p=0.01, niter=10)
        assert dc is not None
        assert isinstance(dc, DataCube)

    def test_merge_cubes(self):
        dc1 = create_test_cube()
        dc2 = create_test_cube()
        dc2.set_wavelengths([4,5,6])
        dc1.merge_cubes(dc2)
        assert dc1 is not None
        np.testing.assert_array_equal(dc1.wavelengths, [0,1,2,4,5,6])
        np.testing.assert_array_equal(dc1.shape, (6,4,4))
        assert isinstance(dc1, DataCube)

    def test_remove_background_dark(self):
        dc = create_test_cube(shape=(3, 24, 24))

        # Create a temporary cube with specific values for testing
        tmp_cube = dc.cube.copy() * 0.2

        # Define the middle region that should remain unchanged
        x_start, x_end = int(dc.shape[1] * 0.25), int(dc.shape[1] * 0.75)
        y_start, y_end = int(dc.shape[2] * 0.25), int(dc.shape[2] * 0.75)

        # Apply specific values to the middle region using broadcasting
        tmp_cube[0, x_start:x_end, y_start:y_end] = 0.8
        tmp_cube[1, x_start:x_end, y_start:y_end] = 0.8
        tmp_cube[2, x_start:x_end, y_start:y_end] = 0.8

        # Set the modified cube
        dc.set_cube(tmp_cube)

        # Call the method under test
        threshold = 50
        dc.remove_background(threshold=threshold, style='dark')

        # Assertions
        assert dc is not None, "DataCube instance should not be None after removing the background."
        assert dc.cube.shape == tmp_cube.shape, "The shape of the cube should remain unchanged after background removal."
        assert isinstance(dc.cube, np.ndarray), "The cube should be a NumPy array."

        # Validate that the middle region remains unchanged
        middle_region = dc.cube[:, x_start:x_end, y_start:y_end]
        expected_middle = tmp_cube[:, x_start:x_end, y_start:y_end]
        assert np.allclose(middle_region, expected_middle), "The middle region should remain unchanged for 'dark' style."

        # Validate that the outside regions have a mean close to 1
        outside_regions = np.concatenate([
            dc.cube[:, :x_start, :].flatten(),  # Top region
            dc.cube[:, x_end:, :].flatten(),   # Bottom region
            dc.cube[:, :, :y_start].flatten(), # Left region
            dc.cube[:, :, y_end:].flatten()    # Right region
        ])

        assert np.isclose(outside_regions.mean(), 0, atol=1e-1), "The outside regions should have a mean value close to 1 for 'dark' style."

    def test_remove_background_bright(self):
        dc = create_test_cube(shape=(3, 24, 24))

        # Create a temporary cube with specific low values for testing
        tmp_cube = dc.cube.copy() * 0.6 + 0.6

        # Define the middle region that should remain unchanged
        x_start, x_end = int(dc.shape[1] * 0.25), int(dc.shape[1] * 0.75)
        y_start, y_end = int(dc.shape[2] * 0.25), int(dc.shape[2] * 0.75)

        # Apply specific low values to the middle region using broadcasting
        tmp_cube[0, x_start:x_end, y_start:y_end] = 0.2
        tmp_cube[1, x_start:x_end, y_start:y_end] = 0.2
        tmp_cube[2, x_start:x_end, y_start:y_end] = 0.2

        # Set the modified cube
        dc.set_cube(tmp_cube)

        from matplotlib import pyplot as plt

        # Call the method under test
        threshold = 10
        dc.remove_background(threshold=threshold, style='bright')

        # Assertions
        assert dc is not None, "DataCube instance should not be None after removing the background."
        assert dc.cube.shape == tmp_cube.shape, "The shape of the cube should remain unchanged after background removal."
        assert isinstance(dc.cube, np.ndarray), "The cube should be a NumPy array."

        # Validate that the middle region remains unchanged
        middle_region = dc.cube[:, x_start:x_end, y_start:y_end]
        expected_middle = tmp_cube[:, x_start:x_end, y_start:y_end]
        assert np.allclose(middle_region, expected_middle), "The middle region should remain unchanged for 'bright' style."

        # Validate that the outside regions have a mean close to 1
        outside_regions = np.concatenate([
            dc.cube[:, :x_start, :].flatten(),  # Top region
            dc.cube[:, x_end:, :].flatten(),   # Bottom region
            dc.cube[:, :, :y_start].flatten(), # Left region
            dc.cube[:, :, y_end:].flatten()    # Right region
        ])

        assert np.isclose(outside_regions.mean(), 1, atol=.5), "The outside regions should have a mean value close to 1 for 'bright' style."

    def test_remove_background_invalid_style(self):
        dc = create_test_cube()
        threshold = .5

        # Test for invalid style
        with pytest.raises(ValueError):
            dc.remove_background(threshold=threshold, style='invalid')

    def test_inverse(self):
        dc = create_test_cube()
        inverted_dc = dc.inverse()
        assert inverted_dc is not None
        assert np.all(inverted_dc.cube >= 0)  # Inversion should result in non-negative values

    def test_remove_vignetting(self):
        dc = create_test_cube(shape=(4, 200, 200))
        corrected_dc = dc.remove_vignetting() # axis=1, slice_params={"start": 0, "end": 2, "step": 1})
        assert corrected_dc is not None
        assert corrected_dc.cube.shape == dc.cube.shape

    def test_merge_cubes_invalid_shape(self):
        dc1 = create_test_cube(shape=(3, 4, 4))
        dc2 = create_test_cube(shape=(3, 5, 5))
        with pytest.raises(NotImplementedError):
            dc1.merge_cubes(dc2)

    def test_register_layers_simple(self):
        import numpy as np
        dc = create_test_cube(shape=(3, 5, 5))
        # Make all layers identical for easy registration
        base = np.random.rand(5, 5)
        dc.cube = np.stack([base, base, base])
        result = dc.register_layers_simple(max_features=1000, match_percent=0.2)
        assert result is dc
        assert dc.cube.shape == (3, 5, 5)
        assert getattr(dc, 'registered', False) is True

    def test_remove_vignetting_poly_invalid_axis(self):
        dc = create_test_cube()
        with pytest.raises(ValueError):
            dc.remove_vignetting_poly(axis=3)

    def test_remove_vignetting_poly_default(self):
        import numpy as np
        dc = create_test_cube(shape=(2, 72, 72))
        original_shape = dc.cube.shape
        result = dc.remove_vignetting_poly()
        assert result is dc
        assert dc.cube.shape == original_shape
        # After polynomial vignetting removal, dtype should be float32
        assert dc.cube.dtype == np.float32

    def test_normalize(self):
        import numpy as np
        dc = create_test_cube(shape=(2, 4, 4))
        data = np.array([
            [[ 1,  2,  3,  4],
             [ 5,  6,  7,  8],
             [ 9, 10, 11, 12],
             [13, 14, 15, 16]],
            [[16, 15, 14, 13],
             [12, 11, 10,  9],
             [ 8,  7,  6,  5],
             [ 4,  3,  2,  1]]
        ], dtype=float)
        dc.set_cube(data)
        dc.normalize()
        # Each layer should now span [0,1]
        for layer in dc.cube:
            assert np.isclose(layer.min(), 0.0)
            assert np.isclose(layer.max(), 1.0)

    def test_register_layers_best(self):
        dc = create_test_cube(shape=(10, 251, 201))

        # Create a temporary cube with specific values for testing
        tmp_cube = dc.cube.copy() * 0.1

        # Define the middle region that should remain unchanged
        x_start, x_end = int(dc.shape[1] * 0.45), int(dc.shape[1] * 0.65)
        y_start, y_end = int(dc.shape[2] * 0.45), int(dc.shape[2] * 0.65)

        # Apply specific values to the middle region using broadcasting
        for idx, layer in enumerate(range(dc.shape[0])):
            tmp_cube[layer, x_start+idx:x_end+idx, y_start+idx:y_end+idx] = 0.9
        dc.set_cube(tmp_cube)
        result = dc.register_layers_best(ref_layer=2, scale_thresh=2.2, rot_thresh=20.)
        assert result is dc
        assert dc.cube.shape == (10, 251, 201)
        assert getattr(dc, 'registered', False) is True

    def test_remove_vignetting(self):
        import numpy as np
        dc = create_test_cube(shape=(3, 4, 4))
        # constant cube so flat-field correction yields same values
        constant = np.ones((3, 4, 4), dtype=np.uint8) * 10
        dc.set_cube(constant.copy())
        dtype_before = dc.cube.dtype
        result = dc.remove_vignetting(sigma=1)
        assert result is dc
        assert dc.cube.shape == constant.shape
        assert dc.cube.dtype == dtype_before
        # all values should remain equal to original constant
        assert np.all(dc.cube == constant)
