ojph 0.0.1__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.
ojph-0.0.1/LICENSE.txt ADDED
@@ -0,0 +1,26 @@
1
+ Copyright 2024, Ramona Optics Inc.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this
7
+ list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ 3. Neither the name of the copyright holder nor the names of its contributors
14
+ may be used to endorse or promote products derived from this software without
15
+ specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND
18
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
ojph-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.1
2
+ Name: ojph
3
+ Version: 0.0.1
4
+ Summary: OpenJPH Bindings for Python and Numpy
5
+ Home-page: https://github.com/ramonaoptics/ojph
6
+ Author: Mark Harfouche
7
+ Author-email: mark@ramonaoptics.com
8
+ License: BSD-3-Clause
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Natural Language :: English
12
+ Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: Implementation :: CPython
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ License-File: LICENSE.txt
23
+ Requires-Dist: numpy>=1.24.0
24
+
25
+ OpenJPH bindings
ojph-0.0.1/README.md ADDED
@@ -0,0 +1 @@
1
+ OpenJPH bindings
@@ -0,0 +1,6 @@
1
+ from ._version import __version__
2
+
3
+ from ._imwrite import imwrite
4
+ from ._imread import imread
5
+
6
+ __all__ = ["imwrite", "imread"]
@@ -0,0 +1,102 @@
1
+ import os
2
+ import numpy as np
3
+ import ctypes
4
+ from .ojph_bindings import J2CInfile, Codestream
5
+ from warnings import warn
6
+
7
+ # Copy the imageio.v3.imread signature
8
+ def imread(uri, *, index=None, plugin=None, extension=None, format_hint=None, **kwargs):
9
+ if index is not None:
10
+ warn(f"index {index} is ignored", stacklevel=2)
11
+ if plugin is not None:
12
+ warn(f"plugin {plugin} is ignored", stacklevel=2)
13
+ if extension is not None:
14
+ warn(f"extension {extension} is ignored", stacklevel=2)
15
+ if format_hint is not None:
16
+ warn(f"format_hint {format_hint} is ignored", stacklevel=2)
17
+
18
+ return OJPHImageFile(uri).read_image()
19
+
20
+
21
+ class OJPHImageFile:
22
+ def __init__(self, filename):
23
+ self._codestream = None
24
+ self._ojph_file = None
25
+ self._filename = filename
26
+
27
+ self._ojph_file = J2CInfile()
28
+ self._ojph_file.open(str(filename))
29
+ self._codestream = Codestream()
30
+ self._codestream.read_headers(self._ojph_file)
31
+
32
+
33
+ siz = self._codestream.access_siz()
34
+ extents = siz.get_image_extent()
35
+ self._shape = extents.y, extents.x
36
+ self._is_planar = self._codestream.is_planar()
37
+
38
+ bit_depth = siz.get_bit_depth(0)
39
+ is_signed = siz.is_signed(0)
40
+ if bit_depth == 8 and not is_signed:
41
+ self._dtype = np.uint8
42
+ elif bit_depth == 8 and is_signed:
43
+ self._dtype = np.int8
44
+ elif bit_depth == 16 and not is_signed:
45
+ self._dtype = np.uint16
46
+ elif bit_depth == 16 and is_signed:
47
+ self._dtype = np.int16
48
+ elif bit_depth == 32 and not is_signed:
49
+ self._dtype = np.uint32
50
+ elif bit_depth == 32 and is_signed:
51
+ self._dtype = np.int32
52
+ else:
53
+ raise ValueError("Unsupported bit depth")
54
+
55
+ def _open_file(self):
56
+ self._ojph_file = J2CInfile()
57
+ self._ojph_file.open(self._filename)
58
+ self._codestream = Codestream()
59
+ self._codestream.read_headers(self._ojph_file)
60
+
61
+ def read_image(self, *, level=0):
62
+ if self._codestream is None:
63
+ self._open_file()
64
+
65
+ self._codestream.restrict_input_resolution(level, level)
66
+ siz = self._codestream.access_siz()
67
+
68
+ height = siz.get_recon_height(0)
69
+ width = siz.get_recon_width(0)
70
+ self._codestream.create()
71
+
72
+ image = np.zeros(
73
+ (height, width),
74
+ dtype=self._dtype
75
+ )
76
+
77
+ for h in range(height):
78
+ line = self._codestream.pull(0)
79
+ # Convert the address to a ctypes pointer to int32
80
+ i32_ptr = ctypes.cast(line.i32_address, ctypes.POINTER(ctypes.c_uint32))
81
+
82
+ # Calculate the total number of bytes (size of the array in elements * size of int32)
83
+ line_array = np.ctypeslib.as_array(
84
+ ctypes.cast(i32_ptr, ctypes.POINTER(ctypes.c_uint32)),
85
+ shape=(line.size,)
86
+ )
87
+ image[h] = line_array
88
+
89
+ self._close_codestream_and_file()
90
+ return image
91
+
92
+ def _close_codestream_and_file(self):
93
+ if self._codestream is not None:
94
+ self._codestream.close()
95
+ # The codestream will close the infile automatically
96
+ elif self._ojph_file is not None:
97
+ self._ojph_file.close()
98
+ self._codestream = None
99
+ self._ojph_file = None
100
+
101
+ def __del__(self):
102
+ self._close_codestream_and_file()
@@ -0,0 +1,82 @@
1
+ import numpy as np
2
+ import ctypes
3
+
4
+ from .ojph_bindings import Codestream, J2COutfile, Point
5
+
6
+
7
+ def imwrite(filename, image):
8
+ # In the future we might be able to pass in a channel_order parameter
9
+ if image.ndim == 2:
10
+ channel_order = 'HW'
11
+ else:
12
+ channel_order = 'HWC'
13
+
14
+ channel_order = channel_order.upper()
15
+
16
+ if len(channel_order) != image.ndim:
17
+ raise ValueError(
18
+ f"The channel order ({channel_order}) must be consistent "
19
+ f"with the image dimensions ({image.ndim})."
20
+ )
21
+
22
+ ojph_file = J2COutfile()
23
+ ojph_file.open(str(filename))
24
+ codestream = Codestream()
25
+
26
+ siz = codestream.access_siz()
27
+ width = image.shape[channel_order.index('W')]
28
+ height = image.shape[channel_order.index('H')]
29
+
30
+ # What does planar mean? it doesn't seem to mean components...
31
+ # is_planar = 'C' in channel_order
32
+ siz.set_image_extent(Point(width, height))
33
+ if 'C' in channel_order:
34
+ num_components = channel_order.index('C')
35
+ else:
36
+ num_components = 1
37
+
38
+ bit_depth = image.dtype.itemsize * 8
39
+ # Is there a better way to detect signed dtypes???
40
+ is_signed = image.dtype.kind != 'u'
41
+ siz.set_num_components(num_components);
42
+ for i in range(num_components):
43
+ # is it necessary to do this in a loop?
44
+ siz.set_component(
45
+ i,
46
+ Point(1, 1), # component downsampling
47
+ bit_depth,
48
+ is_signed,
49
+ )
50
+ cod = codestream.access_cod()
51
+ # cod.set_progression_oder
52
+ # code.set_color_Transform
53
+ cod.set_reversible(True)
54
+ # codestream.set_profile()
55
+
56
+ # set tile_size
57
+ # set tile offset
58
+ # planar true is likely for things like
59
+ # YUV420 where the Y, U, and V planes are stored
60
+ # sparately
61
+ codestream.set_planar(False)
62
+
63
+ codestream.write_headers(ojph_file, None, 0)
64
+ c = 0
65
+ line = codestream.exchange(None, c)
66
+ for i in range(height):
67
+ for c in range(num_components):
68
+ i32_ptr = ctypes.cast(line.i32_address, ctypes.POINTER(ctypes.c_uint32))
69
+ # Calculate the total number of bytes (size of the array in elements * size of int32)
70
+ line_array = np.ctypeslib.as_array(
71
+ ctypes.cast(i32_ptr, ctypes.POINTER(ctypes.c_uint32)),
72
+ shape=(line.size,)
73
+ )
74
+ if image.ndim == 2:
75
+ line_array[...] = image[i, :]
76
+ else:
77
+ line_array[...] = image[i, :, c]
78
+ c_before = c
79
+ line = codestream.exchange(line, c)
80
+
81
+ codestream.flush()
82
+ codestream.close()
@@ -0,0 +1,2 @@
1
+ # This file has been created by setup.py.
2
+ version = '0.0.1'
@@ -0,0 +1,194 @@
1
+ # -*- coding: utf-8 -*-
2
+ # This file is part of 'miniver': https://github.com/jbweston/miniver
3
+ #
4
+ from collections import namedtuple
5
+ import os
6
+ import subprocess
7
+
8
+ Version = namedtuple("Version", ("release", "dev", "labels"))
9
+
10
+ # No public API
11
+ __all__ = []
12
+
13
+ package_root = os.path.dirname(os.path.realpath(__file__))
14
+ package_name = os.path.basename(package_root)
15
+
16
+ STATIC_VERSION_FILE = "_static_version.py"
17
+
18
+
19
+ def get_version(version_file=STATIC_VERSION_FILE):
20
+ version_info = get_static_version_info(version_file)
21
+ version = version_info["version"]
22
+ if version == "__use_git__":
23
+ version = get_version_from_git()
24
+ if not version:
25
+ version = get_version_from_git_archive(version_info)
26
+ if not version:
27
+ version = Version("unknown", None, None)
28
+ return pep440_format(version)
29
+ else:
30
+ return version
31
+
32
+
33
+ def get_static_version_info(version_file=STATIC_VERSION_FILE):
34
+ version_info = {}
35
+ with open(os.path.join(package_root, version_file), "rb") as f:
36
+ exec(f.read(), {}, version_info)
37
+ return version_info
38
+
39
+
40
+ def version_is_from_git(version_file=STATIC_VERSION_FILE):
41
+ return get_static_version_info(version_file)["version"] == "__use_git__"
42
+
43
+
44
+ def pep440_format(version_info):
45
+ release, dev, labels = version_info
46
+
47
+ version_parts = [release]
48
+ if dev:
49
+ if release.endswith("-dev") or release.endswith(".dev"):
50
+ version_parts.append(dev)
51
+ else: # prefer PEP440 over strict adhesion to semver
52
+ version_parts.append(".post{}".format(dev))
53
+
54
+ if labels:
55
+ version_parts.append("+")
56
+ version_parts.append(".".join(labels))
57
+
58
+ return "".join(version_parts)
59
+
60
+
61
+ def get_version_from_git():
62
+ # git describe --first-parent does not take into account tags from branches
63
+ # that were merged-in. The '--long' flag gets us the 'dev' version and
64
+ # git hash, '--always' returns the git hash even if there are no tags.
65
+ for opts in [["--first-parent"], []]:
66
+ try:
67
+ p = subprocess.Popen(
68
+ ["git", "describe", "--tags", "--long", "--always"] + opts,
69
+ cwd=package_root,
70
+ stdout=subprocess.PIPE,
71
+ stderr=subprocess.PIPE,
72
+ )
73
+ except OSError:
74
+ return
75
+ if p.wait() == 0:
76
+ break
77
+ else:
78
+ return
79
+
80
+ if os.environ.get("OJPH_GIT_DESCRIBE", None):
81
+ git_describe = os.environ["OJPH_GIT_DESCRIBE"]
82
+ else:
83
+ git_describe = p.communicate()[0].decode()
84
+
85
+ description = (
86
+ git_describe
87
+ .strip("v") # Tags can have a leading 'v', but the version should not
88
+ .rstrip("\n")
89
+ .rsplit("-", 2) # Split the latest tag, commits since tag, and hash
90
+ )
91
+
92
+ try:
93
+ release, dev, git = description
94
+ except ValueError: # No tags, only the git hash
95
+ # prepend 'g' to match with format returned by 'git describe'
96
+ git = "g{}".format(*description)
97
+ release = "unknown"
98
+ dev = None
99
+
100
+ labels = []
101
+ if dev == "0":
102
+ dev = None
103
+ else:
104
+ labels.append(git)
105
+
106
+ try:
107
+ p = subprocess.Popen(["git", "diff", "--quiet"], cwd=package_root)
108
+ except OSError:
109
+ labels.append("confused") # This should never happen.
110
+ else:
111
+ if p.wait() == 1:
112
+ labels.append("dirty")
113
+
114
+ return Version(release, dev, labels)
115
+
116
+
117
+ # TODO: change this logic when there is a git pretty-format
118
+ # that gives the same output as 'git describe'.
119
+ # Currently we can only tell the tag the current commit is
120
+ # pointing to, or its hash (with no version info)
121
+ # if it is not tagged.
122
+ def get_version_from_git_archive(version_info):
123
+ try:
124
+ refnames = version_info["refnames"]
125
+ git_hash = version_info["git_hash"]
126
+ except KeyError:
127
+ # These fields are not present if we are running from an sdist.
128
+ # Execution should never reach here, though
129
+ return None
130
+
131
+ if git_hash.startswith("$Format") or refnames.startswith("$Format"):
132
+ # variables not expanded during 'git archive'
133
+ return None
134
+
135
+ VTAG = "tag: v"
136
+ refs = set(r.strip() for r in refnames.split(","))
137
+ version_tags = set(r[len(VTAG) :] for r in refs if r.startswith(VTAG))
138
+ if version_tags:
139
+ release, *_ = sorted(version_tags) # prefer e.g. "2.0" over "2.0rc1"
140
+ return Version(release, dev=None, labels=None)
141
+ else:
142
+ return Version("unknown", dev=None, labels=["g{}".format(git_hash)])
143
+
144
+
145
+ __version__ = get_version()
146
+
147
+
148
+ # The following section defines a 'get_cmdclass' function
149
+ # that can be used from setup.py. The '__version__' module
150
+ # global is used (but not modified).
151
+
152
+
153
+ def _write_version(fname):
154
+ # This could be a hard link, so try to delete it first. Is there any way
155
+ # to do this atomically together with opening?
156
+ try:
157
+ os.remove(fname)
158
+ except OSError:
159
+ pass
160
+ with open(fname, "w") as f:
161
+ f.write(
162
+ "# This file has been created by setup.py.\n"
163
+ "version = '{}'\n".format(__version__)
164
+ )
165
+
166
+
167
+ def get_cmdclass(pkg_source_path):
168
+ from setuptools.command.build_py import build_py as build_py_orig
169
+ from setuptools.command.sdist import sdist as sdist_orig
170
+
171
+ class _build_py(build_py_orig):
172
+ def run(self):
173
+ super().run()
174
+
175
+ src_marker = "".join(["src", os.path.sep])
176
+
177
+ if pkg_source_path.startswith(src_marker):
178
+ path = pkg_source_path[len(src_marker):]
179
+ else:
180
+ path = pkg_source_path
181
+ _write_version(
182
+ os.path.join(
183
+ self.build_lib, path, STATIC_VERSION_FILE
184
+ )
185
+ )
186
+
187
+ class _sdist(sdist_orig):
188
+ def make_release_tree(self, base_dir, files):
189
+ super().make_release_tree(base_dir, files)
190
+ _write_version(
191
+ os.path.join(base_dir, pkg_source_path, STATIC_VERSION_FILE)
192
+ )
193
+
194
+ return dict(sdist=_sdist, build_py=_build_py)
@@ -0,0 +1,183 @@
1
+ #include <pybind11/pybind11.h>
2
+ #include <pybind11/stl.h>
3
+
4
+
5
+ #include <openjph/ojph_file.h>
6
+ #include <openjph/ojph_codestream.h>
7
+ #include <openjph/ojph_mem.h>
8
+ #include <openjph/ojph_params.h>
9
+
10
+ namespace py = pybind11;
11
+ using namespace ojph;
12
+
13
+ PYBIND11_MODULE(ojph_bindings, m) {
14
+ py::class_<infile_base>(m, "InfileBase")
15
+ .def("read", &infile_base::read)
16
+ .def("seek", &infile_base::seek)
17
+ .def("tell", &infile_base::tell)
18
+ .def("eof", &infile_base::eof)
19
+ .def("close", &infile_base::close);
20
+
21
+ py::class_<j2c_infile, infile_base>(m, "J2CInfile")
22
+ .def(py::init<>())
23
+ .def("open", &j2c_infile::open)
24
+ .def("read", &j2c_infile::read)
25
+ .def("seek", [](infile_base& self, si64 offset, int origin) {
26
+ return self.seek(offset, static_cast<enum infile_base::seek>(origin));
27
+ })
28
+ .def("tell", &j2c_infile::tell)
29
+ .def("eof", &j2c_infile::eof)
30
+ .def("close", &j2c_infile::close);
31
+
32
+ py::class_<mem_infile, infile_base>(m, "MemInfile")
33
+ .def(py::init<>())
34
+ .def("open", &mem_infile::open)
35
+ .def("read", &mem_infile::read)
36
+ .def("seek", [](mem_infile& self, si64 offset, int origin) {
37
+ return self.seek(offset, static_cast<enum infile_base::seek>(origin));
38
+ })
39
+ .def("tell", &mem_infile::tell)
40
+ .def("eof", &mem_infile::eof)
41
+ .def("close", &mem_infile::close);
42
+
43
+
44
+ py::class_<outfile_base>(m, "outfileBase")
45
+ .def("write", &outfile_base::write)
46
+ .def("seek", &outfile_base::seek)
47
+ .def("tell", &outfile_base::tell)
48
+ .def("close", &outfile_base::close);
49
+
50
+ py::class_<j2c_outfile, outfile_base>(m, "J2COutfile")
51
+ .def(py::init<>())
52
+ .def("open", &j2c_outfile::open)
53
+ .def("write", &j2c_outfile::write)
54
+ .def("tell", &j2c_outfile::tell)
55
+ .def("close", &j2c_outfile::close);
56
+
57
+ // Bindings for codestream class
58
+ py::class_<codestream>(m, "Codestream")
59
+ .def(py::init<>())
60
+ .def("set_planar", &codestream::set_planar)
61
+ .def("set_profile", &codestream::set_profile)
62
+ .def("set_tilepart_divisions", &codestream::set_tilepart_divisions)
63
+ .def("is_tilepart_division_at_resolutions", &codestream::is_tilepart_division_at_resolutions)
64
+ .def("is_tilepart_division_at_components", &codestream::is_tilepart_division_at_components)
65
+ .def("request_tlm_marker", &codestream::request_tlm_marker)
66
+ .def("is_tlm_requested", &codestream::is_tlm_requested)
67
+ .def("write_headers",
68
+ [](codestream &self, outfile_base *file, py::object comments, ui32 num_comments) {
69
+ // Check if the comments argument is None and convert it to nullptr if so
70
+ const comment_exchange* comments_ptr = comments.is_none() ? nullptr : comments.cast<const comment_exchange*>();
71
+ self.write_headers(file, comments_ptr, num_comments);
72
+ },
73
+ py::arg("file"), py::arg("comments") = py::none(), py::arg("num_comments") = 0)
74
+ .def("exchange",
75
+ [](codestream &self, py::object line_buf_obj, ui32 &next_component) -> line_buf* {
76
+ line_buf* buf = nullptr;
77
+ if (!line_buf_obj.is_none()) {
78
+ buf = line_buf_obj.cast<line_buf*>();
79
+ }
80
+ return self.exchange(buf, next_component);
81
+ },
82
+ py::arg("line_buf_obj") = py::none(), py::arg("next_component") = 0)
83
+ .def("flush", &codestream::flush)
84
+ .def("enable_resilience", &codestream::enable_resilience)
85
+ .def("read_headers", &codestream::read_headers)
86
+ .def("restrict_input_resolution", &codestream::restrict_input_resolution)
87
+ .def("create", &codestream::create)
88
+ .def("pull", &codestream::pull)
89
+ .def("close", &codestream::close)
90
+ .def("access_siz", &codestream::access_siz)
91
+ .def("access_cod", &codestream::access_cod)
92
+ .def("access_qcd", &codestream::access_qcd)
93
+ .def("access_nlt", &codestream::access_nlt)
94
+ .def("is_planar", &codestream::is_planar);
95
+
96
+ py::class_<point>(m, "Point")
97
+ .def(py::init<ui32, ui32>(), py::arg("x") = 0, py::arg("y") = 0) // Constructor with default args
98
+ .def_readwrite("x", &point::x)
99
+ .def_readwrite("y", &point::y);
100
+
101
+ py::class_<size>(m, "Size")
102
+ .def(py::init<ui32, ui32>(), py::arg("w") = 0, py::arg("h") = 0) // Constructor with default args
103
+ .def_readwrite("w", &size::w) // width
104
+ .def_readwrite("h", &size::h) // height
105
+ .def("area", &size::area); // Expose the area function
106
+
107
+ py::class_<param_siz>(m, "ParamSiz")
108
+ // .def(py::init<local::param_siz*>()) // Constructor with local::param_siz* argument
109
+ .def("is_signed", &param_siz::is_signed)
110
+
111
+ .def("set_image_extent", &param_siz::set_image_extent)
112
+ .def("set_tile_size", &param_siz::set_tile_size)
113
+ .def("set_image_offset", &param_siz::set_image_offset)
114
+ .def("set_tile_offset", &param_siz::set_tile_offset)
115
+ .def("set_num_components", &param_siz::set_num_components)
116
+ .def("set_component", &param_siz::set_component, py::arg("comp_num"), py::arg("downsampling"), py::arg("bit_depth"), py::arg("is_signed"))
117
+
118
+ .def("get_image_extent", &param_siz::get_image_extent)
119
+ .def("get_image_offset", &param_siz::get_image_offset)
120
+ .def("get_tile_size", &param_siz::get_tile_size)
121
+ .def("get_tile_offset", &param_siz::get_tile_offset)
122
+ .def("get_num_components", &param_siz::get_num_components)
123
+ .def("get_bit_depth", &param_siz::get_bit_depth)
124
+ .def("get_downsampling", &param_siz::get_downsampling)
125
+ .def("get_recon_width", &param_siz::get_recon_width)
126
+ .def("get_recon_height", &param_siz::get_recon_height);
127
+
128
+ py::class_<param_cod>(m, "ParamCod")
129
+ // .def(py::init<local::param_cod*>())
130
+
131
+ .def("set_num_decomposition", &param_cod::set_num_decomposition, py::arg("num_decompositions"))
132
+ .def("set_block_dims", &param_cod::set_block_dims, py::arg("width"), py::arg("height"))
133
+ .def("set_precinct_size", &param_cod::set_precinct_size, py::arg("num_levels"), py::arg("precinct_size"))
134
+ .def("set_progression_order", &param_cod::set_progression_order, py::arg("name"))
135
+ .def("set_color_transform", &param_cod::set_color_transform, py::arg("color_transform"))
136
+ .def("set_reversible", &param_cod::set_reversible, py::arg("reversible"))
137
+
138
+ .def("get_num_decompositions", &param_cod::get_num_decompositions)
139
+ .def("get_block_dims", &param_cod::get_block_dims)
140
+ .def("get_log_block_dims", &param_cod::get_log_block_dims)
141
+ .def("is_reversible", &param_cod::is_reversible)
142
+ .def("get_precinct_size", &param_cod::get_precinct_size, py::arg("level_num"))
143
+ .def("get_log_precinct_size", &param_cod::get_log_precinct_size, py::arg("level_num"))
144
+ .def("get_progression_order", &param_cod::get_progression_order)
145
+ .def("get_progression_order_as_string", &param_cod::get_progression_order_as_string)
146
+ .def("get_num_layers", &param_cod::get_num_layers)
147
+ .def("is_using_color_transform", &param_cod::is_using_color_transform)
148
+ .def("packets_may_use_sop", &param_cod::packets_may_use_sop)
149
+ .def("packets_use_eph", &param_cod::packets_use_eph)
150
+ .def("get_block_vertical_causality", &param_cod::get_block_vertical_causality);
151
+
152
+ py::class_<line_buf, std::unique_ptr<line_buf, py::nodelete>>(m, "LineBuf")
153
+ .def(py::init<>())
154
+
155
+ .def_readwrite("size", &line_buf::size)
156
+ .def_readwrite("pre_size", &line_buf::pre_size)
157
+
158
+ // Wrapping i32 and f32 members as properties (since they are in a union)
159
+ // .def_property("i32", [](line_buf &self) { return self.i32; }, [](line_buf &self, si32* ptr) { self.i32 = ptr; })
160
+ // .def_property("f32", [](line_buf &self) { return self.f32; }, [](line_buf &self, float* ptr) { self.f32 = ptr; })
161
+ // Wrapping i32 and f32 members as addresses (pointers)
162
+ .def_property("i32_address",
163
+ [](line_buf &self) { return reinterpret_cast<uintptr_t>(self.i32); }, // Cast to uintptr_t to pass as integer
164
+ [](line_buf &self, uintptr_t ptr) { self.i32 = reinterpret_cast<si32*>(ptr); }
165
+ ) // Assign pointer back to i32
166
+ .def_property("f32_address",
167
+ [](line_buf &self) { return reinterpret_cast<uintptr_t>(self.f32); }, // Same for float pointer
168
+ [](line_buf &self, uintptr_t ptr) { self.f32 = reinterpret_cast<float*>(ptr); }
169
+ )
170
+
171
+ // Explicit instantiations for pre_alloc, finalize_alloc, and wrap for int and float
172
+ // .def("pre_alloc_int", &line_buf::pre_alloc<int>, py::arg("allocator"), py::arg("num_ele"), py::arg("pre_size"))
173
+ // .def("pre_alloc_float", &line_buf::pre_alloc<float>, py::arg("allocator"), py::arg("num_ele"), py::arg("pre_size"))
174
+
175
+ // .def("finalize_alloc_int", &line_buf::finalize_alloc<int>, py::arg("allocator"))
176
+ // .def("finalize_alloc_float", &line_buf::finalize_alloc<float>, py::arg("allocator"))
177
+
178
+ // .def("wrap_int", &line_buf::wrap<int>, py::arg("buffer"), py::arg("num_ele"), py::arg("pre_size"))
179
+ // .def("wrap_float", &line_buf::wrap<float>, py::arg("buffer"), py::arg("num_ele"), py::arg("pre_size"))
180
+ ;
181
+
182
+ }
183
+
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.1
2
+ Name: ojph
3
+ Version: 0.0.1
4
+ Summary: OpenJPH Bindings for Python and Numpy
5
+ Home-page: https://github.com/ramonaoptics/ojph
6
+ Author: Mark Harfouche
7
+ Author-email: mark@ramonaoptics.com
8
+ License: BSD-3-Clause
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Natural Language :: English
12
+ Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: Implementation :: CPython
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ License-File: LICENSE.txt
23
+ Requires-Dist: numpy>=1.24.0
24
+
25
+ OpenJPH bindings
@@ -0,0 +1,18 @@
1
+ LICENSE.txt
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ ojph/__init__.py
6
+ ojph/_imread.py
7
+ ojph/_imwrite.py
8
+ ojph/_static_version.py
9
+ ojph/_version.py
10
+ ojph/ojph_bindings.cpp
11
+ ojph.egg-info/PKG-INFO
12
+ ojph.egg-info/SOURCES.txt
13
+ ojph.egg-info/dependency_links.txt
14
+ ojph.egg-info/not-zip-safe
15
+ ojph.egg-info/requires.txt
16
+ ojph.egg-info/top_level.txt
17
+ tests/test_lossless_compression.py
18
+ tests/test_package.py
@@ -0,0 +1 @@
1
+
@@ -0,0 +1 @@
1
+ numpy>=1.24.0
@@ -0,0 +1 @@
1
+ ojph
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel", "pybind11"]
3
+ build-backend = "setuptools.build_meta"
ojph-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
ojph-0.0.1/setup.py ADDED
@@ -0,0 +1,84 @@
1
+ import platform
2
+ import sys
3
+ import os
4
+ from setuptools import setup, find_packages, Extension
5
+ # Hmm consider nanobind
6
+ import pybind11
7
+
8
+ with open('README.md', 'r', encoding='utf-8') as fh:
9
+ readme = fh.read()
10
+
11
+ def get_version_and_cmdclass(pkg_path):
12
+ """Load version.py module without importing the whole package.
13
+
14
+ Template code from miniver
15
+ """
16
+ import os
17
+ from importlib.util import module_from_spec, spec_from_file_location
18
+
19
+ spec = spec_from_file_location("version", os.path.join(pkg_path, "_version.py"))
20
+ module = module_from_spec(spec)
21
+ spec.loader.exec_module(module)
22
+ return module.__version__, module.get_cmdclass(pkg_path)
23
+
24
+
25
+ version, cmdclass = get_version_and_cmdclass("ojph")
26
+
27
+ # Include the pybind11 include directory
28
+ include_dirs = [pybind11.get_include()]
29
+ library_dirs = []
30
+
31
+ # Check for windows, add PREFIX/Library to the include dirs for compatibility with conda-forge
32
+ # This doesn't really hurt...
33
+ if platform.system() == 'Windows':
34
+ prefix = sys.prefix
35
+ # For conda environments
36
+ include_dirs.append(os.path.join(prefix, 'Library', 'include'))
37
+ library_dirs.append(os.path.join(prefix, 'Library', 'lib'))
38
+
39
+ # Define the extension module
40
+ ojph_module = Extension(
41
+ 'ojph.ojph_bindings',
42
+ sources=['ojph/ojph_bindings.cpp'],
43
+ include_dirs=include_dirs,
44
+ library_dirs=library_dirs,
45
+ libraries=['openjph'],
46
+ extra_compile_args=[]
47
+ )
48
+
49
+ # Setup
50
+ setup(
51
+ name='ojph',
52
+ version=version,
53
+ cmdclass=cmdclass,
54
+ description='OpenJPH Bindings for Python and Numpy',
55
+ long_description=readme,
56
+ url='https://github.com/ramonaoptics/ojph',
57
+ author='Mark Harfouche',
58
+ author_email='mark@ramonaoptics.com',
59
+ license='BSD-3-Clause',
60
+ classifiers=[
61
+ 'Development Status :: 3 - Alpha',
62
+ 'Intended Audience :: Developers',
63
+ 'Natural Language :: English',
64
+ 'License :: OSI Approved :: BSD License',
65
+ 'Operating System :: OS Independent',
66
+ 'Programming Language :: Python :: 3 :: Only',
67
+ 'Programming Language :: Python :: 3.10',
68
+ 'Programming Language :: Python :: 3.11',
69
+ 'Programming Language :: Python :: 3.12',
70
+ 'Programming Language :: Python :: 3.13',
71
+ 'Programming Language :: Python :: Implementation :: CPython',
72
+ 'Topic :: Software Development :: Libraries :: Python Modules',
73
+ ],
74
+ packages=find_packages(exclude=["tests*"]),
75
+ python_requires='>=3.10',
76
+ install_requires=[
77
+ 'numpy>=1.24.0',
78
+ ],
79
+ license_files=('LICENSE.txt',),
80
+ ext_modules=[ojph_module],
81
+ include_package_data=True,
82
+ zip_safe=False
83
+ )
84
+
@@ -0,0 +1,25 @@
1
+ from ojph import imwrite, imread
2
+ import numpy as np
3
+ import pytest
4
+
5
+
6
+ @pytest.mark.parametrize(
7
+ 'shape', [
8
+ (200, 100),
9
+ (1024, 256),
10
+ (256, 1024),
11
+ (1024, 1024),
12
+ (1024, 2048),
13
+ (4096, 2048),
14
+ (4096, 4096),
15
+ (8192, 1024),
16
+ (8192, 8192),
17
+ ]
18
+ )
19
+ def test_write_lossless(shape, tmp_path):
20
+ filename = tmp_path / 'test.jp2'
21
+ data = np.random.randint(0, 256, shape, dtype=np.uint8)
22
+ imwrite(filename, data)
23
+ image_read = imread(filename)
24
+
25
+ np.testing.assert_array_equal(data, image_read)
@@ -0,0 +1,16 @@
1
+ import ojph
2
+
3
+
4
+ def test_init():
5
+ ojph.__version__
6
+
7
+
8
+ def test_imports():
9
+ from ojph import imread
10
+ from ojph import imwrite
11
+ from ojph._imread import OJPHImageFile
12
+
13
+ # Ensure that this stuff "works"
14
+ imread
15
+ imwrite
16
+ OJPHImageFile