Skip to content

Commit f1c7450

Browse files
committed
feat: EXRInput, EXROutput을 추가하다
EXRInput, EXROutput : EXR File을 읽고 쓰기 위한 Class
1 parent 1f38624 commit f1c7450

File tree

4 files changed

+159
-1
lines changed

4 files changed

+159
-1
lines changed

openexr_python/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@
1010
__version__ = "0.0.0" # dynamic
1111

1212
from .exr_header import *
13-
from .exr_window import *
13+
from .exr_input_file import *
14+
from .exr_output_file import *
15+
from .util import *

openexr_python/exr_input_file.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from dataclasses import dataclass
2+
import os
3+
from typing import Any, Dict, List, Tuple
4+
5+
import numpy as np
6+
from openexr_python.exr_header import EXRHeader
7+
import OpenEXR
8+
from openexr_python.util import (
9+
get_size_from_window,
10+
convert_numpy_type_to_imath_type,
11+
convert_imath_type_to_numpy_type,
12+
)
13+
14+
15+
class EXRInputFile:
16+
def __init__(self, file_path: str):
17+
assert OpenEXR.isOpenExrFile(file_path)
18+
self.file_path = file_path
19+
self.raw_input_file: Any = OpenEXR.InputFile(file_path)
20+
21+
def get_single_channel_buffer(self, channel_key: str) -> bytes:
22+
return self.raw_input_file.channel(channel_key)
23+
24+
def get_multi_channel_buffer(self, channel_keys: List[str]) -> List[bytes]:
25+
assert len(channel_keys) > 0
26+
assert all([len(c) == 1 for c in channel_keys])
27+
return self.raw_input_file.channels(channel_keys)
28+
29+
def read_numpy(self, channel_key: str) -> np.ndarray:
30+
header = self.header
31+
buffer = self.get_single_channel_buffer(channel_key=channel_key)
32+
channel_info = header.channels[channel_key]
33+
dtype = convert_imath_type_to_numpy_type(channel_info.type)
34+
np_buffer = np.frombuffer(buffer=buffer, dtype=dtype)
35+
size = get_size_from_window(header.data_window)
36+
return np.reshape(np_buffer, newshape=(size[1], size[0]))
37+
38+
def close(self):
39+
self.raw_input_file.close()
40+
41+
@property
42+
def header(self) -> EXRHeader:
43+
raw_header = self.raw_input_file.header()
44+
return EXRHeader(raw_header=raw_header)
45+
46+
@property
47+
def is_complete(self) -> bool:
48+
"""
49+
Returns whether this exr file contains all data and is not missing.
50+
51+
Returns:
52+
bool
53+
"""
54+
return self.raw_input_file.isComplete()

openexr_python/exr_output_file.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from dataclasses import dataclass
2+
import os
3+
from typing import Any, Dict, List, Tuple
4+
5+
import numpy as np
6+
from openexr_python.exr_header import EXRHeader
7+
import OpenEXR
8+
from openexr_python.util import (
9+
get_size_from_window,
10+
convert_numpy_type_to_imath_type,
11+
convert_imath_type_to_numpy_type,
12+
)
13+
14+
15+
class EXROutputFile:
16+
def __init__(self, file_path: str, header: EXRHeader) -> None:
17+
assert os.path.isdir(os.path.dirname(file_path))
18+
self.file_path = file_path
19+
self.header = header
20+
self.raw_output_file: Any = OpenEXR.OutputFile(file_path, header.raw_header)
21+
self._is_closed = False
22+
23+
def write_pixels(self, pixels: Dict[str, bytes]):
24+
assert self._is_closed is not True, f"The EXR File already has been closed."
25+
self.raw_output_file.writePixels(pixels)
26+
self._close()
27+
28+
def _convert_bytes_to_numpy(self, numpy_buffer: np.ndarray):
29+
# check the buffer is 2 dim shape(h x w)
30+
shape = numpy_buffer.shape
31+
assert len(shape) == 2
32+
# check the buffer is fit to the window size
33+
data_height, data_width = shape
34+
window_width, window_height = get_size_from_window(self.header.data_window)
35+
assert window_width == data_width
36+
assert window_height == data_height
37+
return numpy_buffer.tobytes()
38+
39+
def write_pixels_with_numpy(self, pixels: Dict[str, np.ndarray]):
40+
pixels_bytes: Dict[str, bytes] = {}
41+
for channel_name, numpy_buffer in pixels.items():
42+
# check the channel is exist
43+
assert channel_name in self.header.channels
44+
pixels_bytes[channel_name] = self._convert_bytes_to_numpy(
45+
numpy_buffer=numpy_buffer
46+
)
47+
# check the buffer type is fit to the window type
48+
assert (
49+
convert_numpy_type_to_imath_type(numpy_buffer.dtype)
50+
== self.header.channels[channel_name].type
51+
)
52+
self.write_pixels(pixels=pixels_bytes)
53+
54+
def current_scan_line(self):
55+
return self.raw_output_file.currentScanLine()
56+
57+
def _close(self):
58+
assert self._is_closed is not True, f"The EXR File already has been closed."
59+
self.raw_output_file.close()
60+
self._is_closed = True

openexr_python/util.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from typing import Tuple
2+
import Imath
3+
import numpy as np
4+
5+
PIXEL_TYPE_CONVERT_MAP = {
6+
np.uint32: Imath.PixelType.UINT,
7+
np.float16: Imath.PixelType.HALF,
8+
np.float32: Imath.PixelType.FLOAT,
9+
Imath.PixelType.UINT: np.uint32,
10+
Imath.PixelType.HALF: np.float16,
11+
Imath.PixelType.FLOAT: np.float32,
12+
}
13+
14+
15+
def convert_numpy_type_to_imath_type(data_type: np.dtype) -> Imath.PixelType:
16+
assert data_type in [np.float16, np.float32, np.uint32]
17+
keys = [k for k in PIXEL_TYPE_CONVERT_MAP.keys()]
18+
idx = -1
19+
for i, key in enumerate(keys):
20+
if key != data_type:
21+
continue
22+
idx = i
23+
break
24+
assert idx != -1
25+
return Imath.PixelType(PIXEL_TYPE_CONVERT_MAP[keys[idx]])
26+
27+
28+
def convert_imath_type_to_numpy_type(data_type: Imath.PixelType):
29+
assert type(data_type) is Imath.PixelType
30+
assert data_type.v in PIXEL_TYPE_CONVERT_MAP
31+
return PIXEL_TYPE_CONVERT_MAP[data_type.v]
32+
33+
34+
def create_channel(data_type: np.dtype, sampling_x: int = 1, sampling_y: int = 1):
35+
assert sampling_x > 0
36+
assert sampling_y > 0
37+
im_data_type = convert_numpy_type_to_imath_type(data_type)
38+
return Imath.Channel(type=im_data_type, xSampling=1, ySampling=1)
39+
40+
41+
def get_size_from_window(window: Imath.Box2i) -> Tuple[int, int]:
42+
return (1 + window.max.x - window.min.x, 1 + window.max.y - window.min.y)

0 commit comments

Comments
 (0)