img_phy_sim.img

Image Input, Output, and Visualization Utilities

This module provides a comprehensive set of tools for loading, saving, displaying, and analyzing images, particularly for scientific and machine learning workflows. It supports grayscale and color images, normalization, inversion, block-wise statistics, and flexible visualization options for single or multiple images.

The core idea is to provide an easy interface for inspecting and comparing images, generating informative visualizations, and preparing image data for further processing.

Main features:

  • Load and save images with optional normalization
  • Display images with flexible size, colormap, and axis options
  • Compare multiple sets of images (input, prediction, ground truth, difference)
  • Advanced multi-image visualization with custom layouts and titles
  • Annotate images with block-wise mean values for quick inspection
  • Highlight specific rows or columns and plot their pixel profiles
  • Utility functions to get image properties (bit depth, width, height)

Typical workflow:

  1. Load images using open() or read multiple paths via show_images().
  2. Visualize single or multiple images using imshow() or advanced_imshow().
  3. Compare predictions with ground truth using show_samples().
  4. Annotate blocks or highlight pixel profiles using plot_image_with_values() and show_image_with_line_and_profile().

Dependencies:

  • numpy
  • cv2 (OpenCV)
  • matplotlib
  • scikit-image

Example:

img = img.open("example.png", should_scale=True)
img = img * 255  # optional scaling
img.show()
show_samples([img], [pred_img], [ground_truth], model_name="MyModel")
plot_image_with_values(img, block_size=16, cmap="gray")
line_values = show_image_with_line_and_profile([img], axis="row", index=50)

