Create a pseudosection plot of type 2.

        For a given measurement data set, create plots that graphically show
        the data in a 2D color plot. Hereby, x and y coordinates in the plot
        are determined by the current dipole (x-axis) and voltage dipole
        (y-axis) of the corresponding measurement configurations.

        This type of rawdata plot can plot any type of measurement
        configurations, i.e., it is not restricted to certain types of
        configurations such as Dipole-dipole or Wenner configurations. However,
        spatial inferences cannot be made from the plots for all configuration
        types.

        Coordinates are generated by separately sorting the dipoles
        (current/voltage) along the first electrode, and then subsequently
        sorting by the difference (skip) between both electrodes.

        Note that this type of raw data plot does not take into account the
        real electrode spacing of the measurement setup.

        Type 2 plots can show normal and reciprocal data at the same time.
        Hereby the upper left triangle of the plot area usually contains normal
        data, and the lower right triangle contains the corresponding
        reciprocal data. Therefore a quick assessment of normal-reciprocal
        differences can be made by visually comparing the symmetry on the 1:1
        line going from the lower left corner to the upper right corner.

        Note that this interpretation usually only holds for Dipole-Dipole data
        (and the related Wenner configurations).

        Parameters
        ----------

        mid: integer or numpy.ndarray
            Measurement ID of stored measurement data, or a numpy array (size
            N) with data that will be plotted. Must have the same length as
            number of configurations.
        ax: matplotlib.Axes object, optional
            axes object to plot to. If not provided, a new figure and axes
            object will be created and returned
        nocb: bool, optional
            if set to False, don't plot the colorbar
        cblabel: string, optional
            label for the colorbar
        cbmin: float, optional
            colorbar minimum
        cbmax: float, optional
            colorbar maximum
        xlabel: string, optional
            xlabel for the plot
        ylabel: string, optional
            ylabel for the plot
        do_not_saturate: bool, optional
            if set to True, then values outside the colorbar range will not
            saturate with the respective limit colors. Instead, values lower
            than the CB are colored "cyan" and vaues above the CB limit are
            colored "red"
        log10: bool, optional
            if set to True, plot the log10 values of the provided data

        Returns
        -------
        fig:
            figure object
        ax:
            axes object

        Examples
        --------

        You can just supply a data vector to the plot function:

        .. plot::
            :include-source:

            import numpy as np
            import crtomo.configManager as CRConfig
            configs = CRConfig.ConfigManager(nr_of_electrodes=48)
            configs.gen_dipole_dipole(skipc=1, stepc=2)
            measurements = np.random.random(configs.nr_of_configs)
            configs.plot_pseudosection_type2(
                mid=measurements,
            )

        Generate a simple type 2 plot:

        .. plot::
            :include-source:

            import numpy as np
            import crtomo.configManager as CRConfig
            configs = CRConfig.ConfigManager(nr_of_electrodes=48)
            configs.gen_dipole_dipole(skipc=1, stepc=2)
            measurements = np.random.random(configs.nr_of_configs)
            mid = configs.add_measurements(measurements)
            configs.plot_pseudosection_type2(
                mid,
                cblabel='this label',
                xlabel='xlabel',
                ylabel='ylabel',
            )

        You can also supply axes to plot to:

        .. plot::
            :include-source:

            import numpy as np
            from crtomo.mpl_setup import *
            import crtomo.configManager as CRConfig

            configs = CRConfig.ConfigManager(nr_of_electrodes=48)
            configs.gen_dipole_dipole(skipc=1, stepc=2)
            K = configs.compute_K_factors(spacing=1)
            measurements = np.random.random(configs.nr_of_configs)
            mid = configs.add_measurements(measurements)

            fig, axes = plt.subplots(1, 2)

            configs.plot_pseudosection_type2(
                mid,
                ax=axes[0],
                cblabel='this label',
                xlabel='xlabel',
                ylabel='ylabel',
            )
            configs.plot_pseudosection_type2(
                K,
                ax=axes[1],
                cblabel='K factor',
                xlabel='xlabel',
                ylabel='ylabel',
            )
            fig.tight_layout()

