Source code for fluidimage.gui.imviewer

"""Simple viewer adapted for fluid images (:mod:`fluidimage.gui.imviewer`)
==========================================================================

Coded with matplotlib GUI!

.. autoclass:: ImageViewer
   :members:
   :private-members:

"""

import argparse
import os
from glob import glob

import matplotlib.pyplot as plt

import fluidimage
from fluiddyn.io.image import imread
from fluiddyn.util import time_as_str
from fluiddyn.util.serieofarrays import SerieOfArraysFromFiles
from fluidimage.gui.base_matplotlib import AppMatplotlibWidgets
from fluidimage.util import safe_eval

extensions = ["png", "tif", "tiff", "jpg", "jpeg", "bmp", "cine"]
extensions = ["." + ext for ext in extensions]

debug = False


def _print_debug(*args):
    if debug:
        print(*args)


def check_image(path):
    return any([path.endswith(ext) for ext in extensions])


size_button = 0.06
sep_button = 0.02
x0 = 0.3

name_buttons = ["-n", "-1", "+1", "+n"]

x_buttons = [
    x0 + i * (size_button + sep_button) for i in range(len(name_buttons))
]


def parse_args():
    parser = argparse.ArgumentParser(
        description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
    )
    parser.add_argument(
        "path",
        help="Path file or directory.",
        type=str,
        nargs="?",
        default=os.getcwd(),
    )
    parser.add_argument(
        "-cm", "--colormap", help="colormap", type=str, default=""
    )
    parser.add_argument("-v", "--verbose", help="verbose mode", action="count")
    parser.add_argument(
        "-V",
        "--version",
        help="Print fluidimage version and exit",
        action="count",
    )
    return parser.parse_args()