Functions:

  • get_bit_depth(img) - Return bit depth of image dtype.
  • get_width_height(img, channels_before=0) - Return (width, height) of an image.
  • open(src, should_scale=False, should_print=True) - Load an image from disk.
  • save(img, src, should_scale=False) - Save an image to disk.
  • imshow(img, size=8, axis_off=True, cmap="gray") - Display an image.
  • show_samples(input_samples, pred_samples, real_samples, ...) - Compare multiple images.
  • advanced_imshow(img, title=None, image_width=10, ...) - Display single or batch images with customization.
  • show_images(image_paths, title=None, image_width=5, ...) - Load and display images from paths.
  • plot_image_with_values(img, block_size=8, ...) - Annotate image with block-wise mean values.
  • show_image_with_line_and_profile(imgs, axis='row', ...) - Highlight a row/column and plot pixel values.
  1"""
  2**Image Input, Output, and Visualization Utilities**
  3
  4This module provides a comprehensive set of tools for loading, saving, displaying,
  5and analyzing images, particularly for scientific and machine learning workflows.
  6It supports grayscale and color images, normalization, inversion, block-wise
  7statistics, and flexible visualization options for single or multiple images.
  8
  9The core idea is to provide an easy interface for inspecting and comparing images,
 10generating informative visualizations, and preparing image data for further processing.
 11
 12Main features:
 13- Load and save images with optional normalization
 14- Display images with flexible size, colormap, and axis options
 15- Compare multiple sets of images (input, prediction, ground truth, difference)
 16- Advanced multi-image visualization with custom layouts and titles
 17- Annotate images with block-wise mean values for quick inspection
 18- Highlight specific rows or columns and plot their pixel profiles
 19- Utility functions to get image properties (bit depth, width, height)
 20
 21Typical workflow:
 221. Load images using `open()` or read multiple paths via `show_images()`.
 232. Visualize single or multiple images using `imshow()` or `advanced_imshow()`.
 243. Compare predictions with ground truth using `show_samples()`.
 254. Annotate blocks or highlight pixel profiles using
 26    `plot_image_with_values()` and `show_image_with_line_and_profile()`.
 27
 28Dependencies:
 29- numpy
 30- cv2 (OpenCV)
 31- matplotlib
 32- scikit-image
 33
 34Example:
 35```python
 36img = img.open("example.png", should_scale=True)
 37img = img * 255  # optional scaling
 38img.show()
 39show_samples([img], [pred_img], [ground_truth], model_name="MyModel")
 40plot_image_with_values(img, block_size=16, cmap="gray")
 41line_values = show_image_with_line_and_profile([img], axis="row", index=50)
 42```
 43
 44Functions:
 45- get_bit_depth(img)                   - Return bit depth of image dtype.
 46- get_width_height(img, channels_before=0) - Return (width, height) of an image.
 47- open(src, should_scale=False, should_print=True) - Load an image from disk.
 48- save(img, src, should_scale=False)  - Save an image to disk.
 49- imshow(img, size=8, axis_off=True, cmap="gray") - Display an image.
 50- show_samples(input_samples, pred_samples, real_samples, ...) - Compare multiple images.
 51- advanced_imshow(img, title=None, image_width=10, ...) - Display single or batch images with customization.
 52- show_images(image_paths, title=None, image_width=5, ...) - Load and display images from paths.
 53- plot_image_with_values(img, block_size=8, ...) - Annotate image with block-wise mean values.
 54- show_image_with_line_and_profile(imgs, axis='row', ...) - Highlight a row/column and plot pixel values.
 55"""
 56
 57
 58
 59# ---------------
 60# >>> Imports <<<
 61# ---------------
 62import os
 63import numpy as np
 64import cv2
 65import matplotlib.pyplot as plt
 66from skimage.measure import block_reduce  # pip install scikit-image
 67
 68
 69
 70# -------------------------
 71# >>> Basic Image tools <<<
 72# -------------------------
 73
 74def get_bit_depth(img):
 75    """
 76    Retrieve the bit depth of an image based on its NumPy data type.
 77
 78    Parameters:
 79        img (numpy.ndarray): <br>
 80            Input image array.
 81
 82    Returns: <br>
 83        int or str:<br> 
 84            Bit depth of the image (8, 16, 32, or 64).<br>
 85            Returns "unknown" if the data type is not recognized.
 86
 87    Notes:
 88        The mapping is defined for common image dtypes:
 89            - np.uint8   →  8-bit
 90            - np.uint16  → 16-bit
 91            - np.int16   → 16-bit
 92            - np.float32 → 32-bit
 93            - np.float64 → 64-bit
 94    """
 95    dtype_to_bits = {
 96        np.uint8: 8,
 97        np.uint16: 16,
 98        np.int16: 16,
 99        np.float32: 32,
100        np.float64: 64
101    }
102    return dtype_to_bits.get(img.dtype.type, "unknown")
103
104
105def get_width_height(img, channels_before=0):
106    """
107    Extract the width and height of an image, optionally offset by leading channels.
108
109    Parameters:
110        - img (numpy.ndarray): <br>
111            Input image array.
112        - channels_before (int, optional): <br>
113            Offset in the shape dimension if channels precede height and width (default: 0).
114
115    Returns:
116        tuple: (width, height) of the image.
117
118    Example:
119        >>> img.shape = (256, 512)
120        >>> get_width_height(img)
121        (512, 256)
122    """
123    height, width = img.shape[0+channels_before:2+channels_before]
124    return width, height
125
126
127
128def open(src, should_scale=False, auto_scale_method=True, should_print=True, unchanged=True):
129    """
130    Load a grayscale image from a file path.
131
132    Parameters:
133        - src (str): Path to the image file.
134        - should_scale (bool, optional):<br>
135            If True, scale pixel values to [0, 1] according to bit depth (default: False).
136        - auto_scale_method (bool, optionl): <br>
137            If True, the scaling will auto decide to up or down scale 
138            if should_sclae is also True else always down scale (default: True).
139        - should_print (bool, optional): <br>
140            If True, print image info to console (default: True).
141
142    Returns: <br>
143        numpy.ndarray: Loaded grayscale image.
144
145    Example:
146        >>> img = open("example.png", should_scale=True)
147        Loaded Image:
148            - Image size: 512x256
149            - Bit depth: 8-bit
150            - Dtype: float64
151    """
152    img = cv2.imread(src,  cv2.IMREAD_UNCHANGED if unchanged else cv2.IMREAD_GRAYSCALE)
153    height, width = img.shape[:2]
154
155    if should_scale:
156        img_max = ((2**get_bit_depth(img)) -1)
157        if auto_scale_method:
158            if img.max() < img_max*0.2:  # or just img.max() <= 1.1
159                img = img * img_max
160            else:
161                img = img / img_max
162        else:
163            img = img / img_max
164
165    if should_print:
166        print(f"Loaded Image:\n    - Image size: {width}x{height}\
167              \n    - Bit depth: {get_bit_depth(img)}-bit\n    - Dtype: {img.dtype}\
168              \n    - Mean: {img.mean()}\n    - Max: {img.max()}\n    - Min: {img.min()}")
169
170    return img
171
172
173
174def save(img, src, should_scale=False, auto_scale_method=True):
175    """
176    Save an image to disk.
177
178    Parameters:
179        - img (numpy.ndarray): Image to save.
180        - src (str): <br>
181            Destination file path.
182        - should_scale (bool, optional): <br>
183            If True, scale pixel values to [0, 1] before saving (default: False).
184        - auto_scale_method (bool, optionl): <br>
185            If True, the scaling will auto decide to up or down scale 
186            if should_sclae is also True else always down scale (default: True).
187
188    Notes:
189        - The function uses OpenCV's `cv2.imwrite` for saving.
190        - The scaling logic divides by the maximum value representable
191          by the bit depth, similar to the `open()` function.
192    """
193    if should_scale:
194        img_max = ((2**get_bit_depth(img)) -1)
195        if auto_scale_method:
196            if img.max() < img_max*0.2:  # or just img.max() <= 1.1
197                img = img * img_max
198            else:
199                img = img / img_max
200        else:
201            img = img / img_max
202
203    cv2.imwrite(src, img)
204
205
206
207def imshow(img, size=8, axis_off=True, cmap="gray"):
208    """
209    Display an image using Matplotlib.
210
211    Parameters:
212        - img (numpy.ndarray): <br>
213            Image to display.
214        - size (int, optional): <br>
215            Display size in inches (default: 8).
216        - axis_off (bool, optional): <br>
217            If True, hides the axes (default: True).
218        - cmap (str, optional):<br>
219            Colormap name. Use 'random' for a random Matplotlib colormap (default: 'gray').
220
221    Behavior:
222        - If `img` has 3 channels, it is converted from BGR to RGB.
223        - If `cmap='random'`, a random colormap is chosen and possibly reversed.
224        - Maintains the aspect ratio based on image dimensions.
225
226    Example:
227        >>> imshow(img, cmap='random')
228        # Displays the image with a randomly selected colormap.
229    """
230    if cmap == "random":
231        cmap = np.random.choice(["viridis",
232                                 "magma",
233                                 "inferno",
234                                 "plasma",
235                                 "cividis",
236                                 "spring",
237                                 "hot",
238                                 "hsv",
239                                 "CMRmap",
240                                 "gnuplot",
241                                 "gnuplot2",
242                                 "jet",
243                                 "turbo"])
244        cmap = cmap if np.random.random() > 0.5 else cmap+"_r"
245
246    height, width = img.shape[:2]
247    ratio = height / width
248    plt.figure(figsize=(size, round(size * ratio)))
249    if img.ndim > 2:
250        plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
251    else:
252        plt.imshow(img, cmap=cmap)
253    plt.axis('off' if axis_off else 'on')
254    plt.show()
255
256
257
258# ----------------------------
259# >>> Advanced Image Tools <<<
260# ----------------------------
261#  from https://github.com/xXAI-botXx/AI/blob/main/src/helper/imshow.py
262
263def show_samples(input_samples, pred_samples, real_samples, model_name="Model",
264                 n_samples=3, n_cols=4, image_width=4, cmap="gray",
265                 normalize=True, invert=False, axis=False,
266                 save_to=None, hspace=0.3, wspace=0.2, use_original_style=False):
267    """
268    Display multiple sets of sample images (input, prediction, ground truth, difference)
269    side by side for visual comparison.
270
271    The function can load images from file paths or accept NumPy arrays directly.
272    It arranges them in a grid and can optionally normalize, invert, or save the output.
273
274    Parameters:
275        - input_samples (list[str] or list[np.ndarray]): <br>
276            Input sample images.
277        - pred_samples (list[str] or list[np.ndarray]): <br>
278            Model prediction images.
279        - real_samples (list[str] or list[np.ndarray]): <br>
280            Ground truth images.
281        - model_name (str, optional): <br>
282            Name of the model to display in titles (default: "Model").
283        - n_samples (int, optional): <br>
284            Number of sample groups to display (default: 3).
285        - n_cols (int, optional): <br>
286            Number of columns per sample group (default: 4).<br>
287            Typically: Input | Prediction | Ground Truth | Difference.
288        - image_width (int, optional): <br>
289            Width of one image in inches (default: 4).
290        - cmap (str, optional): <br>
291            Colormap for displaying grayscale images (default: "gray").
292        - normalize (bool, optional): <br>
293            Whether to normalize pixel values to [0, 1] (default: True).
294        - invert (bool, optional): <br>
295            Whether to invert pixel values (255 - img) (default: False).
296        - axis (bool, optional):<br>
297            Whether to show image axes (default: False).
298        - save_to (str, optional): <br>
299            Path to save the figure (default: None).
300        - hspace (float, optional): <br>
301            Vertical spacing between subplots (default: 0.3).
302        - wspace (float, optional): <br>
303            Horizontal spacing between subplots (default: 0.2).
304        - use_original_style (bool, optional): <br>
305            If True, preserves the current matplotlib style (default: False).
306
307    Returns:<br>
308        None
309
310    Example:
311        >>> show_samples(inputs, preds, reals, model_name="UNet", n_samples=5, cmap="gray")
312    """
313    
314    def load_image(img):
315        if isinstance(img, str):
316            arr = cv2.imread(img, cv2.IMREAD_GRAYSCALE).astype(np.float32)
317        else:
318            arr = img.astype(np.float32)
319        if invert:
320            arr = 255 - arr
321        if normalize:
322            arr /= 255.0
323        return arr
324
325    # Prepare images and titles
326    all_images = []
327    titles = []
328    sub_images = []
329
330    for idx in range(n_samples):
331        all_images.extend([input_samples[idx], pred_samples[idx], real_samples[idx], pred_samples[idx]])
332        titles.extend(["Input", model_name, "Ground Truth", "Difference"])
333        sub_images.extend([None, None, None, real_samples[idx]])
334
335    # Load all images
336    img_arrays = [load_image(im) for im in all_images]
337    sub_arrays = [load_image(im) if im is not None else None for im in sub_images]
338
339    # Compute differences where applicable
340    for i, sub in enumerate(sub_arrays):
341        if sub is not None:
342            img_arrays[i] = np.abs(img_arrays[i] - sub)
343
344    n_images = len(img_arrays)
345    n_rows = n_images // n_cols + int(n_images % n_cols > 0)
346
347    fig, axes = plt.subplots(n_rows, n_cols, figsize=(n_cols*image_width, n_rows*image_width))
348
349    if not use_original_style:
350        plt_style = 'seaborn-v0_8' if 'seaborn-v0_8' in plt.style.available else 'classic'
351        plt.style.use(plt_style)
352
353    axes = axes.ravel() if n_images > 1 else [axes]
354
355    for idx, ax in enumerate(axes):
356        if idx >= n_images:
357            ax.axis('off')
358            continue
359        ax.imshow(img_arrays[idx], cmap=cmap)
360        ax.set_title(titles[idx], fontsize=10)
361        if not axis:
362            ax.axis('off')
363
364    plt.subplots_adjust(hspace=hspace, wspace=wspace)
365
366    if save_to:
367        os.makedirs(os.path.dirname(save_to), exist_ok=True)
368        plt.savefig(save_to, dpi=300)
369
370    plt.show()
371    if not use_original_style:
372        plt.style.use('default')
373
374
375
376def advanced_imshow(img, title=None, image_width=10, axis=False,
377           color_space="RGB", cmap=None, cols=1, save_to=None,
378           hspace=0.2, wspace=0.2,
379           use_original_style=False, invert=False):
380    """
381    Display one or multiple images in a flexible and configurable grid.
382
383    This function supports multiple color spaces, automatic reshaping of 
384    input tensors, batch display, color inversion, and saving to disk.
385
386    Parameters:
387        - img (np.ndarray): <br>
388            Input image or batch of images.<br>
389            Accepted shapes:<br>
390            [H, W], [H, W, C], [N, H, W], or [N, H, W, C].
391        - title (str or list[str], optional): <br>
392            Overall or per-image titles.
393        - image_width (int, optional): <br>
394            Width of each image in inches (default: 10).
395        - axis (bool, optional): <br>
396            Whether to show axes (default: False).
397        - color_space (str, optional): <br>
398            Color space of the image: "RGB", "BGR", "gray", or "HSV" (default: "RGB").
399        - cmap (str, optional): <br>
400            Matplotlib colormap for grayscale images (default: None).
401        - cols (int, optional): <br>
402            Number of columns in the subplot grid (default: 1).
403        - save_to (str, optional): <br>
404            File path to save the figure (default: None).
405        - hspace (float, optional): <br>
406            Vertical spacing between subplots (default: 0.2).
407        - wspace (float, optional): <br>
408            Horizontal spacing between subplots (default: 0.2).
409        - use_original_style (bool, optional): <br>
410            Keep current Matplotlib style if True (default: False).
411        - invert (bool, optional): <br>
412            Invert color values (default: False).
413
414    Returns:<br>
415        None
416
417    Example:
418        >>> advanced_imshow(batch_images, cols=3, color_space="BGR", title="Predictions")
419    """
420    original_style = plt.rcParams.copy()
421    try:
422        img_shape = img.shape
423    except Exception:
424        img = np.array(img)
425        img_shape = img.shape
426    # Transform to 4D array [N, H, W, C]
427    if len(img_shape) == 2:
428        img = img.reshape(1, img_shape[0], img_shape[1], 1)
429    elif len(img_shape) == 3:
430        if img_shape[2] in [1, 3]:  # single image with channels
431            img = img.reshape(1, img_shape[0], img_shape[1], img_shape[2])
432        else:  # multiple gray images [N,H,W]
433            img = img[..., np.newaxis]
434    elif len(img_shape) != 4:
435        raise ValueError(f"Image(s) have wrong shape: {img_shape}")
436
437    n_images = img.shape[0]
438    aspect_ratio = img.shape[2] / img.shape[1]
439    rows = n_images // cols + int(n_images % cols > 0)
440    width = int(image_width * cols)
441    height = int(image_width * rows * aspect_ratio)
442
443    if not use_original_style:
444        plt_style = 'seaborn-v0_8' if 'seaborn-v0_8' in plt.style.available else 'classic'
445        plt.style.use(plt_style)
446
447    fig, axes = plt.subplots(nrows=rows, ncols=cols, figsize=(width, height))
448    axes = np.array(axes).ravel()
449    fig.subplots_adjust(hspace=hspace, wspace=wspace)
450
451    if isinstance(title, str):
452        fig.suptitle(title, fontsize=20, y=0.95)
453
454    # Invert images if needed
455    if invert:
456        max_val = 2**(img.dtype.itemsize*8) - 1
457        img = max_val - img
458
459    for idx, ax in enumerate(axes):
460        if idx >= n_images:
461            ax.axis("off")
462            continue
463        cur_img = img[idx]
464
465        # Handle color spaces
466        used_cmap = cmap
467        if color_space.lower() == "bgr" and cur_img.shape[2] == 3:
468            cur_img = cv2.cvtColor(cur_img, cv2.COLOR_BGR2RGB)
469            used_cmap = None
470        elif color_space.lower() == "hsv" and cur_img.shape[2] == 3:
471            cur_img = cv2.cvtColor(cur_img, cv2.COLOR_HSV2RGB)
472            used_cmap = None
473        elif color_space.lower() in ["gray", "grey", "g"]:
474            if cur_img.shape[2] == 3:
475                cur_img = cv2.cvtColor(cur_img, cv2.COLOR_RGB2GRAY)
476            used_cmap = "gray"
477
478        if isinstance(title, (list, tuple)):
479            ax.set_title(title[idx], fontsize=12)
480        if not axis:
481            ax.axis("off")
482
483        ax.imshow(cur_img.squeeze(), cmap=used_cmap)
484
485    if save_to:
486        os.makedirs(os.path.dirname(save_to), exist_ok=True)
487        fig.savefig(save_to, dpi=300)
488
489    plt.show()
490    if not use_original_style:
491        plt.rcParams.update(original_style)
492
493
494
495def show_images(image_paths: list, title=None, image_width=5, axis=False,
496                color_space="gray", cmap=None, 
497                cols=2, save_to=None, hspace=0.01, wspace=0.01,
498                use_original_style=False, invert=False):
499    """
500    Load and display multiple images from disk using `advanced_imshow`.
501
502    Parameters:
503        - image_paths (list[str]): <br>
504            List of file paths to load.
505        - title (str or list[str], optional): <br>
506            Plot title(s).
507        - image_width (int, optional): <br>
508            Width of each image (default: 5).
509        - axis (bool, optional): <br>
510            Whether to display axes (default: False).
511        - color_space (str, optional): <br>
512            Color space to convert images to.<br>
513            One of: "gray", "rgb", "hsv", "bgr" (default: "gray").
514        - cmap (str, optional): <br>
515            Colormap for grayscale images (default: None).
516        - cols (int, optional): <br>
517            Number of columns in the grid (default: 2).
518        - save_to (str, optional): <br>
519            Path to save the figure (default: None).
520        - hspace (float, optional): <br>
521            Vertical spacing between subplots (default: 0.01).
522        - wspace (float, optional): <br>
523            Horizontal spacing between subplots (default: 0.01).
524        - use_original_style (bool, optional): <br>
525            Keep current Matplotlib style (default: False).
526        - invert (bool, optional): <br>
527            Whether to invert images (default: False).
528
529    Returns:
530        np.ndarray: Loaded images stacked as an array.
531
532    Example:
533        >>> show_images(["img1.png", "img2.png"], color_space="rgb", cols=2)
534    """
535    images = []
536    for img_path in image_paths:
537        img = cv2.imread(img_path)
538        if color_space.lower() == "rgb":
539            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
540        elif color_space.lower() == "hsv":
541            img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
542        elif color_space.lower() in ["gray", "grey", "g"]:
543            img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
544        images.append(img)
545
546    images = np.array(images)
547    advanced_imshow(images, title=title, image_width=image_width, axis=axis,
548           color_space=color_space, cmap=cmap, cols=cols, save_to=save_to,
549           hspace=hspace, wspace=wspace,
550           use_original_style=use_original_style, invert=invert)
551    return images
552
553
554
555def plot_image_with_values(img, block_size=8, cmap='gray', title=None, 
556                           font_size=6, save_to=None):
557    """
558    Plot an image with annotated mean values over non-overlapping blocks.
559
560    Each block represents the mean pixel intensity of its region. The mean
561    values are displayed as text annotations directly on the image.
562
563    Parameters:
564        - img (np.ndarray): <br>
565            2D grayscale image (H, W) or 3D single-channel image (H, W, 1).
566        - block_size (int or tuple, optional): <br>
567            Size of each block (default: 8).
568        - cmap (str, optional): <br>
569            Matplotlib colormap (default: "gray").
570        - title (str, optional): <br>
571            Plot title (default: None).
572        - font_size (int, optional): <br>
573            Font size of value annotations (default: 6).
574        - save_to (str, optional): <br>
575            Path to save the figure (default: None).
576
577    Returns:<br>
578        None
579
580    Example:
581        ```python
582        from imshow import plot_image_with_values
583        import cv2
584
585        img = cv2.imread('example.png', cv2.IMREAD_GRAYSCALE)
586        plot_image_with_values(img, block_size=16, cmap='gray', title='Mean Block Values', font_size=8)
587        ```
588
589        Or:
590        ```python
591        fig, ax = plt.subplots(nrows=2, ncols=4, figsize=(4*4, 6))
592        idx = 0
593
594        img_1 = plot(ax[0][0], path=input_samples[idx], title=f"Input", cmap="gray")
595        plot_image_with_values(img_1, block_size=16, ax=ax[1][0])
596
597        img_2 = plot(ax[0][1], path=pred_model[idx], title=f"{model_name}")
598        plot_image_with_values(img_2, block_size=16, ax=ax[1][1])
599
600        img_3 = plot(ax[0][2], path=real[idx], title=f"ground truth")
601        plot_image_with_values(img_3, block_size=16, ax=ax[1][2])
602
603        img_4 = plot(ax[0][3], path=pred_model[idx], title=f"Difference", sub_image=real[idx])
604        plot_image_with_values(img_4, block_size=16, ax=ax[1][3])
605
606        plt.show()
607        ```
608    """
609    # Ensure 2D image
610    if img.ndim == 3 and img.shape[2] == 1:
611        img = img[:, :, 0]
612    elif img.ndim == 3 and img.shape[2] > 1:
613        raise ValueError("Only grayscale images are supported for block annotation.")
614
615    # Compute mean over blocks
616    mean_img = block_reduce(img, block_size=(block_size, block_size), func=np.mean)
617    max_value = mean_img.max()
618
619    # Plot
620    plt.figure(figsize=(mean_img.shape[1]/2, mean_img.shape[0]/2))
621    plt.imshow(mean_img, cmap=cmap, interpolation='nearest')
622    plt.colorbar(label='Mean Value')
623
624    # Annotate each block
625    for i in range(mean_img.shape[0]):
626        for j in range(mean_img.shape[1]):
627            val = mean_img[i, j]
628            color = 'white' if val < max_value/1.5 else 'black'
629            plt.text(j, i, f'{val:.1f}', ha='center', va='center', color=color, fontsize=font_size)
630
631    plt.title(title or f'Mean Values over {block_size}x{block_size} Blocks')
632    plt.xticks([])
633    plt.yticks([])
634    plt.tight_layout()
635
636    if save_to:
637        os.makedirs(os.path.dirname(save_to), exist_ok=True)
638        plt.savefig(save_to, dpi=300)
639    
640    plt.show()
641
642    
643def show_image_with_line_and_profile(imgs, axis='row', index=None, titles=None, figsize=(10, 4)):
644    """
645    Display one or multiple grayscale images with a highlighted line (row or column)
646    and plot the corresponding pixel intensity profile below or beside each image.
647
648    Parameters:
649        - imgs (list[np.ndarray]):<br>
650            List of grayscale images to analyze.
651        - axis (str, optional):<br>
652            Direction of the line ("row" or "column") (default: "row").
653        - index (int, optional): <br>
654            Index of the selected line. If None, the central line is used (default: None).
655        - titles (list[str], optional): <br>
656            Titles for each image (default: ["Image 1", "Image 2", ...]).
657        - figsize (tuple, optional): <br>
658            Figure size per image pair (default: (10, 4)).
659
660    Returns:
661        - list[np.ndarray]: <br>
662            List of pixel intensity profiles corresponding to the selected line in each image.
663
664    Example:
665        >>> show_image_with_line_and_profile(
666        ...     imgs=[img_input, img_pred, img_gt],
667        ...     axis="row",
668        ...     titles=["Input", "Prediction", "Ground Truth"]
669        ... )
670    """
671    assert axis in ['row', 'column'], "axis must be 'row' or 'column'"
672
673    n_images = len(imgs)
674    if titles is None:
675        titles = [f"Image {i+1}" for i in range(n_images)]
676    line_values_list = []
677
678    fig, axes = plt.subplots(n_images, 2, figsize=(figsize[0]*2, figsize[1]*n_images))
679
680    # Ensure axes is 2D
681    if n_images == 1:
682        axes = np.expand_dims(axes, axis=0)
683
684    for i, (img, ax_pair, title) in enumerate(zip(imgs, axes, titles)):
685        h, w = img.shape
686        if index is None:
687            idx = h // 2 if axis == 'row' else w // 2
688        else:
689            idx = index
690
691        ax_img, ax_profile = ax_pair
692
693        # Show image with line
694        ax_img.imshow(img, cmap='gray', interpolation='nearest')
695        if axis == 'row':
696            ax_img.axhline(idx, color='red', linewidth=1)
697            line_values = img[idx, :]
698        else:
699            ax_img.axvline(idx, color='red', linewidth=1)
700            line_values = img[:, idx]
701        line_values_list.append(line_values)
702
703        ax_img.set_title(title)
704        ax_img.set_xticks([])
705        ax_img.set_yticks([])
706
707        # Plot pixel values along the line
708        x = np.arange(len(line_values))
709        ax_profile.plot(x, line_values, color='black', linewidth=1)
710        ax_profile.set_title(f'Pixel values along {axis} {idx}')
711        ax_profile.set_xlabel('Pixel index')
712        ax_profile.set_ylabel('Intensity')
713        ax_profile.grid(True)
714
715    plt.tight_layout()
716    plt.show()
717
718    return line_values_list
def get_bit_depth(img):
 75def get_bit_depth(img):
 76    """
 77    Retrieve the bit depth of an image based on its NumPy data type.
 78
 79    Parameters:
 80        img (numpy.ndarray): <br>
 81            Input image array.
 82
 83    Returns: <br>
 84        int or str:<br> 
 85            Bit depth of the image (8, 16, 32, or 64).<br>
 86            Returns "unknown" if the data type is not recognized.
 87
 88    Notes:
 89        The mapping is defined for common image dtypes:
 90            - np.uint8   →  8-bit
 91            - np.uint16  → 16-bit
 92            - np.int16   → 16-bit
 93            - np.float32 → 32-bit
 94            - np.float64 → 64-bit
 95    """
 96    dtype_to_bits = {
 97        np.uint8: 8,
 98        np.uint16: 16,
 99        np.int16: 16,
100        np.float32: 32,
101        np.float64: 64
102    }
103    return dtype_to_bits.get(img.dtype.type, "unknown")

