ialdev-io 0.1.0__tar.gz

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.
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: ialdev-io
3
+ Version: 0.1.0
4
+ Summary: iad.io — image and structured file I/O (imread, formats, CIIF, inu helpers, etc.)
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: ialdev-core
8
+ Requires-Dist: numpy>=1.20.0
9
+ Requires-Dist: pillow>=8.0.0
10
+ Requires-Dist: scikit-image>=0.19.0
11
+ Requires-Dist: tifffile
12
+ Requires-Dist: zfpy
13
+ Requires-Dist: pyyaml>=5.4.0
14
+ Requires-Dist: pytest>=7.0.0 ; extra == "dev"
15
+ Provides-Extra: dev
16
+
17
+ # ialdev-io (`fio/`)
18
+
19
+ **PyPI:** `ialdev-io` **Imports:** `iad.io` (and `iad.io.inu` for Inuitive/NU4 helpers)
20
+
21
+ The repository directory is named `fio` (not `io`) to avoid clashing with the Python standard library `io` when running tests with `importlib` import mode.
22
+
23
+ Former `algutils.io`: generic image/file I/O.
24
+
@@ -0,0 +1,7 @@
1
+ # ialdev-io (`fio/`)
2
+
3
+ **PyPI:** `ialdev-io` **Imports:** `iad.io` (and `iad.io.inu` for Inuitive/NU4 helpers)
4
+
5
+ The repository directory is named `fio` (not `io`) to avoid clashing with the Python standard library `io` when running tests with `importlib` import mode.
6
+
7
+ Former `algutils.io`: generic image/file I/O.
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["flit_core>=3.11,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
5
+ [project]
6
+ name = "ialdev-io"
7
+ version = "0.1.0"
8
+ description = "iad.io — image and structured file I/O (imread, formats, CIIF, inu helpers, etc.)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+ dependencies = [
13
+ "ialdev-core",
14
+ "numpy>=1.20.0",
15
+ "pillow>=8.0.0",
16
+ "scikit-image>=0.19.0",
17
+ "tifffile",
18
+ "zfpy",
19
+ "pyyaml>=5.4.0",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ dev = ["pytest>=7.0.0"]
24
+
25
+ [tool.flit.module]
26
+ name = "iad.io"
27
+
28
+ [tool.pytest.ini_options]
29
+ testpaths = ["tests"]
30
+ python_files = ["test_*.py"]
31
+ pythonpath = ["src"]
@@ -0,0 +1,17 @@
1
+ from .imread import imread
2
+ from .imwrite import imsave
3
+
4
+
5
+ def read_meta(path):
6
+ """Read file at path and return dict (e.g. YAML/JSON). Used by extract_attr when meta is a path."""
7
+ path = __import__('pathlib').Path(path)
8
+ suffix = path.suffix.lower()
9
+ if suffix in ('.yaml', '.yml'):
10
+ import yaml
11
+ return yaml.safe_load(path.read_text(encoding='utf-8')) or {}
12
+ if suffix in ('.json', '.js', '.jso', '.jsn'):
13
+ import json
14
+ return json.loads(path.read_text(encoding='utf-8'))
15
+ # fallback: try yaml
16
+ import yaml
17
+ return yaml.safe_load(path.read_text(encoding='utf-8')) or {}
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ from itertools import chain
4
+ from typing import Callable, Any
5
+
6
+ from .format import FileFormat, PathT, read_outputs, Content as CT, sfx_of
7
+
8
+
9
+ class MiddleburyCalib(FileFormat, patterns="calib.txt", content=CT.CONFIG):
10
+ @classmethod
11
+ def read(cls, filename: PathT, *, content=CT.CONFIG, **kws):
12
+ from .special import middlebury_calib
13
+ assert not kws and cls.valid_content(content)
14
+ return middlebury_calib(filename)
15
+
16
+
17
+ _cfg_sfx = [['.yaml', '.yml'], ['.json', '.jso', '.js', '.jsn'], ['.toml', '.tml', '.ini']]
18
+
19
+
20
+ class ConfigFormat(FileFormat, desc="Nested Textual Configuration",
21
+ content=CT.META | CT.CONFIG, patterns=[*chain(*_cfg_sfx)]):
22
+ from iad.core.param import TBox
23
+ _aliases = {sfx: grp[0].strip('.') for grp in _cfg_sfx for sfx in grp}
24
+
25
+ @classmethod
26
+ def read(cls, filename: PathT, *, content=CT.UNDEF, **kws):
27
+ assert cls.valid_content(content)
28
+ name = f'from_{cls._aliases[sfx_of(filename)]}'
29
+ return getattr(cls.TBox, name)(filename=filename, **kws)
30
+
31
+ @classmethod
32
+ def write(cls, filename: PathT, data: dict, **kws) -> Callable[[PathT], Any]:
33
+ """Acquire read function by the format"""
34
+ name = f'to_{cls._aliases[sfx_of(filename)]}'
35
+ return getattr(cls.TBox(data), name)(filename=filename, **kws)
36
+
37
+ @classmethod
38
+ def supports_write(cls, *, data=None, meta=None):
39
+ return meta is None and (
40
+ data is None or issubclass(data, dict))
41
+
42
+
43
+ class TifFormat(FileFormat, desc="Tiff with metadata in ImageDescription Tag",
44
+ patterns=['.tiff', '.tif'],
45
+ content=CT.META | CT.IMAGE):
46
+ _meta_tag = "ImageDescription"
47
+
48
+ @classmethod
49
+ @read_outputs
50
+ def read(cls, filename: PathT, *, content=CT.DATA, **kws):
51
+ """No need for file_form"""
52
+ from tifffile import imread, tiffcomment
53
+ out = []
54
+ if content & CT.DATA:
55
+ out.append(imread(filename))
56
+ if content & CT.META:
57
+ import yaml
58
+ comment = tiffcomment(filename, tagcode=cls._meta_tag)
59
+ meta = yaml.load(comment, Loader=yaml.SafeLoader)
60
+ out.append(meta if isinstance(meta, dict) else None)
61
+ return tuple(out)
62
+
63
+
64
+ class ImageFormat(FileFormat, desc="General image format",
65
+ patterns=['.png', '.bmp', '.jpg'],
66
+ content=CT.IMAGE):
67
+ @classmethod
68
+ def read(cls, filename: PathT):
69
+ from .imread import imread
70
+ return imread(filename)
71
+
72
+ @classmethod
73
+ def write(cls, filename: PathT, data, *, meta=None, **kws):
74
+ from .imread import imsave
75
+ assert meta is None
76
+ imsave(filename, data, **kws)
77
+
78
+
79
+ class InuStereoFormat(FileFormat, desc="Inuitive Stereo Images with Cameras metadata",
80
+ patterns=[
81
+ r"StereoImage_\d+\.tif",
82
+ r"Output_Video_(Left|Right)_\d+\.(bmp|png|tif|jpg)",
83
+ r"Video_\d+\.(bmp|png|tif|jpg)"],
84
+ content=CT.IMAGE | CT.META):
85
+ """Inuitive tif based format for 2 stereo images with custom tags."""
86
+
87
+ @classmethod
88
+ def read(cls, filename: PathT, content=CT.DATA, *kws):
89
+ """No need for file_form"""
90
+ assert not kws
91
+ out = []
92
+ if content & CT.META:
93
+ from iad.io.inu.utils.imread import imread_stereo
94
+ out.append(imread_stereo(str(filename), cam_info=False))
95
+ if content & CT.DATA:
96
+ from iad.io.inu.utils.imread import tiff_inu_cam
97
+ out.append(tiff_inu_cam(str(filename)))
98
+ return tuple(out) if len(out) > 1 else out if len(out) == 1 else 0
@@ -0,0 +1,98 @@
1
+ """ Bit streams operations"""
2
+
3
+
4
+ def save_bit_streams(full_file_name, streams, order=None):
5
+ """ Save bit stream
6
+
7
+ :param full_file_name: the full path of the .tiff file to be saved
8
+ :param streams: dict of dicts of streams. See my_streams in '__main__' for example
9
+ :param order: list of streams' names to record in specific order,
10
+ or list of fio {name:offset} to record in pre-defined offsets
11
+ :return: ---
12
+ """
13
+ import tifffile, json
14
+ from iad.core.binary import join_bits
15
+ streams = streams.copy()
16
+
17
+ def extract_bits_data(names):
18
+ return zip(*[(streams[name]['bits'], streams[name]['data']) for name in names])
19
+
20
+ bits, data_streams = extract_bits_data(streams)
21
+ check_clip = False
22
+ ord_streams = None
23
+
24
+ if order is not None:
25
+ if type(order) == dict:
26
+ offsets = [order[name] for name in streams]
27
+ for name in streams: # ADD offsets TO streams to be used in unpacking
28
+ streams[name]['offset'] = order[name]
29
+ bits = list(zip(bits, offsets))
30
+ else:
31
+ assert hasattr(order, '__iter__')
32
+ bits, data_streams = extract_bits_data(order)
33
+ ord_streams = {name: streams[name] for name in order}
34
+
35
+ tiff_im = join_bits(bits, data_streams, check_clip)
36
+
37
+ for name in streams.keys():
38
+ del streams[name]['data']
39
+ metadata = json.dumps(streams if ord_streams is None else ord_streams) # REMOVE 'data' FROM streams AND PACK INTO JSON
40
+
41
+ tifffile.imsave(full_file_name, tiff_im, description=metadata)
42
+
43
+
44
+ def load_bit_streams(full_file_name):
45
+ """ Load bit streams from tiff file with YML metadata
46
+
47
+ :param full_file_name: the full path of the .tiff file to be loaded.
48
+ the file is assumed to contain YML metadata recorded by save_bit_streams()
49
+ :return: split data streams
50
+ """
51
+ from iad.core.binary import split_bits
52
+ from imread import imread
53
+ tiff_im, streams = imread(full_file_name, get_meta=True)
54
+
55
+ bits = [streams[name]['bits'] for name in streams.keys()]
56
+
57
+ if 'offset' in list(streams.values())[0].keys(): # list(streams.values())[0]['offset'] != None:
58
+ offsets = [streams[name]['offset'] for name in streams.keys()]
59
+ bits = list(zip(bits, offsets))
60
+
61
+ data = split_bits(tiff_im, bits)
62
+
63
+ for i, name in enumerate(streams):
64
+ streams[name]['data'] = data[i]
65
+
66
+ return streams
67
+
68
+
69
+ if __name__ == '__main__':
70
+
71
+ sz_x = 10 #640
72
+ sz_y = 10 #480
73
+ import numpy as np
74
+ conf_inf_bits = 4
75
+ conf_inf = np.random.randint(2 ** conf_inf_bits, size=(sz_x, sz_y)).astype('uint8')
76
+
77
+ disp_inf_bits = 1
78
+ disp_inf = np.ones((sz_x, sz_y), dtype='uint8')
79
+
80
+ my_streams = {
81
+ 'conf_inf': {
82
+ 'data': conf_inf,
83
+ 'bits': conf_inf_bits,
84
+ 'info': 'information score estimated from confidence'
85
+ },
86
+ 'disp_inf': {
87
+ 'data': disp_inf,
88
+ 'bits': disp_inf_bits,
89
+ 'info': 'important estimated from disparity'
90
+ }
91
+ }
92
+
93
+ # Have to support order = None, order as list, and order as dictionary
94
+ order_my = ['disp_inf', 'conf_inf']
95
+ order_dict_my = {'disp_inf': 1, 'conf_inf': 3} # name: offset.
96
+
97
+ save_bit_streams('some_file.tif', my_streams, None) #order_my)
98
+ res_streams = load_bit_streams('some_file.tif')