From 0c036ad9e8252e7c8684250ef0ffca6e7c7074d2 Mon Sep 17 00:00:00 2001 From: dendenxu Date: Tue, 14 Oct 2025 16:49:37 +0800 Subject: [PATCH 1/2] Fix unfinished multi-part file loading in the py This commit fixes the issue where an unfinished multi-part file could not be loaded properly in the python warpper for OpenEXR. This happens when headers for the multi-part file are written to disk but the actual pixels never finish being written. The current fix skips the corrupted or empty parts with no pixel data while retaining the ability for loading the correct ones. Signed-off-by: dendenxu --- src/wrappers/python/PyOpenEXR.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/wrappers/python/PyOpenEXR.cpp b/src/wrappers/python/PyOpenEXR.cpp index 2063c3ce73..09c1043467 100644 --- a/src/wrappers/python/PyOpenEXR.cpp +++ b/src/wrappers/python/PyOpenEXR.cpp @@ -209,17 +209,29 @@ PyFile::PyFile(const std::string& filename, bool separate_channels, bool header_ // auto type = header.type(); - if (type == SCANLINEIMAGE || type == TILEDIMAGE) + try { - P.readPixels(*_inputFile, header.channels(), shape, rgbaChannels, dw, separate_channels); + if (type == SCANLINEIMAGE || type == TILEDIMAGE) + { + P.readPixels(*_inputFile, header.channels(), shape, rgbaChannels, dw, separate_channels); + } + else if (type == DEEPSCANLINE || type == DEEPTILE) + { + P.readDeepPixels(*_inputFile, type, header.channels(), shape, rgbaChannels, dw, separate_channels); + } + parts.append(py::cast(PyPart(P))); } - else if (type == DEEPSCANLINE || type == DEEPTILE) + catch (const std::exception& e) { - P.readDeepPixels(*_inputFile, type, header.channels(), shape, rgbaChannels, dw, separate_channels); + // Log the error and skip appending this part + py::print("Warning: Exception raised reading pixel data for part", part_index, "-", e.what()); } } - - parts.append(py::cast(PyPart(P))); + else + { + // If only reading the header, always append this part + parts.append(py::cast(PyPart(P))); + } } // for parts } From ca6b18b557994323caa58f5b2fca87c1ff1239c5 Mon Sep 17 00:00:00 2001 From: dendenxu Date: Tue, 14 Oct 2025 19:02:09 +0800 Subject: [PATCH 2/2] Add part_indices for the python File API This can specify the specific parts of a multi-part EXR file to be loaded to avoid excessive IO when only some parts of the file is needed by the program. This functionality could be particular useful when the file is very large and only a very small portion of it is required in memory. Signed-off-by: dendenxu --- src/wrappers/python/PyOpenEXR.cpp | 27 ++++++++++++++++++++++++--- src/wrappers/python/PyOpenEXR.h | 2 +- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/wrappers/python/PyOpenEXR.cpp b/src/wrappers/python/PyOpenEXR.cpp index 09c1043467..a29512ce1c 100644 --- a/src/wrappers/python/PyOpenEXR.cpp +++ b/src/wrappers/python/PyOpenEXR.cpp @@ -150,13 +150,29 @@ PyFile::PyFile(const py::dict& header, const py::dict& channels) // e.g. "left.R", "left.G", etc, the channel key is the prefix. // -PyFile::PyFile(const std::string& filename, bool separate_channels, bool header_only) +PyFile::PyFile(const std::string& filename, bool separate_channels, bool header_only, const py::list& part_indices) : filename(filename), _header_only(header_only), _inputFile(std::make_unique(filename.c_str())) { - for (int part_index = 0; part_index < _inputFile->parts(); part_index++) + std::vector indices_to_process; + if (part_indices.empty()) { + // If no specific parts requested, process all parts + for (int i = 0; i < _inputFile->parts(); i++) { + indices_to_process.push_back(i); + } + } else { + // Otherwise, process only the requested parts + for (auto idx : part_indices) { + int part_idx = py::cast(idx); + if (part_idx >= 0 && part_idx < _inputFile->parts()) { + indices_to_process.push_back(part_idx); + } + } + } + + for (int part_index : indices_to_process) { const Header& header = _inputFile->header(part_index); @@ -2757,10 +2773,11 @@ PYBIND11_MODULE(OpenEXR, m) >>> f.write("out.exr") )pbdoc") .def(py::init<>()) - .def(py::init(), + .def(py::init(), py::arg("filename"), py::arg("separate_channels")=false, py::arg("header_only")=false, + py::arg("part_indices")=py::list(), R"pbdoc( Initialize a File by reading the image from the given filename. @@ -2773,10 +2790,14 @@ PYBIND11_MODULE(OpenEXR, m) if False (default), read pixel data into a single "RGB" or "RGBA" numpy array of dimension (height,width,3) or (height,width,4); header_only : bool If True, read only the header metadata, not the image pixel data. + part_indices : list + List of part indices to read. If empty, all parts are read. Example ------- >>> f = OpenEXR.File("image.exr", separate_channels=False, header_only=False) + >>> # Read only parts 0 and 2 + >>> f = OpenEXR.File("multipart.exr", part_indices=[0, 2]) )pbdoc") .def(py::init(), py::arg("header"), diff --git a/src/wrappers/python/PyOpenEXR.h b/src/wrappers/python/PyOpenEXR.h index 8346e3c08e..c9cb23f905 100644 --- a/src/wrappers/python/PyOpenEXR.h +++ b/src/wrappers/python/PyOpenEXR.h @@ -18,7 +18,7 @@ class PyFile { public: PyFile(); - PyFile(const std::string& filename, bool separate_channels = false, bool header_only = false); + PyFile(const std::string& filename, bool separate_channels = false, bool header_only = false, const py::list& part_indices = py::list()); PyFile(const py::dict& header, const py::dict& channels); PyFile(const py::list& parts);