Retrieve the bit depth of an image based on its NumPy data type.

Parameters: img (numpy.ndarray):
Input image array.

Returns:
int or str:
Bit depth of the image (8, 16, 32, or 64).
Returns "unknown" if the data type is not recognized.

Notes: The mapping is defined for common image dtypes: - np.uint8 → 8-bit - np.uint16 → 16-bit - np.int16 → 16-bit - np.float32 → 32-bit - np.float64 → 64-bit

def get_width_height(img, channels_before=0):
106def get_width_height(img, channels_before=0):
107    """
108    Extract the width and height of an image, optionally offset by leading channels.
109
110    Parameters:
111        - img (numpy.ndarray): <br>
112            Input image array.
113        - channels_before (int, optional): <br>
114            Offset in the shape dimension if channels precede height and width (default: 0).
115
116    Returns:
117        tuple: (width, height) of the image.
118
119    Example:
120        >>> img.shape = (256, 512)
121        >>> get_width_height(img)
122        (512, 256)
123    """
124    height, width = img.shape[0+channels_before:2+channels_before]
125    return width, height

Extract the width and height of an image, optionally offset by leading channels.

Parameters: - img (numpy.ndarray):
Input image array. - channels_before (int, optional):
Offset in the shape dimension if channels precede height and width (default: 0).

