haybale-image 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- haybale_image/OVERVIEW.md +30 -0
- haybale_image/QUICKREF.md +27 -0
- haybale_image/__init__.py +43 -0
- haybale_image/adapters/__init__.py +0 -0
- haybale_image/adapters/image_adapters.py +51 -0
- haybale_image/docs/haybale-image.adapter.ImageToGrayAdapter.md +15 -0
- haybale_image/docs/haybale-image.adapter.ImageToRgbAdapter.md +15 -0
- haybale_image/docs/haybale-image.adapter.ImageToRgbaAdapter.md +15 -0
- haybale_image/docs/haybale-image.node.ImageInfoNode.md +23 -0
- haybale_image/docs/haybale-image.node.LoadImageNode.md +37 -0
- haybale_image/docs/haybale-image.node.SaveImageNode.md +63 -0
- haybale_image/docs/haybale-image.node.ViewImageNode.md +40 -0
- haybale_image/docs/haybale-image.panel.ImageSettingsPanel.md +14 -0
- haybale_image/docs/haybale-image.setting.ImageSettings.md +9 -0
- haybale_image/docs/haybale-image.type.IMAGE.md +15 -0
- haybale_image/docs/haybale-image.type.IMAGE_GRAY.md +15 -0
- haybale_image/docs/haybale-image.type.IMAGE_RGB.md +15 -0
- haybale_image/docs/haybale-image.type.IMAGE_RGBA.md +15 -0
- haybale_image/docs/haybale-image.widget.ImageViewerWidget.md +37 -0
- haybale_image/haybale.toml +13 -0
- haybale_image/helpers.py +110 -0
- haybale_image/io/__init__.py +23 -0
- haybale_image/io/_avif.py +49 -0
- haybale_image/io/codecs.py +245 -0
- haybale_image/io/formats.py +74 -0
- haybale_image/io/placement.py +92 -0
- haybale_image/nodes/__init__.py +0 -0
- haybale_image/nodes/image_info.py +44 -0
- haybale_image/nodes/load_image.py +58 -0
- haybale_image/nodes/save_image.py +228 -0
- haybale_image/nodes/view_image.py +101 -0
- haybale_image/panels/__init__.py +1 -0
- haybale_image/panels/image_settings_panel.py +31 -0
- haybale_image/py.typed +0 -0
- haybale_image/settings/__init__.py +0 -0
- haybale_image/settings/image_settings.py +51 -0
- haybale_image/types/__init__.py +0 -0
- haybale_image/types/image.py +109 -0
- haybale_image/value/__init__.py +8 -0
- haybale_image/value/_numpy_ops.py +81 -0
- haybale_image/value/_torch_ops.py +190 -0
- haybale_image/value/enums.py +18 -0
- haybale_image/value/image_value.py +304 -0
- haybale_image/value/layout.py +41 -0
- haybale_image/value/metadata.py +21 -0
- haybale_image/viewer/__init__.py +5 -0
- haybale_image/viewer/backend.py +299 -0
- haybale_image/viewer/image_viewer.js +337 -0
- haybale_image/widgets/__init__.py +0 -0
- haybale_image/widgets/image_viewer_widget.py +86 -0
- haybale_image-0.3.2.dist-info/METADATA +13 -0
- haybale_image-0.3.2.dist-info/RECORD +54 -0
- haybale_image-0.3.2.dist-info/WHEEL +4 -0
- haybale_image-0.3.2.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Image
|
|
2
|
+
|
|
3
|
+
Image types, IO and viewers shared by vision and ML libraries
|
|
4
|
+
|
|
5
|
+
## Nodes
|
|
6
|
+
### Image
|
|
7
|
+
- **Image Info** — Reports the width, height, channel layout and bit depth of an image
|
|
8
|
+
- **Load Image** — Reads an image file from a volume
|
|
9
|
+
- **Save Image** — Writes an image to a file in a volume, in the format its extension names
|
|
10
|
+
- **View Image** — Shows an image on the node, as a live stream or as a still picture
|
|
11
|
+
|
|
12
|
+
## Types
|
|
13
|
+
- **Gray image** — A one-channel picture.
|
|
14
|
+
- **Image** — A picture of any channel layout. Pixels are kept as made and converted on demand.
|
|
15
|
+
- **RGB image** — A three-channel picture.
|
|
16
|
+
- **RGBA image** — A picture with three colour channels and transparency.
|
|
17
|
+
|
|
18
|
+
## Adapters
|
|
19
|
+
- **ImageToGrayAdapter** — Convert an image to gray
|
|
20
|
+
- **ImageToRgbAdapter** — Convert an image to RGB
|
|
21
|
+
- **ImageToRgbaAdapter** — Convert an image to RGBA
|
|
22
|
+
|
|
23
|
+
## Widgets
|
|
24
|
+
- **ImageViewerWidget** — Shows an image, as a live stream or as a still picture
|
|
25
|
+
|
|
26
|
+
## Settings
|
|
27
|
+
- **Image** — Defaults for saving images
|
|
28
|
+
|
|
29
|
+
## Panels
|
|
30
|
+
- **Image** — Defaults for saving images.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# haybale-image — component index (v0.3.2)
|
|
2
|
+
|
|
3
|
+
## node
|
|
4
|
+
- `haybale-image:node:ImageInfoNode` — Image Info — Reports the width, height, channel layout and bit depth of an image _tags: image, info, size, width, height, layout, depth, channels_
|
|
5
|
+
- `haybale-image:node:LoadImageNode` — Load Image — Reads an image file from a volume _tags: image, load, open, read, file, png, jpg, tiff, import_
|
|
6
|
+
- `haybale-image:node:SaveImageNode` — Save Image — Writes an image to a file in a volume, in the format its extension names _tags: image, save, write, export, file, png, jpg, tiff, webp_
|
|
7
|
+
- `haybale-image:node:ViewImageNode` — View Image — Shows an image on the node, as a live stream or as a still picture _tags: image, view, show, preview, display, viewer, stream_
|
|
8
|
+
|
|
9
|
+
## type
|
|
10
|
+
- `haybale-image:type:IMAGE` — Image — A picture of any channel layout. Pixels are kept as made and converted on demand.
|
|
11
|
+
- `haybale-image:type:IMAGE_GRAY` — Gray image — A one-channel picture.
|
|
12
|
+
- `haybale-image:type:IMAGE_RGB` — RGB image — A three-channel picture.
|
|
13
|
+
- `haybale-image:type:IMAGE_RGBA` — RGBA image — A picture with three colour channels and transparency.
|
|
14
|
+
|
|
15
|
+
## adapter
|
|
16
|
+
- `haybale-image:adapter:ImageToGrayAdapter` — ImageToGrayAdapter — Convert an image to gray
|
|
17
|
+
- `haybale-image:adapter:ImageToRgbAdapter` — ImageToRgbAdapter — Convert an image to RGB
|
|
18
|
+
- `haybale-image:adapter:ImageToRgbaAdapter` — ImageToRgbaAdapter — Convert an image to RGBA
|
|
19
|
+
|
|
20
|
+
## widget
|
|
21
|
+
- `haybale-image:widget:ImageViewerWidget` — ImageViewerWidget — Shows an image, as a live stream or as a still picture
|
|
22
|
+
|
|
23
|
+
## setting
|
|
24
|
+
- `haybale-image:setting:ImageSettings` — Image — Defaults for saving images
|
|
25
|
+
|
|
26
|
+
## panel
|
|
27
|
+
- `haybale-image:panel:ImageSettingsPanel` — Image — Defaults for saving images.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Haybale-Image: image values shared by vision and ML libraries.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
- ImageValue (``haybale_image.value``): an image with fixed semantics (RGB order,
|
|
5
|
+
values in [0, 1], layout, source depth) and free storage (dtype, channel order,
|
|
6
|
+
numpy array or torch tensor on any device), reached only through accessors.
|
|
7
|
+
|
|
8
|
+
Component folders (registered in ``register_components``):
|
|
9
|
+
- ``settings``, ``types``, ``adapters``, ``widgets``, ``nodes``, ``panels``: the IMAGE types,
|
|
10
|
+
adapters, widgets, IO nodes and the library settings panel.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from haywire.core.library.base import BaseLibrary
|
|
16
|
+
from haywire.core.library.decorator import library
|
|
17
|
+
from haywire.core.settings.registry import SettingsRegistry
|
|
18
|
+
from haywire.core.adapter.registry import AdapterRegistry
|
|
19
|
+
from haywire.core.node.registry import NodeRegistry
|
|
20
|
+
from haywire.core.types.registry import TypeRegistry
|
|
21
|
+
from haywire.ui.panel.registry import PanelRegistry
|
|
22
|
+
from haywire.ui.widget.registry import WidgetRegistry
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@library(
|
|
26
|
+
file_watcher=True,
|
|
27
|
+
)
|
|
28
|
+
class Library(BaseLibrary):
|
|
29
|
+
"""Image library — image values, types and IO."""
|
|
30
|
+
|
|
31
|
+
def register_components(self):
|
|
32
|
+
"""Register the component folders with their registries."""
|
|
33
|
+
base_path = Path(__file__).parent
|
|
34
|
+
|
|
35
|
+
self.add_folder_to_registry(folder_path=str(base_path / "settings"), registry_cls=SettingsRegistry)
|
|
36
|
+
self.add_folder_to_registry(folder_path=str(base_path / "types"), registry_cls=TypeRegistry)
|
|
37
|
+
self.add_folder_to_registry(folder_path=str(base_path / "adapters"), registry_cls=AdapterRegistry)
|
|
38
|
+
self.add_folder_to_registry(folder_path=str(base_path / "widgets"), registry_cls=WidgetRegistry)
|
|
39
|
+
self.add_folder_to_registry(folder_path=str(base_path / "nodes"), registry_cls=NodeRegistry)
|
|
40
|
+
self.add_folder_to_registry(folder_path=str(base_path / "panels"), registry_cls=PanelRegistry)
|
|
41
|
+
|
|
42
|
+
def validate(self) -> bool:
|
|
43
|
+
return True
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Layout adapters: any image to a gray, RGB or RGBA image."""
|
|
2
|
+
|
|
3
|
+
from typing_extensions import override
|
|
4
|
+
|
|
5
|
+
from haywire.core.adapter.base import BaseAdapter, adapter
|
|
6
|
+
|
|
7
|
+
from haybale_image.types.image import IMAGE, IMAGE_GRAY, IMAGE_RGB, IMAGE_RGBA
|
|
8
|
+
from haybale_image.value import ImageValue, Layout, convert_layout
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _sample(layout: Layout) -> ImageValue:
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
channels = {Layout.GRAY: (), Layout.RGB: (3,), Layout.RGBA: (4,)}[layout]
|
|
15
|
+
return ImageValue.from_array(np.zeros((2, 2, *channels), dtype=np.uint8))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@adapter(description="Convert an image to gray", converts_from=IMAGE, converts_to=IMAGE_GRAY)
|
|
19
|
+
class ImageToGrayAdapter(BaseAdapter):
|
|
20
|
+
"""Convert an image to one gray channel (luma of the colour channels)."""
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
def convert(self, value: ImageValue) -> ImageValue:
|
|
24
|
+
return convert_layout(value, Layout.GRAY)
|
|
25
|
+
|
|
26
|
+
def get_test_value(self) -> ImageValue:
|
|
27
|
+
return _sample(Layout.RGB)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@adapter(description="Convert an image to RGB", converts_from=IMAGE, converts_to=IMAGE_RGB)
|
|
31
|
+
class ImageToRgbAdapter(BaseAdapter):
|
|
32
|
+
"""Convert an image to three colour channels, dropping transparency or repeating gray."""
|
|
33
|
+
|
|
34
|
+
@override
|
|
35
|
+
def convert(self, value: ImageValue) -> ImageValue:
|
|
36
|
+
return convert_layout(value, Layout.RGB)
|
|
37
|
+
|
|
38
|
+
def get_test_value(self) -> ImageValue:
|
|
39
|
+
return _sample(Layout.GRAY)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@adapter(description="Convert an image to RGBA", converts_from=IMAGE, converts_to=IMAGE_RGBA)
|
|
43
|
+
class ImageToRgbaAdapter(BaseAdapter):
|
|
44
|
+
"""Convert an image to colour plus transparency, fully opaque when it had none."""
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
def convert(self, value: ImageValue) -> ImageValue:
|
|
48
|
+
return convert_layout(value, Layout.RGBA)
|
|
49
|
+
|
|
50
|
+
def get_test_value(self) -> ImageValue:
|
|
51
|
+
return _sample(Layout.RGB)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# ImageToGrayAdapter
|
|
2
|
+
|
|
3
|
+
`haybale-image:adapter:ImageToGrayAdapter` · kind: adapter
|
|
4
|
+
|
|
5
|
+
Convert an image to gray
|
|
6
|
+
|
|
7
|
+
## Details
|
|
8
|
+
|
|
9
|
+
- **converts_from**: `haybale-image:type:IMAGE`
|
|
10
|
+
- **converts_to**: `haybale-image:type:IMAGE_GRAY`
|
|
11
|
+
- **priority**: `0`
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
|
|
15
|
+
Convert an image to one gray channel (luma of the colour channels).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# ImageToRgbAdapter
|
|
2
|
+
|
|
3
|
+
`haybale-image:adapter:ImageToRgbAdapter` · kind: adapter
|
|
4
|
+
|
|
5
|
+
Convert an image to RGB
|
|
6
|
+
|
|
7
|
+
## Details
|
|
8
|
+
|
|
9
|
+
- **converts_from**: `haybale-image:type:IMAGE`
|
|
10
|
+
- **converts_to**: `haybale-image:type:IMAGE_RGB`
|
|
11
|
+
- **priority**: `0`
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
|
|
15
|
+
Convert an image to three colour channels, dropping transparency or repeating gray.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# ImageToRgbaAdapter
|
|
2
|
+
|
|
3
|
+
`haybale-image:adapter:ImageToRgbaAdapter` · kind: adapter
|
|
4
|
+
|
|
5
|
+
Convert an image to RGBA
|
|
6
|
+
|
|
7
|
+
## Details
|
|
8
|
+
|
|
9
|
+
- **converts_from**: `haybale-image:type:IMAGE`
|
|
10
|
+
- **converts_to**: `haybale-image:type:IMAGE_RGBA`
|
|
11
|
+
- **priority**: `0`
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
|
|
15
|
+
Convert an image to colour plus transparency, fully opaque when it had none.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Image Info
|
|
2
|
+
|
|
3
|
+
`haybale-image:node:ImageInfoNode` · kind: node
|
|
4
|
+
|
|
5
|
+
Reports the width, height, channel layout and bit depth of an image
|
|
6
|
+
|
|
7
|
+
## Ports
|
|
8
|
+
|
|
9
|
+
| id | direction | type | description |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `image` | inlet | `haybale-image:type:IMAGE` | Image to describe |
|
|
12
|
+
| `width` | outlet | `haywire-core:type:INT` | Width in pixels |
|
|
13
|
+
| `height` | outlet | `haywire-core:type:INT` | Height in pixels |
|
|
14
|
+
| `layout` | outlet | `haywire-core:type:STRING` | Channels: gray, rgb or rgba |
|
|
15
|
+
| `depth` | outlet | `haywire-core:type:INT` | Bits per channel of the source file, such as 8 or 16 |
|
|
16
|
+
|
|
17
|
+
## Notes
|
|
18
|
+
|
|
19
|
+
Reports the width, height, channel layout and bit depth of an `Image`.
|
|
20
|
+
|
|
21
|
+
- `Width`, `Height`: size in pixels.
|
|
22
|
+
- `Layout`: `gray`, `rgb` or `rgba`.
|
|
23
|
+
- `Depth`: bits per channel of the file the image was read from (8, 16 or 32).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Load Image
|
|
2
|
+
|
|
3
|
+
`haybale-image:node:LoadImageNode` · kind: node
|
|
4
|
+
|
|
5
|
+
Reads an image file from a volume
|
|
6
|
+
|
|
7
|
+
## Ports
|
|
8
|
+
|
|
9
|
+
| id | direction | type | description |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `path` | inlet | `haywire-core:type:FILE_PATH` | Image file to read |
|
|
12
|
+
| `progress.main` | config | `haywire-core:type:PROGRESS` | |
|
|
13
|
+
| `image` | outlet | `haybale-image:type:IMAGE` | The decoded image |
|
|
14
|
+
|
|
15
|
+
## Settings
|
|
16
|
+
|
|
17
|
+
| name | bag | default | description |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| `main` | progress | `PROGRESS(active=False, fraction=None, label='')` | |
|
|
20
|
+
|
|
21
|
+
## Notes
|
|
22
|
+
|
|
23
|
+
Reads an image file from a volume into an `Image`.
|
|
24
|
+
|
|
25
|
+
Gray files stay gray, files with transparency become RGBA and everything else
|
|
26
|
+
is RGB. The camera's rotation (EXIF orientation) is applied to the pixels on
|
|
27
|
+
load, because OpenCV's reader ignores it.
|
|
28
|
+
16-bit and float files are read at full precision; see Image Info's `Depth`.
|
|
29
|
+
|
|
30
|
+
The Loading bar shows while the file is read and decoded.
|
|
31
|
+
|
|
32
|
+
The node reports an error naming the file when it is missing or is not an
|
|
33
|
+
image, and when `Path` leads to a volume that is not defined on this machine.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
For Each File → Load Image → Image Info
|
|
37
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Save Image
|
|
2
|
+
|
|
3
|
+
`haybale-image:node:SaveImageNode` · kind: node
|
|
4
|
+
|
|
5
|
+
Writes an image to a file in a volume, in the format its extension names
|
|
6
|
+
|
|
7
|
+
## Ports
|
|
8
|
+
|
|
9
|
+
| id | direction | type | description |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `exec` | inlet | `haywire-core:type:EXEC` | Saves the image |
|
|
12
|
+
| `image` | inlet | `haybale-image:type:IMAGE` | Image to save |
|
|
13
|
+
| `path` | inlet | `haywire-core:type:FILE_PATH` | File to write; its extension picks the format |
|
|
14
|
+
| `if_exists` | config | `haywire-core:type:STRING` | When the file exists: overwrite, skip, or unique-suffix (adds a number) |
|
|
15
|
+
| `depth` | config | `haywire-core:type:STRING` | Bits per channel: source keeps the image's own, or 8, 16, 32 |
|
|
16
|
+
| `progress.main` | config | `haywire-core:type:PROGRESS` | |
|
|
17
|
+
| `next` | outlet | `haywire-core:type:EXEC` | Runs after saving |
|
|
18
|
+
| `saved` | outlet | `haywire-core:type:FILE_PATH` | The file written, or the one skipped |
|
|
19
|
+
|
|
20
|
+
## Settings
|
|
21
|
+
|
|
22
|
+
| name | bag | default | description |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| `compression` | png | `None` | Compression of saved PNG files, from 0 (fastest) to 9 (smallest); never loses detail |
|
|
25
|
+
| `quality` | jpeg | `None` | Quality of saved JPEG files, from 0 (smallest) to 100 (best) |
|
|
26
|
+
| `quality` | webp | `None` | Quality of saved WebP files, from 0 (smallest) to 100 (best) |
|
|
27
|
+
| `lossless` | webp | `None` | Save WebP files without losing detail; WebP quality is then ignored |
|
|
28
|
+
| `compression` | tiff | `None` | Compression of saved TIFF files; none of them loses detail |
|
|
29
|
+
| `quality` | avif | `None` | Quality of saved AVIF files, from 0 (smallest) to 100 (best) |
|
|
30
|
+
| `main` | progress | `PROGRESS(active=False, fraction=None, label='')` | |
|
|
31
|
+
|
|
32
|
+
## Notes
|
|
33
|
+
|
|
34
|
+
Writes `Image` to the file at `Path`, in the format its extension names.
|
|
35
|
+
|
|
36
|
+
Known extensions: `.png`, `.jpg`/`.jpeg`, `.webp`, `.tif`/`.tiff`, `.bmp`, `.gif`,
|
|
37
|
+
`.tga`, `.avif`. The file appears complete or not at all; a half-written file is
|
|
38
|
+
never left behind.
|
|
39
|
+
|
|
40
|
+
- `If Exists`: what to do when the file is already there. `overwrite` replaces
|
|
41
|
+
it, `skip` keeps it and writes nothing, `unique-suffix` saves beside it as
|
|
42
|
+
`name_1.png`, `name_2.png`, and so on.
|
|
43
|
+
- `Depth`: `source` keeps the image's own bit depth; `8`, `16` and `32` (float)
|
|
44
|
+
ask for that depth. A format that cannot store it gets the deepest it can
|
|
45
|
+
(JPEG is always 8-bit, PNG at most 16-bit). 16-bit and float saves drop the
|
|
46
|
+
image's ICC profile and EXIF block.
|
|
47
|
+
|
|
48
|
+
`Saved` is the file that now holds the image: the suffixed name for
|
|
49
|
+
`unique-suffix`, and the existing file for `skip`.
|
|
50
|
+
|
|
51
|
+
The quality and compression settings default to the library's *Image* settings;
|
|
52
|
+
change them here for this node only. Only the settings of the format `Path` names
|
|
53
|
+
are shown.
|
|
54
|
+
|
|
55
|
+
The Saving bar shows while the file is encoded and written.
|
|
56
|
+
|
|
57
|
+
The folder must already exist; add Create Folder before Save Image to make it.
|
|
58
|
+
An empty image, an unknown extension and a volume that is not defined on this
|
|
59
|
+
machine are reported as errors.
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
For Each File → Load Image → … → Save Image (Path = Output Path)
|
|
63
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# View Image
|
|
2
|
+
|
|
3
|
+
`haybale-image:node:ViewImageNode` · kind: node
|
|
4
|
+
|
|
5
|
+
Shows an image on the node, as a live stream or as a still picture
|
|
6
|
+
|
|
7
|
+
## Ports
|
|
8
|
+
|
|
9
|
+
| id | direction | type | description |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `exec` | inlet | `haywire-core:type:EXEC` | Shows the image |
|
|
12
|
+
| `image` | inlet | `haybale-image:type:IMAGE` | Image to show |
|
|
13
|
+
| `next` | outlet | `haywire-core:type:EXEC` | Runs after showing |
|
|
14
|
+
| `image_out` | outlet | `haybale-image:type:IMAGE` | The same image, passed on |
|
|
15
|
+
|
|
16
|
+
## Settings
|
|
17
|
+
|
|
18
|
+
| name | bag | default | description |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| `mode` | viewer | `'still'` | stream for images that change often (a camera); still for large, rarely changing ones |
|
|
21
|
+
| `enlarge` | viewer | `3.0` | How many times wider the view gets when a still image is clicked |
|
|
22
|
+
| `zoom` | viewer | `1.0` | How many times the enlarged view's width the image shows; 1 fits it |
|
|
23
|
+
|
|
24
|
+
## Notes
|
|
25
|
+
|
|
26
|
+
Shows `Image` on the node and passes it on to `Image Out`.
|
|
27
|
+
|
|
28
|
+
- `Mode`: `stream` for images that change many times a second, such as a camera;
|
|
29
|
+
`still` for large images that change rarely, such as a loaded file. A still
|
|
30
|
+
image keeps its transparency.
|
|
31
|
+
|
|
32
|
+
Click a still image to enlarge the view: it gets `Enlarge view` times wider and
|
|
33
|
+
takes the image's shape, and the image shows `Zoom image` times the view's width
|
|
34
|
+
(1 fits it). Drag inside it to move around the image; click again to go back.
|
|
35
|
+
|
|
36
|
+
The view appears once `Image` is connected.
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
Load Image → View Image (Mode = still)
|
|
40
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Image
|
|
2
|
+
|
|
3
|
+
`haybale-image:type:IMAGE` · kind: type
|
|
4
|
+
|
|
5
|
+
A picture of any channel layout. Pixels are kept as made and converted on demand.
|
|
6
|
+
|
|
7
|
+
## Details
|
|
8
|
+
|
|
9
|
+
- **wire_type**: `haywire-core:wire:DataWire`
|
|
10
|
+
- **default**: `{'value': {'width': 0, 'height': 0, 'layout': 'gray'}}`
|
|
11
|
+
- **color**: `#3f8fd2`
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
|
|
15
|
+
A picture of any channel layout; the value is never saved with the graph.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Gray image
|
|
2
|
+
|
|
3
|
+
`haybale-image:type:IMAGE_GRAY` · kind: type
|
|
4
|
+
|
|
5
|
+
A one-channel picture.
|
|
6
|
+
|
|
7
|
+
## Details
|
|
8
|
+
|
|
9
|
+
- **wire_type**: `haywire-core:wire:DataWire`
|
|
10
|
+
- **default**: `{'value': {'width': 0, 'height': 0, 'layout': 'gray'}}`
|
|
11
|
+
- **color**: `#3f8fd2`
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
|
|
15
|
+
A one-channel picture.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# RGB image
|
|
2
|
+
|
|
3
|
+
`haybale-image:type:IMAGE_RGB` · kind: type
|
|
4
|
+
|
|
5
|
+
A three-channel picture.
|
|
6
|
+
|
|
7
|
+
## Details
|
|
8
|
+
|
|
9
|
+
- **wire_type**: `haywire-core:wire:DataWire`
|
|
10
|
+
- **default**: `{'value': {'width': 0, 'height': 0, 'layout': 'rgb'}}`
|
|
11
|
+
- **color**: `#3f8fd2`
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
|
|
15
|
+
A three-channel picture.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# RGBA image
|
|
2
|
+
|
|
3
|
+
`haybale-image:type:IMAGE_RGBA` · kind: type
|
|
4
|
+
|
|
5
|
+
A picture with three colour channels and transparency.
|
|
6
|
+
|
|
7
|
+
## Details
|
|
8
|
+
|
|
9
|
+
- **wire_type**: `haywire-core:wire:DataWire`
|
|
10
|
+
- **default**: `{'value': {'width': 0, 'height': 0, 'layout': 'rgba'}}`
|
|
11
|
+
- **color**: `#3f8fd2`
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
|
|
15
|
+
A picture with three colour channels and transparency.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# ImageViewerWidget
|
|
2
|
+
|
|
3
|
+
`haybale-image:widget:ImageViewerWidget` · kind: widget
|
|
4
|
+
|
|
5
|
+
Shows an image, as a live stream or as a still picture
|
|
6
|
+
|
|
7
|
+
## Details
|
|
8
|
+
|
|
9
|
+
- **min_width**: `160`
|
|
10
|
+
|
|
11
|
+
## Notes
|
|
12
|
+
|
|
13
|
+
Shows the port's image.
|
|
14
|
+
|
|
15
|
+
Properties (`ImageViewerWidget.config(properties={...})`):
|
|
16
|
+
|
|
17
|
+
- `mode`: `stream` (default) sends every image as JPEG, for images that change
|
|
18
|
+
many times a second; `still` sends the image as PNG only when the view asks for
|
|
19
|
+
it, for large images that change rarely. Click a still image to enlarge the
|
|
20
|
+
view; click again to fit it.
|
|
21
|
+
- `quality`: JPEG quality in `stream` mode, 0 to 100 (default `80`).
|
|
22
|
+
- `enlarge`: in `still` mode, how many times wider the view gets on a click; its
|
|
23
|
+
height follows the image's shape (default `2`).
|
|
24
|
+
- `zoom`: how many times the enlarged view's width the image shows (default `2`;
|
|
25
|
+
`1` fits it); drag inside it to move around the image.
|
|
26
|
+
- `frame_width`: CSS width of a border around the view (default `0`, none).
|
|
27
|
+
- `frame_color`: CSS color of that border (default `var(--hw-border)`).
|
|
28
|
+
|
|
29
|
+
`stream` shows nothing until the view is open, and RGBA images lose their
|
|
30
|
+
transparency. `still` keeps transparency and shows 8 bits per channel.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
self.add(IMAGE.as_inlet(
|
|
34
|
+
"image",
|
|
35
|
+
widget=ImageViewerWidget.config(properties={"mode": "still"}),
|
|
36
|
+
))
|
|
37
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Library metadata. Canon for everything descriptive about this haybale;
|
|
2
|
+
# ships in the wheel beside __init__.py and is read from disk at runtime.
|
|
3
|
+
# Edited in the studio's library overview — no reinstall, no reload.
|
|
4
|
+
|
|
5
|
+
name = "haybale-image"
|
|
6
|
+
version = "0.3.2"
|
|
7
|
+
label = "Image"
|
|
8
|
+
description = "Image types, IO and viewers shared by vision and ML libraries"
|
|
9
|
+
tags = ["image"]
|
|
10
|
+
|
|
11
|
+
# Sibling haybales this library subscribes to, as MODULE names.
|
|
12
|
+
# Required for hot-reload scope tracking.
|
|
13
|
+
linked_libraries = []
|
haybale_image/helpers.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Conversions between ``ImageValue`` and PIL / OpenCV BGR arrays, and alpha premultiplication."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from PIL import Image
|
|
7
|
+
|
|
8
|
+
from haybale_image.value import NO_METADATA, ChannelOrder, ImageMetadata, ImageValue, Layout
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def from_pil(im: Image.Image, metadata: ImageMetadata = NO_METADATA) -> ImageValue:
|
|
12
|
+
"""Wrap a PIL image as an ``ImageValue``.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
im: Any PIL image. Gray modes stay gray, ``LA`` and transparent ``P`` become
|
|
16
|
+
RGBA, ``CMYK``/``YCbCr`` and unknown modes are converted to RGB(A).
|
|
17
|
+
metadata: ICC and EXIF blocks to carry with the image.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
The image; 16-bit and float sources record ``source_depth`` 16 / 32.
|
|
21
|
+
"""
|
|
22
|
+
mode = im.mode
|
|
23
|
+
if mode == "1":
|
|
24
|
+
return ImageValue.from_array(np.asarray(im.convert("L")), metadata=metadata)
|
|
25
|
+
if mode == "L":
|
|
26
|
+
return ImageValue.from_array(np.asarray(im), metadata=metadata)
|
|
27
|
+
if mode == "LA":
|
|
28
|
+
la = np.asarray(im)
|
|
29
|
+
rgba = np.concatenate([np.repeat(la[..., :1], 3, axis=2), la[..., 1:]], axis=2)
|
|
30
|
+
return ImageValue.from_array(rgba, metadata=metadata)
|
|
31
|
+
if mode == "P":
|
|
32
|
+
target = "RGBA" if "transparency" in im.info else "RGB"
|
|
33
|
+
return ImageValue.from_array(np.asarray(im.convert(target)), metadata=metadata)
|
|
34
|
+
if mode in ("RGB", "RGBA"):
|
|
35
|
+
return ImageValue.from_array(np.asarray(im), metadata=metadata)
|
|
36
|
+
if mode in ("CMYK", "YCbCr"):
|
|
37
|
+
return ImageValue.from_array(np.asarray(im.convert("RGB")), metadata=metadata)
|
|
38
|
+
if mode in ("I;16", "I;16L", "I;16B", "I;16N"):
|
|
39
|
+
# Native byte order explicitly: I;16B arrives as a big-endian view.
|
|
40
|
+
data = np.asarray(im).astype(np.uint16)
|
|
41
|
+
return ImageValue.from_array(data, source_depth=16, metadata=metadata)
|
|
42
|
+
if mode == "I":
|
|
43
|
+
data = np.clip(np.asarray(im), 0, 65535).astype(np.uint16)
|
|
44
|
+
return ImageValue.from_array(data, source_depth=16, metadata=metadata)
|
|
45
|
+
if mode == "F":
|
|
46
|
+
data = np.asarray(im).astype(np.float32)
|
|
47
|
+
return ImageValue.from_array(data, source_depth=32, metadata=metadata)
|
|
48
|
+
target = "RGBA" if "A" in im.getbands() else "RGB"
|
|
49
|
+
return ImageValue.from_array(np.asarray(im.convert(target)), metadata=metadata)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def to_pil(img: ImageValue, depth: int = 8) -> Image.Image:
|
|
53
|
+
"""Convert an ``ImageValue`` to a PIL image.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
img: The image.
|
|
57
|
+
depth: Bit depth for gray images: 8 (``L``), 16 (``I;16``) or 32 (``F``).
|
|
58
|
+
Ignored for RGB and RGBA, which are always 8-bit (Pillow has no
|
|
59
|
+
16-bit RGB; use OpenCV for that).
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ValueError: ``depth`` is not 8, 16 or 32.
|
|
63
|
+
"""
|
|
64
|
+
if depth not in (8, 16, 32):
|
|
65
|
+
raise ValueError(f"depth must be 8, 16 or 32, got {depth}")
|
|
66
|
+
if img.layout is Layout.GRAY:
|
|
67
|
+
dtype = {8: np.uint8, 16: np.uint16, 32: np.float32}[depth]
|
|
68
|
+
return Image.fromarray(np.ascontiguousarray(img.array(dtype)))
|
|
69
|
+
return Image.fromarray(np.ascontiguousarray(img.array(np.uint8)))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def from_cv_bgr(
|
|
73
|
+
a: np.ndarray, metadata: ImageMetadata = NO_METADATA, source_depth: int | None = None
|
|
74
|
+
) -> ImageValue:
|
|
75
|
+
"""Wrap an OpenCV ``H×W×3``/``H×W×4`` BGR(A) (or gray) array without copying."""
|
|
76
|
+
return ImageValue.from_array(a, order=ChannelOrder.BGR, source_depth=source_depth, metadata=metadata)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def to_cv_bgr(img: ImageValue, dtype: type[np.generic] = np.uint8) -> np.ndarray:
|
|
80
|
+
"""Return the pixels as a BGR(A) array of ``dtype`` (a view when storage allows)."""
|
|
81
|
+
return img.array(dtype, ChannelOrder.BGR)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def premultiply(img: ImageValue) -> np.ndarray:
|
|
85
|
+
"""Return float32 ``H×W×4`` RGB-order pixels with colour multiplied by alpha.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ValueError: ``img`` is not RGBA.
|
|
89
|
+
"""
|
|
90
|
+
if img.layout is not Layout.RGBA:
|
|
91
|
+
raise ValueError(f"premultiply needs an RGBA image, got {img.layout.name}")
|
|
92
|
+
out = np.array(img.array(np.float32), dtype=np.float32, copy=True)
|
|
93
|
+
out[..., :3] *= out[..., 3:4]
|
|
94
|
+
return out
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def unpremultiply(rgba: np.ndarray) -> np.ndarray:
|
|
98
|
+
"""Divide colour by alpha (float32 ``H×W×4``); alpha-0 pixels become 0.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
ValueError: ``rgba`` is not ``H×W×4``.
|
|
102
|
+
"""
|
|
103
|
+
if rgba.ndim != 3 or rgba.shape[2] != 4:
|
|
104
|
+
raise ValueError(f"unpremultiply needs an H×W×4 array, got shape {rgba.shape}")
|
|
105
|
+
out = np.array(rgba, dtype=np.float32, copy=True)
|
|
106
|
+
alpha = out[..., 3:4]
|
|
107
|
+
nz = alpha > 0
|
|
108
|
+
np.divide(out[..., :3], alpha, out=out[..., :3], where=nz)
|
|
109
|
+
out[..., :3] = np.where(nz, out[..., :3], 0.0)
|
|
110
|
+
return out
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Image file IO: formats by extension and the codecs that read and write them."""
|
|
2
|
+
|
|
3
|
+
from haybale_image.io.codecs import EncodeOptions, encode_image, read_image
|
|
4
|
+
from haybale_image.io.formats import (
|
|
5
|
+
DEPTHS,
|
|
6
|
+
FORMAT_BY_EXTENSION,
|
|
7
|
+
HOLDS_METADATA,
|
|
8
|
+
Format,
|
|
9
|
+
best_depth,
|
|
10
|
+
format_for,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"DEPTHS",
|
|
15
|
+
"FORMAT_BY_EXTENSION",
|
|
16
|
+
"HOLDS_METADATA",
|
|
17
|
+
"EncodeOptions",
|
|
18
|
+
"Format",
|
|
19
|
+
"best_depth",
|
|
20
|
+
"encode_image",
|
|
21
|
+
"format_for",
|
|
22
|
+
"read_image",
|
|
23
|
+
]
|