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 +26 -0
- ojph-0.0.1/PKG-INFO +25 -0
- ojph-0.0.1/README.md +1 -0
- ojph-0.0.1/ojph/__init__.py +6 -0
- ojph-0.0.1/ojph/_imread.py +102 -0
- ojph-0.0.1/ojph/_imwrite.py +82 -0
- ojph-0.0.1/ojph/_static_version.py +2 -0
- ojph-0.0.1/ojph/_version.py +194 -0
- ojph-0.0.1/ojph/ojph_bindings.cpp +183 -0
- ojph-0.0.1/ojph.egg-info/PKG-INFO +25 -0
- ojph-0.0.1/ojph.egg-info/SOURCES.txt +18 -0
- ojph-0.0.1/ojph.egg-info/dependency_links.txt +1 -0
- ojph-0.0.1/ojph.egg-info/not-zip-safe +1 -0
- ojph-0.0.1/ojph.egg-info/requires.txt +1 -0
- ojph-0.0.1/ojph.egg-info/top_level.txt +1 -0
- ojph-0.0.1/pyproject.toml +3 -0
- ojph-0.0.1/setup.cfg +4 -0
- ojph-0.0.1/setup.py +84 -0
- ojph-0.0.1/tests/test_lossless_compression.py +25 -0
- ojph-0.0.1/tests/test_package.py +16 -0
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,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,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", ¶m_siz::is_signed)
|
|
110
|
+
|
|
111
|
+
.def("set_image_extent", ¶m_siz::set_image_extent)
|
|
112
|
+
.def("set_tile_size", ¶m_siz::set_tile_size)
|
|
113
|
+
.def("set_image_offset", ¶m_siz::set_image_offset)
|
|
114
|
+
.def("set_tile_offset", ¶m_siz::set_tile_offset)
|
|
115
|
+
.def("set_num_components", ¶m_siz::set_num_components)
|
|
116
|
+
.def("set_component", ¶m_siz::set_component, py::arg("comp_num"), py::arg("downsampling"), py::arg("bit_depth"), py::arg("is_signed"))
|
|
117
|
+
|
|
118
|
+
.def("get_image_extent", ¶m_siz::get_image_extent)
|
|
119
|
+
.def("get_image_offset", ¶m_siz::get_image_offset)
|
|
120
|
+
.def("get_tile_size", ¶m_siz::get_tile_size)
|
|
121
|
+
.def("get_tile_offset", ¶m_siz::get_tile_offset)
|
|
122
|
+
.def("get_num_components", ¶m_siz::get_num_components)
|
|
123
|
+
.def("get_bit_depth", ¶m_siz::get_bit_depth)
|
|
124
|
+
.def("get_downsampling", ¶m_siz::get_downsampling)
|
|
125
|
+
.def("get_recon_width", ¶m_siz::get_recon_width)
|
|
126
|
+
.def("get_recon_height", ¶m_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", ¶m_cod::set_num_decomposition, py::arg("num_decompositions"))
|
|
132
|
+
.def("set_block_dims", ¶m_cod::set_block_dims, py::arg("width"), py::arg("height"))
|
|
133
|
+
.def("set_precinct_size", ¶m_cod::set_precinct_size, py::arg("num_levels"), py::arg("precinct_size"))
|
|
134
|
+
.def("set_progression_order", ¶m_cod::set_progression_order, py::arg("name"))
|
|
135
|
+
.def("set_color_transform", ¶m_cod::set_color_transform, py::arg("color_transform"))
|
|
136
|
+
.def("set_reversible", ¶m_cod::set_reversible, py::arg("reversible"))
|
|
137
|
+
|
|
138
|
+
.def("get_num_decompositions", ¶m_cod::get_num_decompositions)
|
|
139
|
+
.def("get_block_dims", ¶m_cod::get_block_dims)
|
|
140
|
+
.def("get_log_block_dims", ¶m_cod::get_log_block_dims)
|
|
141
|
+
.def("is_reversible", ¶m_cod::is_reversible)
|
|
142
|
+
.def("get_precinct_size", ¶m_cod::get_precinct_size, py::arg("level_num"))
|
|
143
|
+
.def("get_log_precinct_size", ¶m_cod::get_log_precinct_size, py::arg("level_num"))
|
|
144
|
+
.def("get_progression_order", ¶m_cod::get_progression_order)
|
|
145
|
+
.def("get_progression_order_as_string", ¶m_cod::get_progression_order_as_string)
|
|
146
|
+
.def("get_num_layers", ¶m_cod::get_num_layers)
|
|
147
|
+
.def("is_using_color_transform", ¶m_cod::is_using_color_transform)
|
|
148
|
+
.def("packets_may_use_sop", ¶m_cod::packets_may_use_sop)
|
|
149
|
+
.def("packets_use_eph", ¶m_cod::packets_use_eph)
|
|
150
|
+
.def("get_block_vertical_causality", ¶m_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
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
numpy>=1.24.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ojph
|
ojph-0.0.1/setup.cfg
ADDED
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
|