Returns: tuple: (width, height) of the image.

Example:

img.shape = (256, 512) get_width_height(img) (512, 256)

def open( src, should_scale=False, auto_scale_method=True, should_print=True, unchanged=True):
129def open(src, should_scale=False, auto_scale_method=True, should_print=True, unchanged=True):
130    """
131    Load a grayscale image from a file path.
132
133    Parameters:
134        - src (str): Path to the image file.
135        - should_scale (bool, optional):<br>
136            If True, scale pixel values to [0, 1] according to bit depth (default: False).
137        - auto_scale_method (bool, optionl): <br>
138            If True, the scaling will auto decide to up or down scale 
139            if should_sclae is also True else always down scale (default: True).
140        - should_print (bool, optional): <br>
141            If True, print image info to console (default: True).
142
143    Returns: <br>
144        numpy.ndarray: Loaded grayscale image.
145
146    Example:
147        >>> img = open("example.png", should_scale=True)
148        Loaded Image:
149            - Image size: 512x256
150            - Bit depth: 8-bit
151            - Dtype: float64
152    """
153    img = cv2.imread(src,  cv2.IMREAD_UNCHANGED if unchanged else cv2.IMREAD_GRAYSCALE)
154    height, width = img.shape[:2]
155
156    if should_scale:
157        img_max = ((2**get_bit_depth(img)) -1)
158        if auto_scale_method:
159            if img.max() < img_max*0.2:  # or just img.max() <= 1.1
160                img = img * img_max
161            else:
162                img = img / img_max
163        else:
164            img = img / img_max
165
166    if should_print:
167        print(f"Loaded Image:\n    - Image size: {width}x{height}\
168              \n    - Bit depth: {get_bit_depth(img)}-bit\n    - Dtype: {img.dtype}\
169              \n    - Mean: {img.mean()}\n    - Max: {img.max()}\n    - Min: {img.min()}")
170
171    return img

