Auxiliary function to display a numpy 2d array.

    Parameters
    ----------
    image2d : 2d numpy array, float
        2d image to be displayed.
    title : string
        Plot title.
    cbar_label : string
        Color bar label.
    cbar_orientation : string
        Color bar orientation: valid options are 'horizontal' or
        'vertical'.
    show : bool
        If True, the function shows the displayed image. Otherwise
        the function just invoke the plt.imshow() function and
        plt.show() is expected to be executed outside.
    z1z2 : tuple of floats, string or None
        Background and foreground values. If None, zcuts are employed.
    cmap : string
        Color map to be employed.
    image_bbox : tuple (4 integers)
        Image rectangle to be displayed, with indices given by
        (nc1,nc2,ns1,ns2), which correspond to the numpy array:
        image2d[(ns1-1):ns2,(nc1-1):nc2].
    first_pixel : tuple (2 integers)
        (x0,y0) coordinates of pixel at origin.
    crpix1 : float or None
        CRPIX1 parameter corresponding to wavelength calibration in
        the X direction.
    crval1 : float or None
        CRVAL1 parameter corresponding to wavelength calibration in
        the X direction.
    cdelt1 : float or None
        CDELT1 parameter corresponding to wavelength calibration in
        the X direction.
    ds9regfile : file handler
        Ds9 region file to be overplotted.
    geometry : tuple (4 integers) or None
        x, y, dx, dy values employed to set the window geometry.
    tight_layout : bool
        If True, and show=True, a tight display layout is set.
    debugplot : int
        Determines whether intermediate computations and/or plots
        are displayed. The valid codes are defined in
        numina.array.display.pause_debugplot
    using_jupyter : bool
        If True, this function is called from a jupyter notebook.

    Returns
    -------
    ax : axes object
        Matplotlib axes instance. This value is returned only when
        'show' is False.