[docs]class ImageViewer(AppMatplotlibWidgets): """Simple Image viewer.""" def __init__(self, args): path_in = args.path if os.path.isdir(path_in): self.path_files = glob(os.path.join(path_in, "*")) self.path_files = [ path for path in self.path_files if check_image(path) ] self.path_files.sort() ifile = 0 else: path_file = glob(path_in)[0] self.path_files = glob(os.path.join(os.path.split(path_file)[0], "*")) self.path_files = [ path for path in self.path_files if check_image(path) ] self.path_files.sort() ifile = self.path_files.index(path_file) if len(self.path_files) == 0: raise ValueError("No image files detected.") if len(self.path_files) == 1 and self.path_files[0].endswith(".cine"): serie = SerieOfArraysFromFiles(self.path_files[0]) self.path_files = serie.get_path_arrays() path_dir = os.path.split(self.path_files[0])[0] self.nb_images = len(self.path_files) print(f"Will use {self.nb_images} images in the dir {path_dir}") super().__init__() fig = self.fig = plt.figure() fig.canvas.manager.set_window_title( path_dir + " (" + time_as_str()[-8:].replace("-", ":") + ")" ) self.ax = fig.add_axes([0.07, 0.15, 0.7, 0.78]) self.maps = {} try: self.cmap = plt.cm.viridis except AttributeError: self.cmap = plt.cm.jet self.ifile = ifile self._last_was_increase = False im = imread(self.path_files[ifile]) self.clim = [0, 0.99 * im.max()] self._updating_clim = False self.loadim(ifile, im) name_file = self.get_namefile() self.ax.set_title(name_file) self._image_changing = False function_buttons = [None] * len(name_buttons) function_buttons[0] = self._decrease_ifile_n function_buttons[1] = self._decrease_ifile function_buttons[2] = self._increase_ifile function_buttons[3] = self._increase_ifile_n y = size_button / 3.0 for x, name, func in zip(x_buttons, name_buttons, function_buttons): self._create_button(fig, [x, y, size_button, size_button], name, func) self._n = 1 self._create_text_box( fig, [0.1, y, 2 * size_button, size_button], "n = ", self._submit_n, "1", ) self._create_text_box( fig, [0.87, 0.92, 1.5 * size_button, size_button], "cmax = ", self._change_cmax, "{:.2f}".format(self.clim[1]), ) self._create_text_box( fig, [0.87, 0.1, 1.5 * size_button, size_button], "cmin = ", self._change_cmin, "{:.2f}".format(self.clim[0]), ) self._create_button( fig, [0.65, 0.945, 1.2 * size_button, 0.045], "reload", self.reloadim ) self._create_button( fig, [0.85, y, size_button, size_button], "auto", self.set_autoclim ) cax = fig.add_axes([0.83, 0.2, 0.07, 0.7]) self.cbar = fig.colorbar(self.mappable, cax=cax) fig.canvas.mpl_connect("key_press_event", self.onclick) print("press alt+h for help") plt.show() def set_autoclim(self, event): _print_debug("set_autoclim") im = imread(self.path_files[self.ifile]) self.clim = [im.min(), 0.99 * im.max()] self._update_clim() def reloadim(self, event): self.loadim(self.ifile) def loadim(self, ifile, im=None): _print_debug("loadim", ifile, im) if im is None: im = imread(self.path_files[ifile]) _print_debug(self.path_files[ifile]) _print_debug(im) self.mappable = self.ax.imshow( im, interpolation="nearest", cmap=self.cmap, origin="upper", extent=[0, im.shape[1], im.shape[0], 0], vmin=self.clim[0], vmax=self.clim[1], ) self.maps[ifile] = self.mappable def get_namefile(self): return os.path.split(self.path_files[self.ifile])[-1] def change_im(self): self.ifile = self.ifile % self.nb_images if self._image_changing: return self._image_changing = True ifile = self.ifile name_file = self.get_namefile() print("changing to file " + name_file, end="...") _print_debug("") map_old = self.mappable if ifile in self.maps: self.mappable = self.maps[ifile] self.mappable.set_clim(self.clim) self.mappable.set_visible(True) else: self.loadim(ifile) map_old.set_visible(False) self.ax.set_title(name_file) self.fig.canvas.draw() _print_debug("") print("\rchanged to file " + name_file + " " * 20) self._image_changing = False def _switch(self): if self._last_was_increase: self._decrease_ifile_n() else: self._increase_ifile_n() self._last_was_increase = not self._last_was_increase def _increase_ifile(self, event=None): self.ifile += 1 self.change_im() def _decrease_ifile(self, event=None): self.ifile -= 1 self.change_im() def _increase_ifile_n(self, event=None): self.ifile += self._n self.change_im() def _decrease_ifile_n(self, event=None): self.ifile -= self._n self.change_im() def _submit_n(self, text): self._n = safe_eval(text) print("n = ", self._n) def _change_cmax(self, text): _print_debug("_change_cmax") cmax = safe_eval(text) if cmax == self.clim[1]: return self.clim[1] = cmax self._update_clim() def _change_cmin(self, text): _print_debug("_change_cmin") cmin = safe_eval(text) if cmin == self.clim[0]: return self.clim[0] = cmin self._update_clim() def _update_clim(self): if self._updating_clim: return self._updating_clim = True _print_debug("_update_clim:", self.clim) self.mappable.set_clim(self.clim) self.fig.canvas.draw() # self.cbar.set_clim(self.clim) self.fig.draw_without_rendering() txt_cmax = self._textboxes["cmax = "] txt_cmin = self._textboxes["cmin = "] txt_cmin.set_val("{:.2f}".format(self.clim[0])) txt_cmax.set_val("{:.2f}".format(self.clim[1])) _print_debug("_update_clim end") self._updating_clim = False def onclick(self, event): if event.key == "alt+h": print("\nalt+s\t\t\t switch between images\n") if event.inaxes != self.ax: return if event.key == "alt+s": self._switch()
def main(): args = parse_args() if args.version: print(f"fluidimage {fluidimage.__version__}") return ImageViewer(args)