Load a grayscale image from a file path.

Parameters: - src (str): Path to the image file. - should_scale (bool, optional):
If True, scale pixel values to [0, 1] according to bit depth (default: False). - auto_scale_method (bool, optionl):
If True, the scaling will auto decide to up or down scale if should_sclae is also True else always down scale (default: True). - should_print (bool, optional):
If True, print image info to console (default: True).

Returns:
numpy.ndarray: Loaded grayscale image.

Example:

img = open("example.png", should_scale=True) Loaded Image: - Image size: 512x256 - Bit depth: 8-bit - Dtype: float64

def save(img, src, should_scale=False, auto_scale_method=True):
175def save(img, src, should_scale=False, auto_scale_method=True):
176    """
177    Save an image to disk.
178
179    Parameters:
180        - img (numpy.ndarray): Image to save.
181        - src (str): <br>
182            Destination file path.
183        - should_scale (bool, optional): <br>
184            If True, scale pixel values to [0, 1] before saving (default: False).
185        - auto_scale_method (bool, optionl): <br>
186            If True, the scaling will auto decide to up or down scale 
187            if should_sclae is also True else always down scale (default: True).
188
189    Notes:
190        - The function uses OpenCV's `cv2.imwrite` for saving.
191        - The scaling logic divides by the maximum value representable
192          by the bit depth, similar to the `open()` function.
193    """
194    if should_scale:
195        img_max = ((2**get_bit_depth(img)) -1)
196        if auto_scale_method:
197            if img.max() < img_max*0.2:  # or just img.max() <= 1.1
198                img = img * img_max
199            else:
200                img = img / img_max
201        else:
202            img = img / img_max
203
204    cv2.imwrite(src, img)

Save an image to disk.

Parameters: - img (numpy.ndarray): Image to save. - src (str):
Destination file path. - should_scale (bool, optional):
If True, scale pixel values to [0, 1] before saving (default: False). - auto_scale_method (bool, optionl):
If True, the scaling will auto decide to up or down scale if should_sclae is also True else always down scale (default: True).

Notes: - The function uses OpenCV's cv2.imwrite for saving. - The scaling logic divides by the maximum value representable by the bit depth, similar to the open() function.

def imshow(img, size=8, axis_off=True, cmap='gray'):
208def imshow(img, size=8, axis_off=True, cmap="gray"):
209    """
210    Display an image using Matplotlib.
211
212    Parameters:
213        - img (numpy.ndarray): <br>
214            Image to display.
215        - size (int, optional): <br>
216            Display size in inches (default: 8).
217        - axis_off (bool, optional): <br>
218            If True, hides the axes (default: True).
219        - cmap (str, optional):<br>
220            Colormap name. Use 'random' for a random Matplotlib colormap (default: 'gray').
221
222    Behavior:
223        - If `img` has 3 channels, it is converted from BGR to RGB.
224        - If `cmap='random'`, a random colormap is chosen and possibly reversed.
225        - Maintains the aspect ratio based on image dimensions.
226
227    Example:
228        >>> imshow(img, cmap='random')
229        # Displays the image with a randomly selected colormap.
230    """
231    if cmap == "random":
232        cmap = np.random.choice(["viridis",
233                                 "magma",
234                                 "inferno",
235                                 "plasma",
236                                 "cividis",
237                                 "spring",
238                                 "hot",
239                                 "hsv",
240                                 "CMRmap",
241                                 "gnuplot",
242                                 "gnuplot2",
243                                 "jet",
244                                 "turbo"])
245        cmap = cmap if np.random.random() > 0.5 else cmap+"_r"
246
247    height, width = img.shape[:2]
248    ratio = height / width
249    plt.figure(figsize=(size, round(size * ratio)))
250    if img.ndim > 2:
251        plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
252    else:
253        plt.imshow(img, cmap=cmap)
254    plt.axis('off' if axis_off else 'on')
255    plt.show()

Display an image using Matplotlib.

Parameters: - img (numpy.ndarray):
Image to display. - size (int, optional):
Display size in inches (default: 8). - axis_off (bool, optional):
If True, hides the axes (default: True). - cmap (str, optional):
Colormap name. Use 'random' for a random Matplotlib colormap (default: 'gray').

Behavior: - If img has 3 channels, it is converted from BGR to RGB. - If cmap='random', a random colormap is chosen and possibly reversed. - Maintains the aspect ratio based on image dimensions.

Example:

imshow(img, cmap='random') # Displays the image with a randomly selected colormap.

