# Source code for dipy.viz.panel

    import itertools
    import warnings

    import numpy as np

    from dipy.testing.decorators import warning_for_keywords
    from dipy.utils.optpkg import optional_package
    from dipy.viz.gmem import GlobalHorizon

    fury, have_fury, setup_module = optional_package("fury", min_version="0.10.0")

    if have_fury:
        from dipy.viz import actor, colormap, ui

    @warning_for_keywords()
    def build_label(text, *, font_size=18, bold=False):
        """Simple utility function to build labels

        Parameters
        ----------
        text : str
        font_size : int
        bold : bool

        Returns
        -------
        label : TextBlock2D
        """

        label = ui.TextBlock2D()
        label.message = text
        label.font_size = font_size
        label.font_family = "Arial"
        label.justification = "left"
        label.bold = bold
        label.italic = False
        label.shadow = False
        label.actor.GetTextProperty().SetBackgroundColor(0, 0, 0)
        label.actor.GetTextProperty().SetBackgroundOpacity(0.0)
        label.color = (0.7, 0.7, 0.7)

        return label

    def _color_slider(slider):
        slider.default_color = (1, 0.5, 0)
        slider.track.color = (0.8, 0.3, 0)
        slider.active_color = (0.9, 0.4, 0)
        slider.handle.color = (1, 0.5, 0)

    def _color_dslider(slider):
        slider.default_color = (1, 0.5, 0)
        slider.track.color = (0.8, 0.3, 0)
        slider.active_color = (0.9, 0.4, 0)
        slider.handles[0].color = (1, 0.5, 0)
        slider.handles[1].color = (1, 0.5, 0)

    @warning_for_keywords()
    def slicer_panel(
        scene,
        iren,
        *,
        data=None,
        affine=None,
        world_coords=False,
        pam=None,
        mask=None,
        mem=None,
    ):
        """Slicer panel with slicer included

        Parameters
        ----------
        scene : Scene
            Scene.
        iren : Interactor
            Interactor.
        data : 3d ndarray
            Data to be sliced.
        affine : 4x4 ndarray
            Affine matrix.
        world_coords : bool
            If True then the affine is applied.

        peaks : PeaksAndMetrics
            Default None
        mem :

        Returns
        -------
        panel : Panel

        """
        if mem is None:
            mem = GlobalHorizon()

        orig_shape = data.shape
        print("Original shape", orig_shape)
        ndim = data.ndim
        tmp = data
        if ndim == 4:
            if orig_shape[-1] > 3:
                orig_shape = orig_shape[:3]
                # Sometimes, first volume is null, so we try the next one.
                for i in range(orig_shape[-1]):
                    tmp = data[..., i]
                    value_range = np.percentile(data[..., i], q=[2, 98])
                    if np.sum(np.diff(value_range)) != 0:
                        break
            if orig_shape[-1] == 3:
                value_range = (0, 1.0)
                mem.slicer_rgb = True
        if ndim == 3:
            value_range = np.percentile(tmp, q=[2, 98])

        if np.sum(np.diff(value_range)) == 0:
            msg = "Your data does not have any contrast. "
            msg += "Please, check the value range of your data."
            warnings.warn(msg, stacklevel=2)

        if not world_coords:
            affine = np.eye(4)

        image_actor_z = actor.slicer(
            tmp,
            affine=affine,
            value_range=value_range,
            interpolation="nearest",
            picking_tol=0.025,
        )

        tmp_new = image_actor_z.resliced_array()

        if len(data.shape) == 4:
            if data.shape[-1] == 3:
                print("Resized to RAS shape ", tmp_new.shape)
            else:
                print("Resized to RAS shape ", tmp_new.shape + (data.shape[-1],))
        else:
            print("Resized to RAS shape ", tmp_new.shape)

        shape = tmp_new.shape

        if pam is not None:
            peaks_actor_z = actor.peak_slicer(
                pam.peak_dirs, peaks_values=None, mask=mask, affine=affine, colors=None
            )

        slicer_opacity = 1.0
        image_actor_z.opacity(slicer_opacity)

        image_actor_x = image_actor_z.copy()
        x_midpoint = int(np.round(shape[0] / 2))
        image_actor_x.display_extent(
            x_midpoint, x_midpoint, 0, shape[1] - 1, 0, shape[2] - 1
        )

        image_actor_y = image_actor_z.copy()
        y_midpoint = int(np.round(shape[1] / 2))
        image_actor_y.display_extent(
            0, shape[0] - 1, y_midpoint, y_midpoint, 0, shape[2] - 1
        )

        scene.add(image_actor_z)
        scene.add(image_actor_x)
        scene.add(image_actor_y)

        if pam is not None:
            scene.add(peaks_actor_z)

        line_slider_z = ui.LineSlider2D(
            min_value=0,
            max_value=shape[2] - 1,
            initial_value=shape[2] / 2,
            text_template="{value:.0f}",
            length=140,
        )

        _color_slider(line_slider_z)

        def change_slice_z(slider):
            z = int(np.round(slider.value))
            mem.slicer_curr_actor_z.display_extent(0, shape[0] - 1, 0, shape[1] - 1, z, z)
            if pam is not None:
                mem.slicer_peaks_actor_z.display_extent(
                    0, shape[0] - 1, 0, shape[1] - 1, z, z
                )
            mem.slicer_curr_z = z
            scene.reset_clipping_range()

        line_slider_x = ui.LineSlider2D(
            min_value=0,
            max_value=shape[0] - 1,
            initial_value=shape[0] / 2,
            text_template="{value:.0f}",
            length=140,
        )

        _color_slider(line_slider_x)

        def change_slice_x(slider):
            x = int(np.round(slider.value))
            mem.slicer_curr_actor_x.display_extent(x, x, 0, shape[1] - 1, 0, shape[2] - 1)
            scene.reset_clipping_range()
            mem.slicer_curr_x = x
            mem.window_timer_cnt += 100

        line_slider_y = ui.LineSlider2D(
            min_value=0,
            max_value=shape[1] - 1,
            initial_value=shape[1] / 2,
            text_template="{value:.0f}",
            length=140,
        )

        _color_slider(line_slider_y)

        def change_slice_y(slider):
            y = int(np.round(slider.value))

            mem.slicer_curr_actor_y.display_extent(0, shape[0] - 1, y, y, 0, shape[2] - 1)
            scene.reset_clipping_range()
            mem.slicer_curr_y = y

        # TODO there is some small bug when starting the app the handles
        # are sitting a bit low
        double_slider = ui.LineDoubleSlider2D(
            length=140,
            initial_values=value_range,
            min_value=tmp.min(),
            max_value=tmp.max(),
            shape="square",
        )

        _color_dslider(double_slider)

        def apply_colormap(r1, r2):
            if mem.slicer_rgb:
                return

            if mem.slicer_colormap == "disting":
                # use distinguishable colors
                rgb = colormap.distinguishable_colormap(nb_colors=256)
                rgb = np.asarray(rgb)
            else:
                # use matplotlib colormaps
                rgb = colormap.create_colormap(
                    np.linspace(r1, r2, 256), name=mem.slicer_colormap, auto=True
                )
            N = rgb.shape[0]

            lut = colormap.LookupTable()
            lut.SetNumberOfTableValues(N)
            lut.SetRange(r1, r2)
            for i in range(N):
                r, g, b = rgb[i]
                lut.SetTableValue(i, r, g, b)
            lut.SetRampToLinear()
            lut.Build()

            mem.slicer_curr_actor_z.output.SetLookupTable(lut)
            mem.slicer_curr_actor_z.output.Update()

        def on_change_ds(slider):
            values = slider._values
            r1, r2 = values
            apply_colormap(r1, r2)

        # TODO trying to see why there is a small bug in double slider
        # double_slider.left_disk_value = 0
        # double_slider.right_disk_value = 98

        # double_slider.update(0)
        # double_slider.update(1)

        double_slider.on_change = on_change_ds

        opacity_slider = ui.LineSlider2D(
            min_value=0.0,
            max_value=1.0,
            initial_value=slicer_opacity,
            length=140,
            text_template="{ratio:.0%}",
        )

        _color_slider(opacity_slider)

        def change_opacity(slider):
            slicer_opacity = slider.value
            mem.slicer_curr_actor_x.opacity(slicer_opacity)
            mem.slicer_curr_actor_y.opacity(slicer_opacity)
            mem.slicer_curr_actor_z.opacity(slicer_opacity)

        volume_slider = ui.LineSlider2D(
            min_value=0,
            max_value=data.shape[-1] - 1,
            initial_value=0,
            length=140,
            text_template="{value:.0f}",
            shape="square",
        )

        _color_slider(volume_slider)

        def change_volume(istyle, obj, slider):
            vol_idx = int(np.round(slider.value))
            mem.slicer_vol_idx = vol_idx

            scene.rm(mem.slicer_curr_actor_x)
            scene.rm(mem.slicer_curr_actor_y)
            scene.rm(mem.slicer_curr_actor_z)

            tmp = data[..., vol_idx]
            image_actor_z = actor.slicer(
                tmp,
                affine=affine,
                value_range=value_range,
                interpolation="nearest",
                picking_tol=0.025,
            )

            tmp_new = image_actor_z.resliced_array()
            mem.slicer_vol = tmp_new

            z = mem.slicer_curr_z
            image_actor_z.display_extent(0, shape[0] - 1, 0, shape[1] - 1, z, z)

            mem.slicer_curr_actor_z = image_actor_z
            mem.slicer_curr_actor_x = image_actor_z.copy()

            if pam is not None:
                mem.slicer_peaks_actor_z = peaks_actor_z

            x = mem.slicer_curr_x
            mem.slicer_curr_actor_x.display_extent(x, x, 0, shape[1] - 1, 0, shape[2] - 1)

            mem.slicer_curr_actor_y = image_actor_z.copy()
            y = mem.slicer_curr_y
            mem.slicer_curr_actor_y.display_extent(0, shape[0] - 1, y, y, 0, shape[2] - 1)

            mem.slicer_curr_actor_z.AddObserver(
                "LeftButtonPressEvent", left_click_picker_callback, 1.0
            )
            mem.slicer_curr_actor_x.AddObserver(
                "LeftButtonPressEvent", left_click_picker_callback, 1.0
            )
            mem.slicer_curr_actor_y.AddObserver(
                "LeftButtonPressEvent", left_click_picker_callback, 1.0
            )
            scene.add(mem.slicer_curr_actor_z)
            scene.add(mem.slicer_curr_actor_x)
            scene.add(mem.slicer_curr_actor_y)

            if pam is not None:
                scene.add(mem.slicer_peaks_actor_z)

            r1, r2 = double_slider._values
            apply_colormap(r1, r2)

            istyle.force_render()

        def left_click_picker_callback(obj, ev):
            """Get the value of the clicked voxel and show it in the panel."""

            event_pos = iren.GetEventPosition()

            obj.picker.Pick(event_pos[0], event_pos[1], 0, scene)

            i, j, k = obj.picker.GetPointIJK()
            res = mem.slicer_vol[i, j, k]
            try:
                message = f"{res:.3f}"
            except TypeError:
                message = f"{res[0]:.3f} {res[1]:.3f} {res[2]:.3f}"
            picker_label.message = f"({str(i)}, {str(j)}, {str(k)}) {message}"

        mem.slicer_vol_idx = 0
        mem.slicer_vol = tmp_new
        mem.slicer_curr_actor_x = image_actor_x
        mem.slicer_curr_actor_y = image_actor_y
        mem.slicer_curr_actor_z = image_actor_z

        if pam is not None:
            # change_volume.peaks_actor_z = peaks_actor_z
            mem.slicer_peaks_actor_z = peaks_actor_z

        mem.slicer_curr_actor_x.AddObserver(
            "LeftButtonPressEvent", left_click_picker_callback, 1.0
        )
        mem.slicer_curr_actor_y.AddObserver(
            "LeftButtonPressEvent", left_click_picker_callback, 1.0
        )
        mem.slicer_curr_actor_z.AddObserver(
            "LeftButtonPressEvent", left_click_picker_callback, 1.0
        )

        if pam is not None:
            mem.slicer_peaks_actor_z.AddObserver(
                "LeftButtonPressEvent", left_click_picker_callback, 1.0
            )

        mem.slicer_curr_x = int(np.round(shape[0] / 2))
        mem.slicer_curr_y = int(np.round(shape[1] / 2))
        mem.slicer_curr_z = int(np.round(shape[2] / 2))

        line_slider_x.on_change = change_slice_x
        line_slider_y.on_change = change_slice_y
        line_slider_z.on_change = change_slice_z

        double_slider.on_change = on_change_ds

        opacity_slider.on_change = change_opacity

        volume_slider.handle_events(volume_slider.handle.actor)
        volume_slider.on_left_mouse_button_released = change_volume

        line_slider_label_x = build_label(text="X Slice")
        line_slider_label_x.visibility = True
        x_counter = itertools.count()

        def label_callback_x(obj, event):
            line_slider_label_x.visibility = not line_slider_label_x.visibility
            line_slider_x.set_visibility(line_slider_label_x.visibility)
            cnt = next(x_counter)
            if line_slider_label_x.visibility and cnt > 0:
                scene.add(mem.slicer_curr_actor_x)
            else:
                scene.rm(mem.slicer_curr_actor_x)
            iren.Render()

        line_slider_label_x.actor.AddObserver("LeftButtonPressEvent", label_callback_x, 1.0)

        line_slider_label_y = build_label(text="Y Slice")
        line_slider_label_y.visibility = True
        y_counter = itertools.count()

        def label_callback_y(obj, event):
            line_slider_label_y.visibility = not line_slider_label_y.visibility
            line_slider_y.set_visibility(line_slider_label_y.visibility)
            cnt = next(y_counter)
            if line_slider_label_y.visibility and cnt > 0:
                scene.add(mem.slicer_curr_actor_y)
            else:
                scene.rm(mem.slicer_curr_actor_y)
            iren.Render()

        line_slider_label_y.actor.AddObserver("LeftButtonPressEvent", label_callback_y, 1.0)

        line_slider_label_z = build_label(text="Z Slice")
        line_slider_label_z.visibility = True
        z_counter = itertools.count()

        def label_callback_z(obj, event):
            line_slider_label_z.visibility = not line_slider_label_z.visibility
            line_slider_z.set_visibility(line_slider_label_z.visibility)
            cnt = next(z_counter)
            if line_slider_label_z.visibility and cnt > 0:
                scene.add(mem.slicer_curr_actor_z)
            else:
                scene.rm(mem.slicer_curr_actor_z)

            iren.Render()

        line_slider_label_z.actor.AddObserver("LeftButtonPressEvent", label_callback_z, 1.0)

        opacity_slider_label = build_label(text="Opacity")
        volume_slider_label = build_label(text="Volume")
        picker_label = build_label(text="")
        double_slider_label = build_label(text="Colormap")
        slicer_panel_label = build_label(text="Slicer panel", bold=True)

        def label_colormap_callback(obj, event):
            if mem.slicer_colormap_cnt == len(mem.slicer_colormaps) - 1:
                mem.slicer_colormap_cnt = 0
            else:
                mem.slicer_colormap_cnt += 1

            cnt = mem.slicer_colormap_cnt
            mem.slicer_colormap = mem.slicer_colormaps[cnt]
            double_slider_label.message = mem.slicer_colormap
            values = double_slider._values
            r1, r2 = values
            apply_colormap(r1, r2)
            iren.Render()

        double_slider_label.actor.AddObserver(
            "LeftButtonPressEvent", label_colormap_callback, 1.0
        )

        # volume_slider.on_right_mouse_button_released = change_volume2
        def label_opacity_callback(obj, event):
            if opacity_slider.value == 0:
                opacity_slider.value = 100
                opacity_slider.update()
                slicer_opacity = 1
            else:
                opacity_slider.value = 0
                opacity_slider.update()
                slicer_opacity = 0
            mem.slicer_curr_actor_x.opacity(slicer_opacity)
            mem.slicer_curr_actor_y.opacity(slicer_opacity)
            mem.slicer_curr_actor_z.opacity(slicer_opacity)
            iren.Render()

        opacity_slider_label.actor.AddObserver(
            "LeftButtonPressEvent", label_opacity_callback, 1.0
        )

        if data.ndim == 4:
            panel_size = (320, 400 + 100)
        if data.ndim == 3:
            panel_size = (320, 300 + 100)

        panel = ui.Panel2D(
            size=panel_size, position=(870, 10), color=(1, 1, 1), opacity=0.1, align="right"
        )

        ys = np.linspace(0, 1, 10)

        panel.add_element(line_slider_z, coords=(0.42, ys[1]))
        panel.add_element(line_slider_y, coords=(0.42, ys[2]))
        panel.add_element(line_slider_x, coords=(0.42, ys[3]))
        panel.add_element(opacity_slider, coords=(0.42, ys[4]))
        panel.add_element(double_slider, coords=(0.42, (ys[7] + ys[8]) / 2.0))

        if data.ndim == 4:
            if data.shape[-1] > 3:
                panel.add_element(volume_slider, coords=(0.42, ys[6]))

        panel.add_element(line_slider_label_z, coords=(0.1, ys[1]))
        panel.add_element(line_slider_label_y, coords=(0.1, ys[2]))
        panel.add_element(line_slider_label_x, coords=(0.1, ys[3]))
        panel.add_element(opacity_slider_label, coords=(0.1, ys[4]))
        panel.add_element(double_slider_label, coords=(0.1, (ys[7] + ys[8]) / 2.0))

        if data.ndim == 4:
            if data.shape[-1] > 3:
                panel.add_element(volume_slider_label, coords=(0.1, ys[6]))

        panel.add_element(picker_label, coords=(0.2, ys[5]))

        panel.add_element(slicer_panel_label, coords=(0.05, 0.9))

        scene.add(panel)

        # initialize colormap
        r1, r2 = value_range
        apply_colormap(r1, r2)

        return panel