def ximshow(image2d, title=None, show=True,
            cbar_label=None, cbar_orientation=None,
            z1z2=None, cmap="hot",
            image_bbox=None, first_pixel=(1,1),
            crpix1=None, crval1=None, cdelt1=None,
            ds9regfile=None,
            geometry=(0, 0, 640, 480), tight_layout=True,
            debugplot=0, using_jupyter=False):
    """Auxiliary function to display a numpy 2d array.

    Parameters
    ----------
    image2d : 2d numpy array, float
        2d image to be displayed.
    title : string
        Plot title.
    cbar_label : string
        Color bar label.
    cbar_orientation : string
        Color bar orientation: valid options are 'horizontal' or
        'vertical'.
    show : bool
        If True, the function shows the displayed image. Otherwise
        the function just invoke the plt.imshow() function and
        plt.show() is expected to be executed outside.
    z1z2 : tuple of floats, string or None
        Background and foreground values. If None, zcuts are employed.
    cmap : string
        Color map to be employed.
    image_bbox : tuple (4 integers)
        Image rectangle to be displayed, with indices given by
        (nc1,nc2,ns1,ns2), which correspond to the numpy array:
        image2d[(ns1-1):ns2,(nc1-1):nc2].
    first_pixel : tuple (2 integers)
        (x0,y0) coordinates of pixel at origin.
    crpix1 : float or None
        CRPIX1 parameter corresponding to wavelength calibration in
        the X direction.
    crval1 : float or None
        CRVAL1 parameter corresponding to wavelength calibration in
        the X direction.
    cdelt1 : float or None
        CDELT1 parameter corresponding to wavelength calibration in
        the X direction.
    ds9regfile : file handler
        Ds9 region file to be overplotted.
    geometry : tuple (4 integers) or None
        x, y, dx, dy values employed to set the window geometry.
    tight_layout : bool
        If True, and show=True, a tight display layout is set.
    debugplot : int
        Determines whether intermediate computations and/or plots
        are displayed. The valid codes are defined in
        numina.array.display.pause_debugplot
    using_jupyter : bool
        If True, this function is called from a jupyter notebook.

    Returns
    -------
    ax : axes object
        Matplotlib axes instance. This value is returned only when
        'show' is False.

    """

    from numina.array.display.matplotlib_qt import plt

    if not show and using_jupyter:
        plt.ioff()

    # protections
    if type(image2d) is not np.ndarray:
        raise ValueError("image2d=" + str(image2d) +
                         " must be a numpy.ndarray")
    elif image2d.ndim is not 2:
        raise ValueError("image2d.ndim=" + str(image2d.dim) +
                         " must be 2")

    naxis2_, naxis1_ = image2d.shape

    # check if wavelength calibration is provided
    wavecalib = crval1 is not None and cdelt1 is not None

    # read bounding box limits
    if image_bbox is None:
        nc1 = 1
        nc2 = naxis1_
        ns1 = 1
        ns2 = naxis2_
    else:
        nc1, nc2, ns1, ns2 = image_bbox
        if 1 <= nc1 <= nc2 <= naxis1_:
            pass
        else:
            raise ValueError("Invalid bounding box limits")
        if 1 <= ns1 <= ns2 <= naxis2_:
            pass
        else:
            raise ValueError("Invalid bounding box limits")

    def get_current_zoom(ax_image, debug=False):
        """Return subimage corresponding to current zoom.

        Parameters
        ----------
        ax_image : axes
            Image axes.
        debug : bool
            If True, the image corners are printed.

        Returns
        -------
        subimage : numpy array (floats)
            Subimage.

        """

        xmin_image, xmax_image = ax_image.get_xlim()
        ymin_image, ymax_image = ax_image.get_ylim()
        ixmin = int(xmin_image + 0.5)
        ixmax = int(xmax_image + 0.5)
        iymin = int(ymin_image + 0.5)
        iymax = int(ymax_image + 0.5)
        ixmin -= first_pixel[0] - 1
        ixmax -= first_pixel[0] - 1
        iymin -= first_pixel[1] - 1
        iymax -= first_pixel[1] - 1
        if ixmin < nc1:
            ixmin = nc1
        if ixmin > nc2:
            ixmin = nc2
        if ixmax < nc1:
            ixmax = nc1
        if ixmax > nc2:
            ixmax = nc2
        if iymin < ns1:
            iymin = ns1
        if iymin > ns2:
            iymin = ns2
        if iymax < ns1:
            iymax = ns1
        if iymax > ns2:
            iymax = ns2
        if debug:
            print("\n>>> xmin, xmax, ymin, ymax [pixels; origin (1,1)]:",
                  ixmin, ixmax, iymin, iymax)
        return image2d[(iymin-1):iymax, (ixmin-1):ixmax]

    def keypress(event):
        """Deal with keyboard events, allowing the update of vmin and vmax.

        Note that a call to raw_input() is not allowed within this
        function since, in that case, the following runtime error
        is raised: can't re-enter readline

        For that reason, the new vmin and vmax values should be
        entered blindly.

        To avoid collisions with navigation keyboard shortcuts,
        check the table available at:
        http://matplotlib.org/users/navigation_toolbar.html

        """

        global dum_str
        global dum_par
        if event.key == "?":
            print("""
Keyword events
==============
Home/Reset......................: h or r or home
Back............................: c or left arrow or backspace
Forward.........................: v or right arrow
Pan/Zoom........................: p
Zoom-to-rect....................: o
Save............................: ctrl + s
Toggle fullscreen...............: ctrl + f
Close plot......................: ctrl + w
Set zscale......................: /
Set bg=min and fg=max values....: ,
Display statistical summary.....: .
Set foreground by keyboard......: m
Set background by keyboard......: n
Activate/deactivate ds9 regions.: a
Constrain pan/zoom to x axis....: hold x when panning/zooming with mouse
Constrain pan/zoom to y axis....: hold y when panning/zooming with mouse
Preserve aspect ratio...........: hold CONTROL when panning/zooming with mouse
Toggle grid.....................: g when mouse is over an axes
Toggle x axis scale (log/linear): L or k when mouse is over an axes
Toggle y axis scale (log/linear): l when mouse is over an axes
            """)
        elif event.key == "/":
            subimage2d = get_current_zoom(ax, debug=True)
            new_vmin, new_vmax = ZScaleInterval().get_limits(subimage2d)
            print(">>> setting cuts to vmin=" + str(new_vmin) +
                  " and vmax=" + str(new_vmax))
            im_show.set_clim(vmin=new_vmin)
            im_show.set_clim(vmax=new_vmax)
            dum_str = ''
            dum_par = ''
            plt.show(block=False)
            plt.pause(0.001)
        elif event.key == ",":
            subimage2d = get_current_zoom(ax, debug=True)
            new_vmin = subimage2d.min()
            new_vmax = subimage2d.max()
            print(">>> setting cuts to vmin=" + str(new_vmin) +
                  " and vmax=" + str(new_vmax))
            im_show.set_clim(vmin=new_vmin)
            im_show.set_clim(vmax=new_vmax)
            dum_str = ''
            dum_par = ''
            plt.show(block=False)
            plt.pause(0.001)
        elif event.key == ".":
            subimage2d = get_current_zoom(ax, debug=True)
            summary(subimage2d.flatten(), debug=True)
        elif event.key == "n":
            print("Type (blindly!) vmin <return>")
            dum_str = ''
            dum_par = "vmin"
        elif event.key == "m":
            print("Type (blindly!) vmax <return>")
            dum_str = ''
            dum_par = "vmax"
        elif event.key == "enter":
            if dum_par == "vmin":
                try:
                    new_vmin = float(dum_str)
                except ValueError:
                    print("Invalid vmin=" + dum_str)
                    dum_str = ''
                    print("Type again (blindly!) vmin <return>")
                else:
                    print("Setting vmin=" + dum_str)
                    im_show.set_clim(vmin=new_vmin)
                    dum_str = ''
                    dum_par = ''
                    plt.show(block=False)
                    plt.pause(0.001)
            elif dum_par == "vmax":
                try:
                    new_vmax = float(dum_str)
                except ValueError:
                    print("Invalid vmax=" + dum_str)
                    dum_str = ''
                    print("Type again (blindly!) vmax <return>")
                else:
                    print("Setting vmax=" + dum_str)
                    im_show.set_clim(vmax=new_vmax)
                    dum_str = ''
                    dum_par = ''
                    plt.show(block=False)
                    plt.pause(0.001)
        else:
            if dum_str == '':
                dum_str = event.key
            else:
                dum_str += event.key

    # plot limits
    xmin = float(nc1) - 0.5 + (first_pixel[0] - 1)
    xmax = float(nc2) + 0.5 + (first_pixel[0] - 1)
    ymin = float(ns1) - 0.5 + (first_pixel[1] - 1)
    ymax = float(ns2) + 0.5 + (first_pixel[1] - 1)

    # display image
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.autoscale(False)
    ax.set_xlabel('image pixel in the X direction')
    ax.set_ylabel('image pixel in the Y direction')
    ax.set_xlim(xmin, xmax)
    ax.set_ylim(ymin, ymax)
    ax.grid(False)
    if z1z2 is None:
        z1, z2 = ZScaleInterval().get_limits(
            image2d[(ns1 - 1):ns2, (nc1 - 1):nc2]
        )
    elif z1z2 == "minmax":
        z1 = image2d[(ns1 - 1):ns2, (nc1 - 1):nc2].min()
        z2 = image2d[(ns1 - 1):ns2, (nc1 - 1):nc2].max()
    else:
        z1, z2 = z1z2
    im_show = plt.imshow(image2d[(ns1 - 1):ns2, (nc1 - 1):nc2],
                         cmap=cmap, aspect='auto',
                         vmin=z1, vmax=z2,
                         interpolation='nearest', origin='low',
                         extent=[xmin, xmax, ymin, ymax])
    if cbar_label is None:
        cbar_label = "Number of counts"
    if cbar_orientation is None:
        cbar_orientation = "horizontal"
    plt.colorbar(im_show, shrink=1.0, label=cbar_label,
                 orientation=cbar_orientation)
    if title is not None:
        if wavecalib:
            ax.set_title(title + "\n\n")
        else:
            ax.set_title(title)

    if ds9regfile is not None:
        overplot_ds9reg(ds9regfile.name, ax)

    # set the geometry
    set_window_geometry(geometry)

    # connect keypress event with function responsible for
    # updating vmin and vmax
    fig.canvas.mpl_connect('key_press_event', keypress)

    # wavelength scale
    if wavecalib:
        if crpix1 is None:
            crpix1 = 1.0
        xminwv = crval1 + (xmin - crpix1) * cdelt1
        xmaxwv = crval1 + (xmax - crpix1) * cdelt1
        ax2 = ax.twiny()
        ax2.grid(False)
        ax2.set_xlim(xminwv, xmaxwv)
        ax2.set_xlabel('Wavelength (Angstroms)')
        ax.set_xlim(xmin, xmax)
        ax.set_ylim(ymin, ymax)

    # show plot or return axes
    if show:
        pause_debugplot(debugplot, pltshow=show, tight_layout=tight_layout)
    else:
        if tight_layout:
            plt.tight_layout()
        # return axes
        if using_jupyter:
            plt.ion()
        return ax