def show_samples( input_samples, pred_samples, real_samples, model_name='Model', n_samples=3, n_cols=4, image_width=4, cmap='gray', normalize=True, invert=False, axis=False, save_to=None, hspace=0.3, wspace=0.2, use_original_style=False):
264def show_samples(input_samples, pred_samples, real_samples, model_name="Model",
265                 n_samples=3, n_cols=4, image_width=4, cmap="gray",
266                 normalize=True, invert=False, axis=False,
267                 save_to=None, hspace=0.3, wspace=0.2, use_original_style=False):
268    """
269    Display multiple sets of sample images (input, prediction, ground truth, difference)
270    side by side for visual comparison.
271
272    The function can load images from file paths or accept NumPy arrays directly.
273    It arranges them in a grid and can optionally normalize, invert, or save the output.
274
275    Parameters:
276        - input_samples (list[str] or list[np.ndarray]): <br>
277            Input sample images.
278        - pred_samples (list[str] or list[np.ndarray]): <br>
279            Model prediction images.
280        - real_samples (list[str] or list[np.ndarray]): <br>
281            Ground truth images.
282        - model_name (str, optional): <br>
283            Name of the model to display in titles (default: "Model").
284        - n_samples (int, optional): <br>
285            Number of sample groups to display (default: 3).
286        - n_cols (int, optional): <br>
287            Number of columns per sample group (default: 4).<br>
288            Typically: Input | Prediction | Ground Truth | Difference.
289        - image_width (int, optional): <br>
290            Width of one image in inches (default: 4).
291        - cmap (str, optional): <br>
292            Colormap for displaying grayscale images (default: "gray").
293        - normalize (bool, optional): <br>
294            Whether to normalize pixel values to [0, 1] (default: True).
295        - invert (bool, optional): <br>
296            Whether to invert pixel values (255 - img) (default: False).
297        - axis (bool, optional):<br>
298            Whether to show image axes (default: False).
299        - save_to (str, optional): <br>
300            Path to save the figure (default: None).
301        - hspace (float, optional): <br>
302            Vertical spacing between subplots (default: 0.3).
303        - wspace (float, optional): <br>
304            Horizontal spacing between subplots (default: 0.2).
305        - use_original_style (bool, optional): <br>
306            If True, preserves the current matplotlib style (default: False).
307
308    Returns:<br>
309        None
310
311    Example:
312        >>> show_samples(inputs, preds, reals, model_name="UNet", n_samples=5, cmap="gray")
313    """
314    
315    def load_image(img):
316        if isinstance(img, str):
317            arr = cv2.imread(img, cv2.IMREAD_GRAYSCALE).astype(np.float32)
318        else:
319            arr = img.astype(np.float32)
320        if invert:
321            arr = 255 - arr
322        if normalize:
323            arr /= 255.0
324        return arr
325
326    # Prepare images and titles
327    all_images = []
328    titles = []
329    sub_images = []
330
331    for idx in range(n_samples):
332        all_images.extend([input_samples[idx], pred_samples[idx], real_samples[idx], pred_samples[idx]])
333        titles.extend(["Input", model_name, "Ground Truth", "Difference"])
334        sub_images.extend([None, None, None, real_samples[idx]])
335
336    # Load all images
337    img_arrays = [load_image(im) for im in all_images]
338    sub_arrays = [load_image(im) if im is not None else None for im in sub_images]
339
340    # Compute differences where applicable
341    for i, sub in enumerate(sub_arrays):
342        if sub is not None:
343            img_arrays[i] = np.abs(img_arrays[i] - sub)
344
345    n_images = len(img_arrays)
346    n_rows = n_images // n_cols + int(n_images % n_cols > 0)
347
348    fig, axes = plt.subplots(n_rows, n_cols, figsize=(n_cols*image_width, n_rows*image_width))
349
350    if not use_original_style:
351        plt_style = 'seaborn-v0_8' if 'seaborn-v0_8' in plt.style.available else 'classic'
352        plt.style.use(plt_style)
353
354    axes = axes.ravel() if n_images > 1 else [axes]
355
356    for idx, ax in enumerate(axes):
357        if idx >= n_images:
358            ax.axis('off')
359            continue
360        ax.imshow(img_arrays[idx], cmap=cmap)
361        ax.set_title(titles[idx], fontsize=10)
362        if not axis:
363            ax.axis('off')
364
365    plt.subplots_adjust(hspace=hspace, wspace=wspace)
366
367    if save_to:
368        os.makedirs(os.path.dirname(save_to), exist_ok=True)
369        plt.savefig(save_to, dpi=300)
370
371    plt.show()
372    if not use_original_style:
373        plt.style.use('default')

Display multiple sets of sample images (input, prediction, ground truth, difference) side by side for visual comparison.

The function can load images from file paths or accept NumPy arrays directly. It arranges them in a grid and can optionally normalize, invert, or save the output.

Parameters: - input_samples (list[str] or list[np.ndarray]):
Input sample images. - pred_samples (list[str] or list[np.ndarray]):
Model prediction images. - real_samples (list[str] or list[np.ndarray]):
Ground truth images. - model_name (str, optional):
Name of the model to display in titles (default: "Model"). - n_samples (int, optional):
Number of sample groups to display (default: 3). - n_cols (int, optional):
Number of columns per sample group (default: 4).
Typically: Input | Prediction | Ground Truth | Difference. - image_width (int, optional):
Width of one image in inches (default: 4). - cmap (str, optional):
Colormap for displaying grayscale images (default: "gray"). - normalize (bool, optional):
Whether to normalize pixel values to [0, 1] (default: True). - invert (bool, optional):
Whether to invert pixel values (255 - img) (default: False). - axis (bool, optional):
Whether to show image axes (default: False). - save_to (str, optional):
Path to save the figure (default: None). - hspace (float, optional):
Vertical spacing between subplots (default: 0.3). - wspace (float, optional):
Horizontal spacing between subplots (default: 0.2). - use_original_style (bool, optional):
If True, preserves the current matplotlib style (default: False).

Returns:
None

Example:

show_samples(inputs, preds, reals, model_name="UNet", n_samples=5, cmap="gray")

def advanced_imshow( img, title=None, image_width=10, axis=False, color_space='RGB', cmap=None, cols=1, save_to=None, hspace=0.2, wspace=0.2, use_original_style=False, invert=False):
377def advanced_imshow(img, title=None, image_width=10, axis=False,
378           color_space="RGB", cmap=None, cols=1, save_to=None,
379           hspace=0.2, wspace=0.2,
380           use_original_style=False, invert=False):
381    """
382    Display one or multiple images in a flexible and configurable grid.
383
384    This function supports multiple color spaces, automatic reshaping of 
385    input tensors, batch display, color inversion, and saving to disk.
386
387    Parameters:
388        - img (np.ndarray): <br>
389            Input image or batch of images.<br>
390            Accepted shapes:<br>
391            [H, W], [H, W, C], [N, H, W], or [N, H, W, C].
392        - title (str or list[str], optional): <br>
393            Overall or per-image titles.
394        - image_width (int, optional): <br>
395            Width of each image in inches (default: 10).
396        - axis (bool, optional): <br>
397            Whether to show axes (default: False).
398        - color_space (str, optional): <br>
399            Color space of the image: "RGB", "BGR", "gray", or "HSV" (default: "RGB").
400        - cmap (str, optional): <br>
401            Matplotlib colormap for grayscale images (default: None).
402        - cols (int, optional): <br>
403            Number of columns in the subplot grid (default: 1).
404        - save_to (str, optional): <br>
405            File path to save the figure (default: None).
406        - hspace (float, optional): <br>
407            Vertical spacing between subplots (default: 0.2).
408        - wspace (float, optional): <br>
409            Horizontal spacing between subplots (default: 0.2).
410        - use_original_style (bool, optional): <br>
411            Keep current Matplotlib style if True (default: False).
412        - invert (bool, optional): <br>
413            Invert color values (default: False).
414
415    Returns:<br>
416        None
417
418    Example:
419        >>> advanced_imshow(batch_images, cols=3, color_space="BGR", title="Predictions")
420    """
421    original_style = plt.rcParams.copy()
422    try:
423        img_shape = img.shape
424    except Exception:
425        img = np.array(img)
426        img_shape = img.shape
427    # Transform to 4D array [N, H, W, C]
428    if len(img_shape) == 2:
429        img = img.reshape(1, img_shape[0], img_shape[1], 1)
430    elif len(img_shape) == 3:
431        if img_shape[2] in [1, 3]:  # single image with channels
432            img = img.reshape(1, img_shape[0], img_shape[1], img_shape[2])
433        else:  # multiple gray images [N,H,W]
434            img = img[..., np.newaxis]
435    elif len(img_shape) != 4:
436        raise ValueError(f"Image(s) have wrong shape: {img_shape}")
437
438    n_images = img.shape[0]
439    aspect_ratio = img.shape[2] / img.shape[1]
440    rows = n_images // cols + int(n_images % cols > 0)
441    width = int(image_width * cols)
442    height = int(image_width * rows * aspect_ratio)
443
444    if not use_original_style:
445        plt_style = 'seaborn-v0_8' if 'seaborn-v0_8' in plt.style.available else 'classic'
446        plt.style.use(plt_style)
447
448    fig, axes = plt.subplots(nrows=rows, ncols=cols, figsize=(width, height))
449    axes = np.array(axes).ravel()
450    fig.subplots_adjust(hspace=hspace, wspace=wspace)
451
452    if isinstance(title, str):
453        fig.suptitle(title, fontsize=20, y=0.95)
454
455    # Invert images if needed
456    if invert:
457        max_val = 2**(img.dtype.itemsize*8) - 1
458        img = max_val - img
459
460    for idx, ax in enumerate(axes):
461        if idx >= n_images:
462            ax.axis("off")
463            continue
464        cur_img = img[idx]
465
466        # Handle color spaces
467        used_cmap = cmap
468        if color_space.lower() == "bgr" and cur_img.shape[2] == 3:
469            cur_img = cv2.cvtColor(cur_img, cv2.COLOR_BGR2RGB)
470            used_cmap = None
471        elif color_space.lower() == "hsv" and cur_img.shape[2] == 3:
472            cur_img = cv2.cvtColor(cur_img, cv2.COLOR_HSV2RGB)
473            used_cmap = None
474        elif color_space.lower() in ["gray", "grey", "g"]:
475            if cur_img.shape[2] == 3:
476                cur_img = cv2.cvtColor(cur_img, cv2.COLOR_RGB2GRAY)
477            used_cmap = "gray"
478
479        if isinstance(title, (list, tuple)):
480            ax.set_title(title[idx], fontsize=12)
481        if not axis:
482            ax.axis("off")
483
484        ax.imshow(cur_img.squeeze(), cmap=used_cmap)
485
486    if save_to:
487        os.makedirs(os.path.dirname(save_to), exist_ok=True)
488        fig.savefig(save_to, dpi=300)
489
490    plt.show()
491    if not use_original_style:
492        plt.rcParams.update(original_style)

