# SPDX-License-Identifier: MIT

import os
import numpy as np
import json

from copy import deepcopy

from xypattern import Pattern
from xypattern.auto_background import SmoothBrucknerBackground

from .util import Signal
from .util.ImgCorrection import CbnCorrection, ObliqueAngleDetectorAbsorptionCorrection

from .util.calc import convert_units
from . import ImgModel, CalibrationModel, MaskModel, PatternModel, BatchModel
from .MapModel import MapModel
from .CalibrationModel import DetectorModes


def _json_numpy_default(obj):
    """JSON encoder default for numpy int/float types."""
    if isinstance(obj, np.integer):
        return int(obj)
    if isinstance(obj, np.floating):
        return float(obj)
    if isinstance(obj, np.ndarray):
        return obj.tolist()
    raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")


class Configuration(object):
    """
    The configuration class contains a working combination of an ImgModel, PatternModel, MaskModel and CalibrationModel.
    It does handles the core data manipulation of Dioptas.
    The management of multiple Configurations is done by the DioptasModel.
    """

    def __init__(self, working_directories=None):
        super(Configuration, self).__init__()

        self.img_model = ImgModel()
        self.mask_model = MaskModel()
        self.calibration_model = CalibrationModel(self.img_model)
        self.pattern_model = PatternModel()

        self.batch_model = BatchModel(self)
        self.map_model = MapModel(self)

        if working_directories is None:
            self.working_directories = {
                "calibration": "",
                "mask": "",
                "image": os.path.expanduser("~"),
                "pattern": "",
                "overlay": "",
                "phase": "",
                "batch": os.path.expanduser("~"),
            }
        else:
            self.working_directories = working_directories

        self.use_mask = False

        self.transparent_mask = False

        self._integration_rad_points = None
        self._integration_unit = "2th_deg"
        self._oned_azimuth_range = None
        self.trim_trailing_zeros = True

        self._cake_azimuth_points = 360
        self._cake_azimuth_range = None

        self._auto_integrate_pattern = True
        self._auto_integrate_cake = False

        self.auto_save_integrated_pattern = False
        self.integrated_patterns_file_formats = [".xy"]

        self.cake_changed = Signal()
        self._connect_signals()

    def _connect_signals(self):
        """
        Connects the img_changed signal to responding functions.
        """
        self.img_model.img_changed.connect(self.update_mask_dimension)
        self.img_model.img_changed.connect(self.integrate_image_1d)

    def integrate_image_1d(self, update_pattern_model=True):
        """
        Integrates the image in the ImageModel to a Pattern. Will also automatically save the integrated pattern, if
        auto_save_integrated is True.

        :param update_pattern_model: If True, updates pattern_model and emits pattern_changed signal.
            Set to False during batch/map integration to avoid unnecessary GUI updates.
        """
        if self.calibration_model.is_calibrated:
            if self.use_mask:
                mask = self.mask_model.get_mask()
            elif self.mask_model.roi is not None:
                mask = self.mask_model.roi_mask
            else:
                mask = None

            x, y = self.calibration_model.integrate_1d(
                azi_range=self.oned_azimuth_range,
                mask=mask,
                unit=self.integration_unit,
                num_points=self.integration_rad_points,
                trim_zeros=self.trim_trailing_zeros,
            )

            if update_pattern_model:
                self.pattern_model.set_pattern(
                    x, y, self.img_model.filename, unit=self.integration_unit
                )

                if self.auto_save_integrated_pattern:
                    self._auto_save_patterns()

            return x, y

    def integrate_image_2d(self):
        """
        Integrates the image in the ImageModel to a Cake.
        """
        if self.use_mask:
            mask = self.mask_model.get_mask()
        elif self.mask_model.roi is not None:
            mask = self.mask_model.roi_mask
        else:
            mask = None

        self.calibration_model.integrate_2d(
            mask=mask,
            rad_points=self._integration_rad_points,
            azimuth_points=self._cake_azimuth_points,
            azimuth_range=self._cake_azimuth_range,
        )

        self.cake_changed.emit()

    def save_pattern(self, filename=None, subtract_background=False):
        """
        Saves the current integrated pattern. The format depends on the file ending. Possible file formats:
            [*.xy, *.chi, *.dat, *.fxye]
        :param filename: where to save the file
        :param subtract_background: flat whether the pattern should be saved with or without subtracted background
        """
        if filename is None:
            filename = self.img_model.filename

        if filename.endswith(".xy"):
            self.pattern_model.save_pattern(
                filename,
                header=self._create_xy_header(),
                subtract_background=subtract_background,
            )
        elif filename.endswith(".fxye"):
            self.pattern_model.save_pattern(
                filename,
                header=self._create_fxye_header(filename),
                subtract_background=subtract_background,
            )
        else:
            self.pattern_model.save_pattern(
                filename, subtract_background=subtract_background
            )

    def save_background_pattern(self, filename=None):
        """
        Saves the current fit background as a pattern. The format depends on the file ending. Possible file formats:
            [*.xy, *.chi, *.dat, *.fxye]
        """
        if filename is None:
            filename = self.img_model.filename

        if filename.endswith(".xy"):
            self.pattern_model.save_auto_background_as_pattern(
                filename, header=self._create_xy_header()
            )
        elif filename.endswith(".fxye"):
            self.pattern_model.save_auto_background_as_pattern(
                filename, header=self._create_fxye_header(filename)
            )
        else:
            self.pattern_model.save_pattern(filename)

    def _create_xy_header(self) -> str:
        """
        Creates the header for the xy file format (contains information about calibration parameters).
        :return: header string
        """
        header = self.calibration_model.create_file_header()
        header = header.replace("\r\n", "\n")
        header = header + "\n#\n# " + self._integration_unit + "\t I"
        return header

    def _create_fxye_header(self, filename) -> str:
        """
        Creates the header for the fxye file format (used by GSAS and GSAS-II) containing the calibration information
        :return: header string
        """
        header = "Generated file " + filename + " using DIOPTAS\n"
        header = header + self.calibration_model.create_file_header()
        unit = self._integration_unit
        lam = self.calibration_model.wavelength
        if unit == "q_A^-1":
            con = "CONQ"
        else:
            con = "CONS"

        header = (
            header
            + "\nBANK\t1\tNUM_POINTS\tNUM_POINTS "
            + con
            + "\tMIN_X_VAL\tSTEP_X_VAL "
            + "{0:.5g}".format(lam * 1e10)
            + " 0.0 FXYE"
        )
        return header

    def _auto_save_patterns(self):
        """
        Saves the current pattern in the pattern working directory (specified in self.working_directories['pattern'].
        When background subtraction is enabled in the pattern model the pattern will be saved with background
        subtraction and without in another sub-folder. ('bkg_subtracted')
        """
        for file_ending in self.integrated_patterns_file_formats:
            filename = os.path.join(
                self.working_directories["pattern"],
                os.path.basename(str(self.img_model.filename)).split(".")[:-1][0]
                + file_ending,
            )
            filename = filename.replace("\\", "/")
            self.save_pattern(filename, subtract_background=False)

        pattern = self.pattern_model.pattern

        if pattern.background_pattern is not None or pattern.auto_bkg is not None:
            for file_ending in self.integrated_patterns_file_formats:
                directory = os.path.join(
                    self.working_directories["pattern"], "bkg_subtracted"
                )
                if not os.path.exists(directory):
                    os.mkdir(directory)
                filename = os.path.join(
                    directory, self.pattern_model.pattern.name + file_ending
                )
                filename = filename.replace("\\", "/")
                self.save_pattern(filename, subtract_background=True)

    def update_mask_dimension(self):
        """
        Updates the shape of the mask in the MaskModel to the shape of the image in the ImageModel.
        """
        self.mask_model.set_dimension(self.img_model._img_data.shape)

    @property
    def integration_rad_points(self) -> int:
        return self._integration_rad_points

    @integration_rad_points.setter
    def integration_rad_points(self, new_value: int):
        self._integration_rad_points = new_value
        self.integrate_image_1d()
        if self.auto_integrate_cake:
            self.integrate_image_2d()

    @property
    def cake_azimuth_points(self) -> int:
        return self._cake_azimuth_points

    @cake_azimuth_points.setter
    def cake_azimuth_points(self, new_value):
        self._cake_azimuth_points = new_value
        if self.auto_integrate_cake:
            self.integrate_image_2d()

    @property
    def cake_azimuth_range(self):
        return self._cake_azimuth_range

    @cake_azimuth_range.setter
    def cake_azimuth_range(self, new_value):
        self._cake_azimuth_range = new_value
        if self.auto_integrate_cake:
            self.integrate_image_2d()

    @property
    def oned_azimuth_range(self):
        return self._oned_azimuth_range

    @oned_azimuth_range.setter
    def oned_azimuth_range(self, new_value):
        self._oned_azimuth_range = new_value
        if self.auto_integrate_pattern:
            self.integrate_image_1d()

    @property
    def integration_unit(self) -> str:
        return self._integration_unit

    @integration_unit.setter
    def integration_unit(self, new_unit: str):
        old_unit = self.integration_unit
        self._integration_unit = new_unit

        pattern = self.pattern_model.pattern
        x = getattr(pattern, "x", None)
        valid_units = {"2th_deg", "q_A^-1", "d_A"}
        if old_unit not in valid_units or new_unit not in valid_units:
            return
        if old_unit == new_unit:
            return
        if x is not None and len(x) > 1:
            pattern.transform_x(
                lambda x: convert_units(
                    x, self.calibration_model.wavelength, old_unit, new_unit
                )
            )
            self.integrate_image_1d()

    @property
    def correct_solid_angle(self):
        return self.calibration_model.correct_solid_angle

    @correct_solid_angle.setter
    def correct_solid_angle(self, new_val):
        self.calibration_model.correct_solid_angle = new_val
        if self.auto_integrate_pattern:
            self.integrate_image_1d()
        if self._auto_integrate_cake:
            self.integrate_image_2d()

    @property
    def is_calibrated(self) -> bool:
        return self.calibration_model.is_calibrated

    @property
    def auto_integrate_cake(self) -> bool:
        return self._auto_integrate_cake

    @auto_integrate_cake.setter
    def auto_integrate_cake(self, new_value):
        if self._auto_integrate_cake == new_value:
            return

        self._auto_integrate_cake = new_value
        if new_value:
            self.img_model.img_changed.connect(self.integrate_image_2d)
        else:
            self.img_model.img_changed.disconnect(self.integrate_image_2d)

    @property
    def auto_integrate_pattern(self) -> bool:
        return self._auto_integrate_pattern

    @auto_integrate_pattern.setter
    def auto_integrate_pattern(self, new_value):
        if self._auto_integrate_pattern == new_value:
            return

        self._auto_integrate_pattern = new_value
        if new_value:
            self.img_model.img_changed.connect(self.integrate_image_1d)
        else:
            self.img_model.img_changed.disconnect(self.integrate_image_1d)

    @property
    def cake_img(self) -> np.ndarray:
        return self.calibration_model.cake_img

    @property
    def roi(self):
        return self.mask_model.roi

    @roi.setter
    def roi(self, new_val):
        self.mask_model.roi = new_val
        self.integrate_image_1d()

    def copy(self) -> "Configuration":
        """
        Creates a copy of the current configuration directory
        :return: copied configuration
        :rtype: Configuration
        """
        new_configuration = Configuration(self.working_directories)
        new_configuration.img_model._img_data = self.img_model._img_data
        new_configuration.img_model.img_transformations = deepcopy(
            self.img_model.img_transformations
        )

        new_configuration.calibration_model.set_pyFAI(
            self.calibration_model.get_calibration_parameter()[0]
        )
        new_configuration.integrate_image_1d()

        return new_configuration

    def save_in_hdf5(self, hdf5_group):
        """
        Saves the configuration group in the given hdf5_group.
        :type hdf5_group: h5py.Group
        """

        f = hdf5_group
        # save general information
        general_information = f.create_group("general_information")
        # integration parameters:
        general_information.attrs["integration_unit"] = self.integration_unit
        if self.integration_rad_points:
            general_information.attrs["integration_num_points"] = (
                self.integration_rad_points
            )
        else:
            general_information.attrs["integration_num_points"] = 0

        # cake parameters:
        general_information.attrs["auto_integrate_cake"] = self.auto_integrate_cake
        general_information.attrs["cake_azimuth_points"] = self.cake_azimuth_points
        if self.cake_azimuth_range is None:
            general_information.attrs["cake_azimuth_range"] = "None"
        else:
            general_information.attrs["cake_azimuth_range"] = self.cake_azimuth_range

        # mask parameters
        general_information.attrs["use_mask"] = self.use_mask
        general_information.attrs["transparent_mask"] = self.transparent_mask

        # auto save parameters
        general_information.attrs["auto_save_integrated_pattern"] = (
            self.auto_save_integrated_pattern
        )
        formats = [
            n.encode("ascii", "ignore") for n in self.integrated_patterns_file_formats
        ]
        general_information.create_dataset(
            "integrated_patterns_file_formats", (len(formats), 1), "S10", formats
        )

        # save working directories
        working_directories_gp = f.create_group("working_directories")
        try:
            for key in self.working_directories:
                working_directories_gp.attrs[key] = self.working_directories[key]
        except TypeError:
            self.working_directories = {
                "calibration": "",
                "mask": "",
                "image": "",
                "pattern": "",
                "overlay": "",
                "phase": "",
                "batch": "",
            }
            for key in self.working_directories:
                working_directories_gp.attrs[key] = self.working_directories[key]

        # save image model
        image_group = f.create_group("image_model")
        image_group.attrs["auto_process"] = self.img_model.autoprocess
        image_group.attrs["factor"] = self.img_model.factor
        image_group.attrs["has_background"] = self.img_model.has_background()
        image_group.attrs["background_filename"] = self.img_model.background_filename
        image_group.attrs["background_offset"] = self.img_model.background_offset
        image_group.attrs["background_scaling"] = self.img_model.background_scaling
        if self.img_model.has_background():
            background_data = self.img_model.untransformed_background_data
            image_group.create_dataset(
                "background_data", background_data.shape, "f", background_data
            )

        image_group.attrs["series_max"] = self.img_model.series_max
        image_group.attrs["series_pos"] = self.img_model.series_pos

        # image corrections
        corrections_group = image_group.create_group("corrections")
        corrections_group.attrs["has_corrections"] = self.img_model.has_corrections()
        for (
            correction,
            correction_object,
        ) in self.img_model.img_corrections.corrections.items():
            if correction in ["cbn", "oiadac"]:
                correction_data = correction_object.get_data()
                imcd = corrections_group.create_dataset(
                    correction, correction_data.shape, "f", correction_data
                )
                for param, value in correction_object.get_params().items():
                    imcd.attrs[param] = value
            elif correction == "transfer":
                params = correction_object.get_params()
                transfer_group = corrections_group.create_group("transfer")
                original_data = params["original_data"]
                response_data = params["response_data"]
                original_ds = transfer_group.create_dataset(
                    "original_data", original_data.shape, "f", original_data
                )
                original_ds.attrs["filename"] = params["original_filename"]
                response_ds = transfer_group.create_dataset(
                    "response_data", response_data.shape, "f", response_data
                )
                response_ds.attrs["filename"] = params["response_filename"]

        # the actual image
        image_group.attrs["filename"] = self.img_model.filename
        current_raw_image = self.img_model.untransformed_raw_img_data

        raw_image_data = image_group.create_dataset(
            "raw_image_data", current_raw_image.shape, dtype="f"
        )
        raw_image_data[...] = current_raw_image

        # image transformations
        transformations_group = image_group.create_group("image_transformations")
        for ind, transformation in enumerate(
            self.img_model.get_transformations_string_list()
        ):
            transformations_group.attrs[str(ind)] = transformation

        # save roi data
        if self.roi is not None:
            image_group.attrs["has_roi"] = True
            image_group.create_dataset("roi", (4,), "i8", tuple(self.roi))
        else:
            image_group.attrs["has_roi"] = False

        # save mask model
        mask_group = f.create_group("mask")
        current_mask = self.mask_model.get_mask()
        mask_data = mask_group.create_dataset("data", current_mask.shape, dtype=bool)
        mask_data[...] = current_mask

        # save detector information
        detector_group = f.create_group("detector")
        detector_mode = self.calibration_model.detector_mode
        detector_group.attrs["detector_mode"] = detector_mode.value
        if detector_mode == DetectorModes.PREDEFINED:
            detector_group.attrs["detector_name"] = self.calibration_model.detector.name
        elif detector_mode == DetectorModes.NEXUS:
            detector_group.attrs["nexus_filename"] = (
                self.calibration_model.detector.filename
            )

        # save calibration model
        calibration_group = f.create_group("calibration_model")
        # version 2.0 is used to indicate that the pyFAI parameters are stored as a json string
        calibration_group.attrs["version"] = "2.0"

        calibration_filename = self.calibration_model.filename
        if calibration_filename.endswith(".poni"):
            base_filename, ext = self.calibration_model.filename.rsplit(".", 1)
        else:
            base_filename = self.calibration_model.filename
            ext = "poni"
        calibration_group.attrs["calibration_filename"] = base_filename + "." + ext

        pyfai_config = self.calibration_model.pattern_geometry.get_config()
        calibration_group.attrs["pyfai_parameters"] = json.dumps(
            pyfai_config, default=_json_numpy_default
        )
        calibration_group.attrs["polarization_factor"] = (
            self.calibration_model.polarization_factor
        )

        calibration_group.attrs["correct_solid_angle"] = self.correct_solid_angle
        if self.calibration_model.distortion_spline_filename is not None:
            calibration_group.attrs["distortion_spline_filename"] = (
                self.calibration_model.distortion_spline_filename
            )

        # save background pattern and pattern model
        background_pattern_group = f.create_group("background_pattern")
        try:
            background_pattern_x = self.pattern_model.background_pattern._original_x
            background_pattern_y = self.pattern_model.background_pattern._original_y
        except (TypeError, AttributeError):
            background_pattern_x = None
            background_pattern_y = None
        if background_pattern_x is not None and background_pattern_y is not None:
            background_pattern_group.attrs["has_background_pattern"] = True
            bgx = background_pattern_group.create_dataset(
                "x", background_pattern_x.shape, dtype="f"
            )
            bgy = background_pattern_group.create_dataset(
                "y", background_pattern_y.shape, dtype="f"
            )
            bgx[...] = background_pattern_x
            bgy[...] = background_pattern_y
        else:
            background_pattern_group.attrs["has_background_pattern"] = False

        pattern_group = f.create_group("pattern")
        try:
            pattern_x = self.pattern_model.pattern._original_x
            pattern_y = self.pattern_model.pattern._original_y
        except (TypeError, AttributeError):
            pattern_x = None
            pattern_y = None
        if pattern_x is not None and pattern_y is not None:
            px = pattern_group.create_dataset("x", pattern_x.shape, dtype="f")
            py = pattern_group.create_dataset("y", pattern_y.shape, dtype="f")
            px[...] = pattern_x
            py[...] = pattern_y
        pattern_group.attrs["pattern_filename"] = self.pattern_model.pattern_filename
        pattern_group.attrs["unit"] = self.pattern_model.unit
        pattern_group.attrs["file_iteration_mode"] = (
            self.pattern_model.file_iteration_mode
        )
        if self.pattern_model.pattern.auto_bkg:
            pattern_group.attrs["auto_background_subtraction"] = True
            auto_background_group = pattern_group.create_group(
                "auto_background_settings"
            )
            auto_bkg = self.pattern_model.pattern.auto_bkg

            if type(auto_bkg) == SmoothBrucknerBackground:
                auto_background_group.attrs["smoothing"] = auto_bkg.smooth_width
                auto_background_group.attrs["iterations"] = auto_bkg.iterations
                auto_background_group.attrs["poly_order"] = auto_bkg.cheb_order

            auto_bkg_roi = self.pattern_model.pattern.auto_bkg_roi
            if auto_bkg_roi is not None:
                auto_background_group.attrs["x_start"] = auto_bkg_roi[0]
                auto_background_group.attrs["x_end"] = auto_bkg_roi[1]
        else:
            pattern_group.attrs["auto_background_subtraction"] = False

        # save map model
        self.map_model.save_in_hdf5(f)

    def load_from_hdf5(self, hdf5_group):
        """
        Loads a configuration from the specified hdf5_group.
        :type hdf5_group: h5py.Group
        """

        f = hdf5_group

        # disable all automatic functions
        self.auto_integrate_pattern = False
        self.auto_integrate_cake = False
        self.auto_save_integrated_pattern = False

        # get working directories
        working_directories = {}
        for key, value in f.get("working_directories").attrs.items():
            if os.path.isdir(value):
                working_directories[key] = value
            else:
                working_directories[key] = ""
        self.working_directories = working_directories

        # load pyFAI parameters
        try:
            calibration_schema_version = f.get("calibration_model").attrs["version"]
            if calibration_schema_version == "2.0":
                # pyFAI parameters are stored as a json string
                pyfai_config = json.loads(
                    f.get("calibration_model").attrs["pyfai_parameters"]
                )
                self.calibration_model.set_pyFAI_config(pyfai_config)

                # polarization factor is stored as an attribute (for some reason not in the pyFAI config)
                self.calibration_model.polarization_factor = f.get(
                    "calibration_model"
                ).attrs["polarization_factor"]
        except (KeyError, ValueError):
            # if the version is not set, we assume that the pyFAI parameters are stored in the old way
            # pyFAI parameters are stored as attributes of the group
            # this is the old way of storing pyFAI parameters
            # and is kept for backwards compatibility
            pyfai_parameters = {}
            pyfai_parameters_group = f.get("calibration_model").get("pyfai_parameters")
            if pyfai_parameters_group is not None:
                for key, value in pyfai_parameters_group.attrs.items():
                    pyfai_parameters[key] = value

            try:
                if pyfai_parameters:
                    self.calibration_model.set_pyFAI(pyfai_parameters)

            except (KeyError, ValueError):
                print("Problem with saved pyFAI calibration parameters")
                pass

        filename = f.get("calibration_model").attrs["calibration_filename"]
        (_, base_name) = os.path.split(filename)
        self.calibration_model.filename = filename
        self.calibration_model.calibration_name = base_name

        try:
            self.correct_solid_angle = f.get("calibration_model").attrs[
                "correct_solid_angle"
            ]
        except KeyError:
            pass

        try:
            distortion_spline_filename = f.get("calibration_model").attrs[
                "distortion_spline_filename"
            ]
            self.calibration_model.load_distortion(distortion_spline_filename)
        except KeyError:
            pass

        # load detector definition
        try:
            detector_mode = f.get("detector").attrs["detector_mode"]
            if detector_mode == DetectorModes.PREDEFINED.value:
                detector_name = f.get("detector").attrs["detector_name"]
                self.calibration_model.load_detector(detector_name)
            elif detector_mode == DetectorModes.NEXUS.value:
                nexus_filename = f.get("detector").attrs["nexus_filename"]
                self.calibration_model.load_detector_from_file(nexus_filename)
        except AttributeError:  # to ensure backwards compatibility
            pass

        # load img_model
        self.img_model._img_data = np.copy(
            f.get("image_model").get("raw_image_data")[...]
        )
        filename = f.get("image_model").attrs["filename"]
        self.img_model.filename = filename

        try:
            self.img_model.file_name_iterator.update_filename(filename)
            self.img_model._directory_watcher.path = os.path.dirname(filename)
        except EnvironmentError:
            pass

        self.img_model.autoprocess = f.get("image_model").attrs["auto_process"]
        self.img_model.autoprocess_changed.emit()
        self.img_model.factor = f.get("image_model").attrs["factor"]

        try:
            self.img_model.series_max = f.get("image_model").attrs["series_max"]
            self.img_model.series_pos = f.get("image_model").attrs["series_pos"]
        except KeyError:
            pass

        if f.get("image_model").attrs["has_background"]:
            self.img_model.background_data = np.copy(
                f.get("image_model").get("background_data")[...]
            )
            self.img_model.background_filename = f.get("image_model").attrs[
                "background_filename"
            ]
            self.img_model.background_scaling = f.get("image_model").attrs[
                "background_scaling"
            ]
            self.img_model.background_offset = f.get("image_model").attrs[
                "background_offset"
            ]

        # load image transformations
        transformation_group = f.get("image_model").get("image_transformations")
        transformation_list = []
        for key, transformation in transformation_group.attrs.items():
            transformation_list.append(transformation)
        self.calibration_model.load_transformations_string_list(transformation_list)
        self.img_model.load_transformations_string_list(transformation_list)

        # load roi data
        if f.get("image_model").attrs["has_roi"]:
            self.roi = tuple(f.get("image_model").get("roi")[...])

        # load mask model
        self.mask_model.set_mask(np.copy(f.get("mask").get("data")[...]))

        # load pattern model
        if f.get("pattern").get("x") and f.get("pattern").get("y"):
            self.pattern_model.set_pattern(
                f.get("pattern").get("x")[...],
                f.get("pattern").get("y")[...],
                f.get("pattern").attrs["pattern_filename"],
                f.get("pattern").attrs["unit"],
            )
            self.pattern_model.file_iteration_mode = f.get("pattern").attrs[
                "file_iteration_mode"
            ]
        self.integration_unit = f.get("general_information").attrs["integration_unit"]

        if f.get("background_pattern").attrs["has_background_pattern"]:
            self.pattern_model.background_pattern = Pattern(
                f.get("background_pattern").get("x")[...],
                f.get("background_pattern").get("y")[...],
                "background_pattern",
            )

        if f.get("pattern").attrs["auto_background_subtraction"]:
            self.pattern_model.pattern.auto_bkg_roi = [
                f.get("pattern").get("auto_background_settings").attrs["x_start"],
                f.get("pattern").get("auto_background_settings").attrs["x_end"],
            ]
            self.pattern_model.pattern.auto_bkg = SmoothBrucknerBackground(
                f.get("pattern").get("auto_background_settings").attrs["smoothing"],
                f.get("pattern").get("auto_background_settings").attrs["iterations"],
                f.get("pattern").get("auto_background_settings").attrs["poly_order"],
            )

        # load general configuration
        if f.get("general_information").attrs["integration_num_points"]:
            self.integration_rad_points = int(
                f.get("general_information").attrs["integration_num_points"]
            )

        # cake parameters:
        self.auto_integrate_cake = f.get("general_information").attrs[
            "auto_integrate_cake"
        ]
        try:
            self.cake_azimuth_points = f.get("general_information").attrs[
                "cake_azimuth_points"
            ]
        except KeyError as e:
            pass
        try:
            if f.get("general_information").attrs["cake_azimuth_range"] == "None":
                self.cake_azimuth_range = None
            else:
                self.cake_azimuth_range = f.get("general_information").attrs[
                    "cake_azimuth_range"
                ]
        except KeyError as e:
            pass

        # mask parameters
        self.use_mask = f.get("general_information").attrs["use_mask"]
        self.transparent_mask = f.get("general_information").attrs["transparent_mask"]

        # corrections
        if f.get("image_model").get("corrections").attrs["has_corrections"]:
            for name, correction_group in (
                f.get("image_model").get("corrections").items()
            ):
                params = {}
                for param, val in correction_group.attrs.items():
                    params[param] = val
                if name == "cbn":
                    tth_array = (
                        180.0 / np.pi * self.calibration_model.tth_array
                    )
                    azi_array = (
                        180.0 / np.pi * self.calibration_model.azi_array
                    )
                    cbn_correction = CbnCorrection(
                        tth_array=tth_array, azi_array=azi_array
                    )

                    cbn_correction.set_params(params)
                    cbn_correction.update()
                    self.img_model.add_img_correction(cbn_correction, name)
                elif name == "oiadac":
                    tth_array = (
                        180.0 / np.pi * self.calibration_model.tth_array
                    )
                    azi_array = (
                        180.0 / np.pi * self.calibration_model.azi_array
                    )
                    oiadac = ObliqueAngleDetectorAbsorptionCorrection(
                        tth_array=tth_array, azi_array=azi_array
                    )

                    oiadac.set_params(params)
                    oiadac.update()
                    self.img_model.add_img_correction(oiadac, name)
                elif name == "transfer":
                    params = {
                        "original_data": correction_group.get("original_data")[...],
                        "original_filename": correction_group.get(
                            "original_data"
                        ).attrs["filename"],
                        "response_data": correction_group.get("response_data")[...],
                        "response_filename": correction_group.get(
                            "response_data"
                        ).attrs["filename"],
                    }

                    self.img_model.transfer_correction.set_params(params)
                    self.img_model.enable_transfer_function()

        # autosave parameters
        self.auto_save_integrated_pattern = f.get("general_information").attrs[
            "auto_save_integrated_pattern"
        ]
        self.integrated_patterns_file_formats = []
        for file_format in f.get("general_information").get(
            "integrated_patterns_file_formats"
        ):
            self.integrated_patterns_file_formats.append(file_format[0].decode("utf-8"))

        if self.calibration_model.is_calibrated:
            self.integrate_image_1d()
        else:
            self.pattern_model.pattern.recalculate_pattern()

        # load map model
        self.map_model.load_from_hdf5(f)
