Metadata-Version: 2.4
Name: img-show
Version: 1.0.0
Summary: A opinionated tool for coercing data into something displayable
Project-URL: Homepage, https://github.com/belfner/img-show
Project-URL: Repository, https://github.com/belfner/img-show
Author-email: Ben Elfner <belfner@belfner.com>
License: MIT
License-File: LICENSE
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: Unix
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Requires-Dist: numpy>=1.24.4
Provides-Extra: opencv
Requires-Dist: opencv-python>=4.5.4.60; extra == 'opencv'
Provides-Extra: opencv-contrib
Requires-Dist: opencv-contrib-python>=4.5.4.60; extra == 'opencv-contrib'
Provides-Extra: opencv-contrib-headless
Requires-Dist: opencv-contrib-python-headless>=4.5.4.60; extra == 'opencv-contrib-headless'
Provides-Extra: opencv-headless
Requires-Dist: opencv-python-headless>=4.5.4.60; extra == 'opencv-headless'
Description-Content-Type: text/markdown

# img-show

`img-show` is a thin, opinionated wrapper around OpenCV for displaying images. It accepts NumPy arrays, PyTorch tensors, and PIL images; auto-coerces shapes, dtypes, and channel orders; resizes windows to fit the screen; alpha-composites RGBA over a checkerboard; and renders inline as PNG when running inside a Jupyter notebook.

## Installation

`img-show` depends only on NumPy at install time. OpenCV is selected through one of four mutually-exclusive extras so you can match it to your environment (desktop, server, or contrib build):

```bash
pip install img-show[opencv]                  # standard build with GUI
pip install img-show[opencv-contrib]          # standard + contrib modules
pip install img-show[opencv-headless]         # no GUI (servers, CI, Jupyter-only)
pip install img-show[opencv-contrib-headless] # headless + contrib modules
```

Pick exactly one. The extras are mutually exclusive at the resolver level (uv enforces this via a `conflicts` entry); installing two OpenCV variants side-by-side results in a broken `cv2` import.

If you already manage `cv2` yourself (for example with a system package or a custom wheel), install the bare package:

```bash
pip install img-show
```

`img-show` will detect the existing `cv2` import at runtime.

### Requirements

- Python >= 3.10
- NumPy >= 1.24.4
- OpenCV >= 4.5.4.60 (provided by one of the extras above)
- Tkinter (used only to query screen size; if unavailable a 1920x1080 fallback is used)

### Optional dependencies

- **PIL / Pillow** — enables passing `PIL.Image.Image` objects directly; not required for NumPy or PyTorch inputs.
- **PyTorch** — enables passing `torch.Tensor` objects directly.
- **IPython** — required only when running inside a Jupyter notebook; used for inline PNG rendering.

## Quick start

```python
import numpy as np
from img_show import show_img

img = np.random.rand(256, 256, 3)
show_img(img)
```

## Capability detection

At import time `img-show` probes the runtime environment and exposes two read-only flags:

```python
from img_show import HAS_CV2, IS_HEADLESS

HAS_CV2      # bool         — True when cv2 is importable
IS_HEADLESS  # bool | None  — True for headless cv2 builds,
             #                False for GUI builds,
             #                None when cv2 is missing or the provider
             #                cannot be identified
```

Headless detection uses `importlib.metadata.packages_distributions()` to look up which PyPI distribution provides the `cv2` import name. The check is fast and runs once at import.

Use these flags to branch your own code if you want to avoid `RuntimeError` from the display functions:

```python
from img_show import HAS_CV2, IS_HEADLESS, show_img

if HAS_CV2 and IS_HEADLESS is False:
    show_img(my_image)
else:
    # fall back to saving or logging the array
    ...
```

## Function requirements

Each public function has a different capability surface. The table below describes what must be available for each function to succeed:

| Function       | cv2 required | GUI build required           | Display required             | Works in Jupyter            |
|----------------|--------------|------------------------------|------------------------------|-----------------------------|
| `coerce_img`   | no           | no                           | no                           | yes                         |
| `show_img`     | yes          | yes (outside Jupyter only)   | yes (outside Jupyter only)   | yes (renders inline as PNG) |
| `show_imgs`    | yes          | yes (outside Jupyter only)   | yes (outside Jupyter only)   | yes (renders inline as PNG) |
| `close_all`    | yes          | no                           | no                           | yes (no-op if no windows)   |

When a requirement is unmet, the function raises `RuntimeError` at call time with a message that tells the user which extra to install. `coerce_img` is the only function that always works, because it is pure NumPy.

## Jupyter inline rendering

When `img-show` detects a `ZMQInteractiveShell` (the standard Jupyter kernel), `show_img` and `show_imgs` encode each image to PNG with `cv2.imencode` and emit it through `IPython.display.Image`, captioned by the window name. The GUI and display requirements are skipped on this path, so a headless OpenCV build inside Jupyter is fully supported.

```python
# In a Jupyter cell
from img_show import show_img
show_img(my_image, window_name='Step 3 output')
```

## Usage

### Multiple input types

```python
import numpy as np
import torch
from PIL import Image
from img_show import show_img

show_img(np.random.rand(256, 256, 3))                # NumPy array
show_img(torch.randn(3, 256, 256))                   # PyTorch tensor (channels-first)
show_img(Image.open('photo.png'))                    # PIL image
```

PIL images use RGB channel order; `img-show` reorders them to BGR for correct OpenCV display. NumPy and PyTorch inputs are assumed to be BGR already (or single-channel) and pass through unchanged.