Display one or multiple images in a flexible and configurable grid.

This function supports multiple color spaces, automatic reshaping of input tensors, batch display, color inversion, and saving to disk.

Parameters: - img (np.ndarray):
Input image or batch of images.
Accepted shapes:
[H, W], [H, W, C], [N, H, W], or [N, H, W, C]. - title (str or list[str], optional):
Overall or per-image titles. - image_width (int, optional):
Width of each image in inches (default: 10). - axis (bool, optional):
Whether to show axes (default: False). - color_space (str, optional):
Color space of the image: "RGB", "BGR", "gray", or "HSV" (default: "RGB"). - cmap (str, optional):
Matplotlib colormap for grayscale images (default: None). - cols (int, optional):
Number of columns in the subplot grid (default: 1). - save_to (str, optional):
File path to save the figure (default: None). - hspace (float, optional):
Vertical spacing between subplots (default: 0.2). - wspace (float, optional):
Horizontal spacing between subplots (default: 0.2). - use_original_style (bool, optional):
Keep current Matplotlib style if True (default: False). - invert (bool, optional):
Invert color values (default: False).

Returns:
None

Example:

advanced_imshow(batch_images, cols=3, color_space="BGR", title="Predictions")

def show_images( image_paths: list, title=None, image_width=5, axis=False, color_space='gray', cmap=None, cols=2, save_to=None, hspace=0.01, wspace=0.01, use_original_style=False, invert=False):
496def show_images(image_paths: list, title=None, image_width=5, axis=False,
497                color_space="gray", cmap=None, 
498                cols=2, save_to=None, hspace=0.01, wspace=0.01,
499                use_original_style=False, invert=False):
500    """
501    Load and display multiple images from disk using `advanced_imshow`.
502
503    Parameters:
504        - image_paths (list[str]): <br>
505            List of file paths to load.
506        - title (str or list[str], optional): <br>
507            Plot title(s).
508        - image_width (int, optional): <br>
509            Width of each image (default: 5).
510        - axis (bool, optional): <br>
511            Whether to display axes (default: False).
512        - color_space (str, optional): <br>
513            Color space to convert images to.<br>
514            One of: "gray", "rgb", "hsv", "bgr" (default: "gray").
515        - cmap (str, optional): <br>
516            Colormap for grayscale images (default: None).
517        - cols (int, optional): <br>
518            Number of columns in the grid (default: 2).
519        - save_to (str, optional): <br>
520            Path to save the figure (default: None).
521        - hspace (float, optional): <br>
522            Vertical spacing between subplots (default: 0.01).
523        - wspace (float, optional): <br>
524            Horizontal spacing between subplots (default: 0.01).
525        - use_original_style (bool, optional): <br>
526            Keep current Matplotlib style (default: False).
527        - invert (bool, optional): <br>
528            Whether to invert images (default: False).
529
530    Returns:
531        np.ndarray: Loaded images stacked as an array.
532
533    Example:
534        >>> show_images(["img1.png", "img2.png"], color_space="rgb", cols=2)
535    """
536    images = []
537    for img_path in image_paths:
538        img = cv2.imread(img_path)
539        if color_space.lower() == "rgb":
540            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
541        elif color_space.lower() == "hsv":
542            img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
543        elif color_space.lower() in ["gray", "grey", "g"]:
544            img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
545        images.append(img)
546
547    images = np.array(images)
548    advanced_imshow(images, title=title, image_width=image_width, axis=axis,
549           color_space=color_space, cmap=cmap, cols=cols, save_to=save_to,
550           hspace=hspace, wspace=wspace,
551           use_original_style=use_original_style, invert=invert)
552    return images

Load and display multiple images from disk using advanced_imshow.

Parameters: - image_paths (list[str]):
List of file paths to load. - title (str or list[str], optional):
Plot title(s). - image_width (int, optional):
Width of each image (default: 5). - axis (bool, optional):
Whether to display axes (default: False). - color_space (str, optional):
Color space to convert images to.
One of: "gray", "rgb", "hsv", "bgr" (default: "gray"). - cmap (str, optional):
Colormap for grayscale images (default: None). - cols (int, optional):
Number of columns in the grid (default: 2). - save_to (str, optional):
Path to save the figure (default: None). - hspace (float, optional):
Vertical spacing between subplots (default: 0.01). - wspace (float, optional):
Horizontal spacing between subplots (default: 0.01). - use_original_style (bool, optional):
Keep current Matplotlib style (default: False). - invert (bool, optional):
Whether to invert images (default: False).

Returns: np.ndarray: Loaded images stacked as an array.

Example:

show_images(["img1.png", "img2.png"], color_space="rgb", cols=2)

