Skip to content

Commit 4d795e8

Browse files
authored
Add testclf tool (#49)
* Add testclf tool Signed-off-by: Michael Dolan <michdolan@gmail.com> * Fix formatting and add suggested comment Signed-off-by: Michael Dolan <michdolan@gmail.com>
1 parent 6ae111a commit 4d795e8

File tree

3 files changed

+212
-0
lines changed

3 files changed

+212
-0
lines changed

utilities/tools/testclf/README.rst

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
Test CLF
2+
========
3+
4+
Apply a Common LUT Format (CLF) transform to input RGB data to test its
5+
accuracy. Recommended image format: OpenEXR.
6+
7+
Usage
8+
-----
9+
10+
For full documentation, run:
11+
12+
`python testclf.py --help`
13+
14+
To apply a CLF to an image, run:
15+
16+
`python testclf.py transform.clf input.exr -o output.exr`
17+
18+
To apply a CLF to one or more RGB float triplets, run:
19+
20+
`python testclf.py transform.clf R,G,B R,G,B ...`
21+
22+
which will print to the shell:
23+
24+
```
25+
R,G,B -> R,G,B
26+
R,G,B -> R,G,B
27+
```
28+
29+
To apply a CLF inverse, add the `-i` or `--inverse` option to either
30+
command.
31+
32+
Dependencies
33+
------------
34+
35+
* Python (>=3.7)
36+
* `pip install requirements.txt`
37+
* Installs [numpy](https://pypi.org/project/numpy/)
38+
* Installs [opencolorio](https://pypi.org/project/opencolorio/)
39+
* Installs [imageio](https://pypi.org/project/imageio/)
40+
* `imageio_download_bin freeimage`
41+
* Installs [imageio FreeImage plugin](https://imageio.readthedocs.io/en/stable/_autosummary/imageio.plugins.freeimage.html) to enable OpenEXR support
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
numpy
2+
opencolorio
3+
imageio

utilities/tools/testclf/testclf.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
# Copyright Contributors to the OpenColorIO Project.
3+
"""
4+
Test CLF
5+
========
6+
7+
CLI tool to apply a Common LUT Format (CLF) transform to input RGB data
8+
to test its accuracy.
9+
10+
This module can also be imported and the function ``test_clf`` called
11+
to use the tool programmatically.
12+
13+
In addition to CLF, this tool will work with any other LUT format
14+
supported by OpenColorIO, including CTF.
15+
"""
16+
17+
import argparse
18+
import sys
19+
import traceback
20+
21+
import numpy as np
22+
import imageio
23+
import PyOpenColorIO as ocio
24+
25+
__author__ = "OpenColorIO Contributors"
26+
__copyright__ = "Copyright Contributors to the OpenColorIO Project."
27+
__license__ = "New BSD License - https://opensource.org/licenses/BSD-3-Clause"
28+
__maintainer__ = "OpenColorIO Contributors"
29+
__email__ = "ocio-dev@lists.aswf.io"
30+
__status__ = "Production"
31+
32+
__all__ = ["test_clf"]
33+
34+
35+
def test_clf(clf_path, input_data, output_path, inverse=False):
36+
"""
37+
Apply a Common LUT Format (CLF) transform to input RGB data to test
38+
its accuracy. Recommended image format: OpenEXR.
39+
40+
Parameters
41+
----------
42+
clf_path : str
43+
*CLF* transform file path.
44+
input_data : list[str]
45+
Single input image file path or one or more "," delimited RGB
46+
float triplet strings
47+
(e.g. ["1.0,0.0,0.0", "0.0,0.1,0.0", "0,0,1"]).
48+
output_path : str, optional
49+
Output image file path, required when an input image file is
50+
specified.
51+
inverse : bool, optional
52+
Whether to apply transform in the inverse direction.
53+
54+
Raises
55+
------
56+
RuntimeError
57+
If the input parameters are invalid.
58+
"""
59+
output_image = False
60+
num_channels = 3
61+
62+
if "," in input_data[0]:
63+
# Interpret as RGB array
64+
src_rgb = np.array(
65+
[list(map(float, c.split(","))) for c in input_data],
66+
dtype=np.float32,
67+
)
68+
if not src_rgb.shape == (len(input_data), 3):
69+
raise RuntimeError(
70+
f"Invalid input array shape {src_rgb.shape}. Expected (N, 3)."
71+
)
72+
else:
73+
# Interpret as RGB image path
74+
src_rgb = imageio.imread(input_data[0])
75+
num_channels = src_rgb.shape[2]
76+
77+
output_image = True
78+
if not output_path:
79+
raise RuntimeError(
80+
"Output path (-o OUTPUT, --output OUTPUT) required when input is an "
81+
"image file."
82+
)
83+
84+
# Create default OCIO config
85+
config = ocio.Config.CreateRaw()
86+
87+
# Build a processor from a single transform
88+
file_tf = ocio.FileTransform(
89+
src=clf_path,
90+
interpolation=ocio.INTERP_BEST,
91+
direction=ocio.TRANSFORM_DIR_INVERSE
92+
if inverse
93+
else ocio.TRANSFORM_DIR_FORWARD,
94+
)
95+
proc = config.getProcessor(file_tf)
96+
cpu_proc = proc.getDefaultCPUProcessor()
97+
98+
# Apply file transform to a copy of src pixels in-place. Preserve src for
99+
# comparison.
100+
dst_rgb = np.copy(src_rgb)
101+
102+
if num_channels == 4:
103+
cpu_proc.applyRGBA(dst_rgb)
104+
elif num_channels == 3:
105+
cpu_proc.applyRGB(dst_rgb)
106+
else:
107+
raise RuntimeError(
108+
f"Unsupported number of channels ({num_channels}) in input data. Image "
109+
f"must be RGB (3) or RGBA (4)."
110+
)
111+
112+
if output_image:
113+
# Write array to output image
114+
imageio.imwrite(output_path, dst_rgb)
115+
else:
116+
# Print pixel transformations
117+
for src_pixel, dst_pixel in zip(src_rgb, dst_rgb):
118+
print(
119+
f"{', '.join(map(str, src_pixel))} -> "
120+
f"{', '.join(map(str, dst_pixel))}"
121+
)
122+
123+
124+
if __name__ == "__main__":
125+
# Tool interface
126+
parser = argparse.ArgumentParser(
127+
description="Apply a Common LUT Format (CLF) transform to input RGB data to "
128+
"test its accuracy. Recommended image format: OpenEXR."
129+
)
130+
parser.add_argument(
131+
"clf",
132+
type=str,
133+
help="CLF transform file.",
134+
)
135+
parser.add_argument(
136+
"input",
137+
type=str,
138+
nargs="+",
139+
help="Input image file or one or more ',' delimited RGB float triplets "
140+
"(e.g. 1.0,0.0,0.0 0.0,0.1,0.0 0,0,1).",
141+
)
142+
parser.add_argument(
143+
"-o",
144+
"--output",
145+
type=str,
146+
help="Output image file, required when an input image file is specified.",
147+
)
148+
parser.add_argument(
149+
"-i",
150+
"--inverse",
151+
action="store_true",
152+
help="Apply transform in the inverse direction.",
153+
)
154+
155+
# Parse arguments
156+
args = parser.parse_args()
157+
158+
# Run tool and exit
159+
exit_code = 1
160+
161+
try:
162+
test_clf(args.clf, args.input, args.output, args.inverse)
163+
exit_code = 0
164+
except Exception as e:
165+
traceback.print_exc()
166+
print(f"An error occurred: {e}")
167+
168+
sys.exit(exit_code)

0 commit comments

Comments
 (0)