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.
Files changed (54) hide show
  1. haybale_image/OVERVIEW.md +30 -0
  2. haybale_image/QUICKREF.md +27 -0
  3. haybale_image/__init__.py +43 -0
  4. haybale_image/adapters/__init__.py +0 -0
  5. haybale_image/adapters/image_adapters.py +51 -0
  6. haybale_image/docs/haybale-image.adapter.ImageToGrayAdapter.md +15 -0
  7. haybale_image/docs/haybale-image.adapter.ImageToRgbAdapter.md +15 -0
  8. haybale_image/docs/haybale-image.adapter.ImageToRgbaAdapter.md +15 -0
  9. haybale_image/docs/haybale-image.node.ImageInfoNode.md +23 -0
  10. haybale_image/docs/haybale-image.node.LoadImageNode.md +37 -0
  11. haybale_image/docs/haybale-image.node.SaveImageNode.md +63 -0
  12. haybale_image/docs/haybale-image.node.ViewImageNode.md +40 -0
  13. haybale_image/docs/haybale-image.panel.ImageSettingsPanel.md +14 -0
  14. haybale_image/docs/haybale-image.setting.ImageSettings.md +9 -0
  15. haybale_image/docs/haybale-image.type.IMAGE.md +15 -0
  16. haybale_image/docs/haybale-image.type.IMAGE_GRAY.md +15 -0
  17. haybale_image/docs/haybale-image.type.IMAGE_RGB.md +15 -0
  18. haybale_image/docs/haybale-image.type.IMAGE_RGBA.md +15 -0
  19. haybale_image/docs/haybale-image.widget.ImageViewerWidget.md +37 -0
  20. haybale_image/haybale.toml +13 -0
  21. haybale_image/helpers.py +110 -0
  22. haybale_image/io/__init__.py +23 -0
  23. haybale_image/io/_avif.py +49 -0
  24. haybale_image/io/codecs.py +245 -0
  25. haybale_image/io/formats.py +74 -0
  26. haybale_image/io/placement.py +92 -0
  27. haybale_image/nodes/__init__.py +0 -0
  28. haybale_image/nodes/image_info.py +44 -0
  29. haybale_image/nodes/load_image.py +58 -0
  30. haybale_image/nodes/save_image.py +228 -0
  31. haybale_image/nodes/view_image.py +101 -0
  32. haybale_image/panels/__init__.py +1 -0
  33. haybale_image/panels/image_settings_panel.py +31 -0
  34. haybale_image/py.typed +0 -0
  35. haybale_image/settings/__init__.py +0 -0
  36. haybale_image/settings/image_settings.py +51 -0
  37. haybale_image/types/__init__.py +0 -0
  38. haybale_image/types/image.py +109 -0
  39. haybale_image/value/__init__.py +8 -0
  40. haybale_image/value/_numpy_ops.py +81 -0
  41. haybale_image/value/_torch_ops.py +190 -0
  42. haybale_image/value/enums.py +18 -0
  43. haybale_image/value/image_value.py +304 -0
  44. haybale_image/value/layout.py +41 -0
  45. haybale_image/value/metadata.py +21 -0
  46. haybale_image/viewer/__init__.py +5 -0
  47. haybale_image/viewer/backend.py +299 -0
  48. haybale_image/viewer/image_viewer.js +337 -0
  49. haybale_image/widgets/__init__.py +0 -0
  50. haybale_image/widgets/image_viewer_widget.py +86 -0
  51. haybale_image-0.3.2.dist-info/METADATA +13 -0
  52. haybale_image-0.3.2.dist-info/RECORD +54 -0
  53. haybale_image-0.3.2.dist-info/WHEEL +4 -0
  54. 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,14 @@
1
+ # Image
2
+
3
+ `haybale-image:panel:ImageSettingsPanel` · kind: panel
4
+
5
+ Defaults for saving images.
6
+
7
+ ## Details
8
+
9
+ - **surface**: `barns`
10
+ - **order**: `10`
11
+
12
+ ## Notes
13
+
14
+ Defaults for saving images; each Save Image node can override them.
@@ -0,0 +1,9 @@
1
+ # Image
2
+
3
+ `haybale-image:setting:ImageSettings` · kind: setting
4
+
5
+ Defaults for saving images
6
+
7
+ ## Notes
8
+
9
+ Defaults for saving images; each Save Image node can override them.
@@ -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 = []
@@ -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
+ ]