### Shape coercion

Singleton dimensions are squeezed automatically, and channels-first layouts are transposed to channels-last:

```python
show_img(np.random.rand(1, 256, 256, 3))      # leading singleton stripped
show_img(np.random.rand(1, 1, 3, 256, 256))   # channels-first detected and fixed
show_img(np.random.rand(256, 256, 1))         # grayscale with trailing singleton
```

### Dtype normalization

The table below describes the dtype that `coerce_img` returns. The display path (`show_img` / `show_imgs`) then converts every result to uint8 before handing it to OpenCV — uint16 is scaled by `1/257`, floats in `[0, 1]` are scaled by `255`, and anything else is clipped into `[0, 255]`.

| Input dtype     | `coerce_img` output                                                 |
|-----------------|----------------------------------------------------------------------|
| `uint8`         | passed through unchanged                                             |
| `uint16`        | passed through unchanged (display path will rescale to uint8)        |
| `bool`          | scaled to 0/255 uint8                                                |
| other integer   | scaled to the 0-1 range as float64                                   |
| float           | scaled to 0-1 if out of range or near-constant                       |

### Alpha compositing

Four-channel images (RGBA from PIL, or BGRA arrays) are composited over a gray checkerboard at display time. The original four-channel array is preserved by `coerce_img`; compositing happens only in the display pipeline.

### Window sizing

If the image is taller than the screen (minus a 250-pixel toolbar margin) or wider than the screen, a resizable window is created and sized to fit while preserving aspect ratio. Otherwise an auto-sizing window is created.

### Multiple images

```python
from img_show import show_imgs

show_imgs(
    [img1, img2, img3],
    window_names=['Original', 'Filtered', 'Diff'],
)
```

When `window_names` is omitted the names default to `Image 1`, `Image 2`, ... A collision with an already-tracked window appends `(2)`, `(3)`, ...

### Keeping windows open

Pass `destroy_window=False` (or `destroy_windows=False`) to leave windows open after `show_img`/`show_imgs` returns. The names are tracked internally; call `close_all` to dismiss them later:

```python
from img_show import show_img, close_all

show_img(img1, window_name='Step 1', destroy_window=False)
show_img(img2, window_name='Step 2', destroy_window=False)

# ... later
close_all()
```

`close_all` silently ignores windows that have already been closed by other means (for example by clicking the X button).

## API reference

### `show_img(img, window_name=' ', wait_delay=0, do_wait=True, destroy_window=True)`

Display a single image. Coerces and prepares the input, then either opens an OpenCV window or renders inline in Jupyter.

| Parameter        | Type      | Default | Description                                                                                                  |
|------------------|-----------|---------|--------------------------------------------------------------------------------------------------------------|
| `img`            | Any       |         | NumPy array, PyTorch tensor, or PIL image.                                                                   |
| `window_name`    | str       | `' '`   | Window title (also used as the caption in Jupyter).                                                          |
| `wait_delay`     | int       | `0`     | Milliseconds to wait for a keypress when `do_wait=True`. `0` waits indefinitely.                             |
| `do_wait`        | bool      | `True`  | When `True`, block on `cv2.waitKey`. When `False`, paint the window briefly and return.                      |
| `destroy_window` | bool      | `True`  | When `True`, close the window after waiting. When `False`, leave it open and track it for `close_all`.       |

**Raises** `RuntimeError` when OpenCV is missing, when a headless build is installed and the call is made outside Jupyter, or when no display environment is available outside Jupyter.

### `show_imgs(imgs, window_names=None, wait_delay=0, do_wait=True, destroy_windows=True)`

Display multiple images. Each image opens its own window (or renders inline in Jupyter).

| Parameter         | Type                | Default | Description                                                                       |
|-------------------|---------------------|---------|-----------------------------------------------------------------------------------|
| `imgs`            | Iterable[Any]       |         | Collection of images.                                                             |
| `window_names`    | Iterable[str] \| None | `None`  | Names matching `imgs`. When `None`, auto-generated as `Image 1`, `Image 2`, ... |
| `wait_delay`      | int                 | `0`     | Same as `show_img`.                                                              |
| `do_wait`         | bool                | `True`  | Same as `show_img`.                                                              |
| `destroy_windows` | bool                | `True`  | Same as `show_img`'s `destroy_window`.                                           |

**Raises** `ValueError` when `len(window_names) != len(imgs)`. Same `RuntimeError` cases as `show_img`.

### `coerce_img(img) -> numpy.ndarray`

Convert a NumPy array, PyTorch tensor, or PIL image into a displayable NumPy array. Returns a uint8, uint16, or normalized floating-point array depending on the input. Four-channel inputs remain four-channel; alpha compositing is applied only by the display path.

This function has no runtime requirements beyond NumPy and always works.

### `close_all() -> None`

Close every window opened by `show_img` or `show_imgs` with `destroy_window=False` and clear the tracking set. Already-closed windows are ignored silently.

**Raises** `RuntimeError` when OpenCV is missing.

### Module attributes

- `HAS_CV2: bool` — True when `cv2` is importable.
- `IS_HEADLESS: bool | None` — True for a headless cv2 build, False for a GUI build, None when cv2 is missing or the provider cannot be identified.
- `open_window_names: set[str]` — Names of currently tracked windows. Modified by `show_img`, `show_imgs`, and `close_all`.

## License

`img-show` is licensed under the [MIT License](LICENSE).

## Author

Ben Elfner