def plot_pseudosection_type2(self, mid, **kwargs):
        """Create a pseudosection plot of type 2.

        For a given measurement data set, create plots that graphically show
        the data in a 2D color plot. Hereby, x and y coordinates in the plot
        are determined by the current dipole (x-axis) and voltage dipole
        (y-axis) of the corresponding measurement configurations.

        This type of rawdata plot can plot any type of measurement
        configurations, i.e., it is not restricted to certain types of
        configurations such as Dipole-dipole or Wenner configurations. However,
        spatial inferences cannot be made from the plots for all configuration
        types.

        Coordinates are generated by separately sorting the dipoles
        (current/voltage) along the first electrode, and then subsequently
        sorting by the difference (skip) between both electrodes.

        Note that this type of raw data plot does not take into account the
        real electrode spacing of the measurement setup.

        Type 2 plots can show normal and reciprocal data at the same time.
        Hereby the upper left triangle of the plot area usually contains normal
        data, and the lower right triangle contains the corresponding
        reciprocal data. Therefore a quick assessment of normal-reciprocal
        differences can be made by visually comparing the symmetry on the 1:1
        line going from the lower left corner to the upper right corner.

        Note that this interpretation usually only holds for Dipole-Dipole data
        (and the related Wenner configurations).

        Parameters
        ----------

        mid: integer or numpy.ndarray
            Measurement ID of stored measurement data, or a numpy array (size
            N) with data that will be plotted. Must have the same length as
            number of configurations.
        ax: matplotlib.Axes object, optional
            axes object to plot to. If not provided, a new figure and axes
            object will be created and returned
        nocb: bool, optional
            if set to False, don't plot the colorbar
        cblabel: string, optional
            label for the colorbar
        cbmin: float, optional
            colorbar minimum
        cbmax: float, optional
            colorbar maximum
        xlabel: string, optional
            xlabel for the plot
        ylabel: string, optional
            ylabel for the plot
        do_not_saturate: bool, optional
            if set to True, then values outside the colorbar range will not
            saturate with the respective limit colors. Instead, values lower
            than the CB are colored "cyan" and vaues above the CB limit are
            colored "red"
        log10: bool, optional
            if set to True, plot the log10 values of the provided data

        Returns
        -------
        fig:
            figure object
        ax:
            axes object

        Examples
        --------

        You can just supply a data vector to the plot function:

        .. plot::
            :include-source:

            import numpy as np
            import crtomo.configManager as CRConfig
            configs = CRConfig.ConfigManager(nr_of_electrodes=48)
            configs.gen_dipole_dipole(skipc=1, stepc=2)
            measurements = np.random.random(configs.nr_of_configs)
            configs.plot_pseudosection_type2(
                mid=measurements,
            )

        Generate a simple type 2 plot:

        .. plot::
            :include-source:

            import numpy as np
            import crtomo.configManager as CRConfig
            configs = CRConfig.ConfigManager(nr_of_electrodes=48)
            configs.gen_dipole_dipole(skipc=1, stepc=2)
            measurements = np.random.random(configs.nr_of_configs)
            mid = configs.add_measurements(measurements)
            configs.plot_pseudosection_type2(
                mid,
                cblabel='this label',
                xlabel='xlabel',
                ylabel='ylabel',
            )

        You can also supply axes to plot to:

        .. plot::
            :include-source:

            import numpy as np
            from crtomo.mpl_setup import *
            import crtomo.configManager as CRConfig

            configs = CRConfig.ConfigManager(nr_of_electrodes=48)
            configs.gen_dipole_dipole(skipc=1, stepc=2)
            K = configs.compute_K_factors(spacing=1)
            measurements = np.random.random(configs.nr_of_configs)
            mid = configs.add_measurements(measurements)

            fig, axes = plt.subplots(1, 2)

            configs.plot_pseudosection_type2(
                mid,
                ax=axes[0],
                cblabel='this label',
                xlabel='xlabel',
                ylabel='ylabel',
            )
            configs.plot_pseudosection_type2(
                K,
                ax=axes[1],
                cblabel='K factor',
                xlabel='xlabel',
                ylabel='ylabel',
            )
            fig.tight_layout()




        """
        c = self.configs

        AB_ids = self._get_unique_identifiers(c[:, 0:2])
        MN_ids = self._get_unique_identifiers(c[:, 2:4])

        ab_sorted = np.sort(c[:, 0:2], axis=1)
        mn_sorted = np.sort(c[:, 2:4], axis=1)

        AB_coords = [
            AB_ids[x] for x in
            (ab_sorted[:, 0] * 1e5 + ab_sorted[:, 1]).astype(int)
        ]
        MN_coords = [
            MN_ids[x] for x in
            (mn_sorted[:, 0] * 1e5 + mn_sorted[:, 1]).astype(int)
        ]

        # check for duplicate positions
        ABMN_coords = np.vstack((AB_coords, MN_coords)).T.copy()
        _, counts = np.unique(
            ABMN_coords.view(
                ABMN_coords.dtype.descr * 2
            ),
            return_counts=True,
        )
        if np.any(counts > 1):
            print('found duplicate coordinates!')
            duplicate_configs = np.where(counts > 1)[0]
            print('duplicate configs:')
            print('A B M N')
            for i in duplicate_configs:
                print(c[i, :])

        # prepare matrix
        if isinstance(mid, int):
            plot_values = self.measurements[mid]
        elif isinstance(mid, np.ndarray):
            plot_values = np.squeeze(mid)
        else:
            raise Exception('Data in parameter "mid" not understood')

        if kwargs.get('log10', False):
            plot_values = np.log10(plot_values)

        C = np.zeros((len(MN_ids.items()), len(AB_ids))) * np.nan
        C[MN_coords, AB_coords] = plot_values

        # for display purposes, reverse the first dimension
        C = C[::-1, :]

        ax = kwargs.get('ax', None)
        if ax is None:
            fig, ax = plt.subplots(1, 1, figsize=(15 / 2.54, 10 / 2.54))
        fig = ax.get_figure()

        cmap = mpl.cm.get_cmap('viridis')
        if kwargs.get('do_not_saturate', False):
            cmap.set_over(
                color='r'
            )
            cmap.set_under(
                color='c'
            )
        im = ax.matshow(
            C,
            interpolation='none',
            cmap=cmap,
            aspect='auto',
            vmin=kwargs.get('cbmin', None),
            vmax=kwargs.get('cbmax', None),
            extent=[
                0, max(AB_coords),
                0, max(MN_coords),
            ],
        )

        max_xy = max((max(AB_coords), max(MN_coords)))
        ax.plot(
            (0, max_xy),
            (0, max_xy),
            '-',
            color='k',
            linewidth=1.0,
        )

        if not kwargs.get('nocb', False):
            cb = fig.colorbar(im, ax=ax)
            cb.set_label(
                kwargs.get('cblabel', '')
            )

        ax.set_xlabel(
            kwargs.get('xlabel', 'current dipoles')
        )
        ax.set_ylabel(
            kwargs.get('ylabel', 'voltage dipoles')
        )

        return fig, ax