def plot_image_with_values( img, block_size=8, cmap='gray', title=None, font_size=6, save_to=None):
556def plot_image_with_values(img, block_size=8, cmap='gray', title=None, 
557                           font_size=6, save_to=None):
558    """
559    Plot an image with annotated mean values over non-overlapping blocks.
560
561    Each block represents the mean pixel intensity of its region. The mean
562    values are displayed as text annotations directly on the image.
563
564    Parameters:
565        - img (np.ndarray): <br>
566            2D grayscale image (H, W) or 3D single-channel image (H, W, 1).
567        - block_size (int or tuple, optional): <br>
568            Size of each block (default: 8).
569        - cmap (str, optional): <br>
570            Matplotlib colormap (default: "gray").
571        - title (str, optional): <br>
572            Plot title (default: None).
573        - font_size (int, optional): <br>
574            Font size of value annotations (default: 6).
575        - save_to (str, optional): <br>
576            Path to save the figure (default: None).
577
578    Returns:<br>
579        None
580
581    Example:
582        ```python
583        from imshow import plot_image_with_values
584        import cv2
585
586        img = cv2.imread('example.png', cv2.IMREAD_GRAYSCALE)
587        plot_image_with_values(img, block_size=16, cmap='gray', title='Mean Block Values', font_size=8)
588        ```
589
590        Or:
591        ```python
592        fig, ax = plt.subplots(nrows=2, ncols=4, figsize=(4*4, 6))
593        idx = 0
594
595        img_1 = plot(ax[0][0], path=input_samples[idx], title=f"Input", cmap="gray")
596        plot_image_with_values(img_1, block_size=16, ax=ax[1][0])
597
598        img_2 = plot(ax[0][1], path=pred_model[idx], title=f"{model_name}")
599        plot_image_with_values(img_2, block_size=16, ax=ax[1][1])
600
601        img_3 = plot(ax[0][2], path=real[idx], title=f"ground truth")
602        plot_image_with_values(img_3, block_size=16, ax=ax[1][2])
603
604        img_4 = plot(ax[0][3], path=pred_model[idx], title=f"Difference", sub_image=real[idx])
605        plot_image_with_values(img_4, block_size=16, ax=ax[1][3])
606
607        plt.show()
608        ```
609    """
610    # Ensure 2D image
611    if img.ndim == 3 and img.shape[2] == 1:
612        img = img[:, :, 0]
613    elif img.ndim == 3 and img.shape[2] > 1:
614        raise ValueError("Only grayscale images are supported for block annotation.")
615
616    # Compute mean over blocks
617    mean_img = block_reduce(img, block_size=(block_size, block_size), func=np.mean)
618    max_value = mean_img.max()
619
620    # Plot
621    plt.figure(figsize=(mean_img.shape[1]/2, mean_img.shape[0]/2))
622    plt.imshow(mean_img, cmap=cmap, interpolation='nearest')
623    plt.colorbar(label='Mean Value')
624
625    # Annotate each block
626    for i in range(mean_img.shape[0]):
627        for j in range(mean_img.shape[1]):
628            val = mean_img[i, j]
629            color = 'white' if val < max_value/1.5 else 'black'
630            plt.text(j, i, f'{val:.1f}', ha='center', va='center', color=color, fontsize=font_size)
631
632    plt.title(title or f'Mean Values over {block_size}x{block_size} Blocks')
633    plt.xticks([])
634    plt.yticks([])
635    plt.tight_layout()
636
637    if save_to:
638        os.makedirs(os.path.dirname(save_to), exist_ok=True)
639        plt.savefig(save_to, dpi=300)
640    
641    plt.show()

Plot an image with annotated mean values over non-overlapping blocks.

Each block represents the mean pixel intensity of its region. The mean values are displayed as text annotations directly on the image.

Parameters: - img (np.ndarray):
2D grayscale image (H, W) or 3D single-channel image (H, W, 1). - block_size (int or tuple, optional):
Size of each block (default: 8). - cmap (str, optional):
Matplotlib colormap (default: "gray"). - title (str, optional):
Plot title (default: None). - font_size (int, optional):
Font size of value annotations (default: 6). - save_to (str, optional):
Path to save the figure (default: None).

Returns:
None

Example:

from imshow import plot_image_with_values
import cv2

img = cv2.imread('example.png', cv2.IMREAD_GRAYSCALE)
plot_image_with_values(img, block_size=16, cmap='gray', title='Mean Block Values', font_size=8)
Or:
fig, ax = plt.subplots(nrows=2, ncols=4, figsize=(4*4, 6))
idx = 0

img_1 = plot(ax[0][0], path=input_samples[idx], title=f"Input", cmap="gray")
plot_image_with_values(img_1, block_size=16, ax=ax[1][0])

img_2 = plot(ax[0][1], path=pred_model[idx], title=f"{model_name}")
plot_image_with_values(img_2, block_size=16, ax=ax[1][1])

img_3 = plot(ax[0][2], path=real[idx], title=f"ground truth")
plot_image_with_values(img_3, block_size=16, ax=ax[1][2])

img_4 = plot(ax[0][3], path=pred_model[idx], title=f"Difference", sub_image=real[idx])
plot_image_with_values(img_4, block_size=16, ax=ax[1][3])

plt.show()
def show_image_with_line_and_profile(imgs, axis='row', index=None, titles=None, figsize=(10, 4)):
644def show_image_with_line_and_profile(imgs, axis='row', index=None, titles=None, figsize=(10, 4)):
645    """
646    Display one or multiple grayscale images with a highlighted line (row or column)
647    and plot the corresponding pixel intensity profile below or beside each image.
648
649    Parameters:
650        - imgs (list[np.ndarray]):<br>
651            List of grayscale images to analyze.
652        - axis (str, optional):<br>
653            Direction of the line ("row" or "column") (default: "row").
654        - index (int, optional): <br>
655            Index of the selected line. If None, the central line is used (default: None).
656        - titles (list[str], optional): <br>
657            Titles for each image (default: ["Image 1", "Image 2", ...]).
658        - figsize (tuple, optional): <br>
659            Figure size per image pair (default: (10, 4)).
660
661    Returns:
662        - list[np.ndarray]: <br>
663            List of pixel intensity profiles corresponding to the selected line in each image.
664
665    Example:
666        >>> show_image_with_line_and_profile(
667        ...     imgs=[img_input, img_pred, img_gt],
668        ...     axis="row",
669        ...     titles=["Input", "Prediction", "Ground Truth"]
670        ... )
671    """
672    assert axis in ['row', 'column'], "axis must be 'row' or 'column'"
673
674    n_images = len(imgs)
675    if titles is None:
676        titles = [f"Image {i+1}" for i in range(n_images)]
677    line_values_list = []
678
679    fig, axes = plt.subplots(n_images, 2, figsize=(figsize[0]*2, figsize[1]*n_images))
680
681    # Ensure axes is 2D
682    if n_images == 1:
683        axes = np.expand_dims(axes, axis=0)
684
685    for i, (img, ax_pair, title) in enumerate(zip(imgs, axes, titles)):
686        h, w = img.shape
687        if index is None:
688            idx = h // 2 if axis == 'row' else w // 2
689        else:
690            idx = index
691
692        ax_img, ax_profile = ax_pair
693
694        # Show image with line
695        ax_img.imshow(img, cmap='gray', interpolation='nearest')
696        if axis == 'row':
697            ax_img.axhline(idx, color='red', linewidth=1)
698            line_values = img[idx, :]
699        else:
700            ax_img.axvline(idx, color='red', linewidth=1)
701            line_values = img[:, idx]
702        line_values_list.append(line_values)
703
704        ax_img.set_title(title)
705        ax_img.set_xticks([])
706        ax_img.set_yticks([])
707
708        # Plot pixel values along the line
709        x = np.arange(len(line_values))
710        ax_profile.plot(x, line_values, color='black', linewidth=1)
711        ax_profile.set_title(f'Pixel values along {axis} {idx}')
712        ax_profile.set_xlabel('Pixel index')
713        ax_profile.set_ylabel('Intensity')
714        ax_profile.grid(True)
715
716    plt.tight_layout()
717    plt.show()
718
719    return line_values_list

Display one or multiple grayscale images with a highlighted line (row or column) and plot the corresponding pixel intensity profile below or beside each image.

Parameters: - imgs (list[np.ndarray]):
List of grayscale images to analyze. - axis (str, optional):
Direction of the line ("row" or "column") (default: "row"). - index (int, optional):
Index of the selected line. If None, the central line is used (default: None). - titles (list[str], optional):
Titles for each image (default: ["Image 1", "Image 2", ...]). - figsize (tuple, optional):
Figure size per image pair (default: (10, 4)).

Returns: - list[np.ndarray]:
List of pixel intensity profiles corresponding to the selected line in each image.

Example:

show_image_with_line_and_profile( ... imgs=[img_input, img_pred, img_gt], ... axis="row", ... titles=["Input", "Prediction", "Ground Truth"] ... )