Skip to content

Commit 80374c0

Browse files
authored
Python internals: support int8[] metadata and ICCProfile, refactor (#3556)
This patch allows ParamValue, ParamValueList, and ImageSpec to support attribute/getattribute of metadata consisting of uint8[] arrays, which is how we store certain binary blobs or raw byte arrays. We translate this to Python as a numpy array of type uint8 (dtype='B'). This fixes a limitation where it was not possible to directly retrieve or set the "ICCProfile" metadata (which is stored as a uint8 array) from the Python APIs, as you could easily do from the C++ APIs. In the process, I found similar bits of code in several different places, in various states of incompleteness. I refactored to make a single make_pyobject() utility and then used that call in place of several other redundant pieces of functionality.
1 parent 96f420b commit 80374c0

File tree

12 files changed

+184
-152
lines changed

12 files changed

+184
-152
lines changed

src/libOpenImageIO/exif.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,11 @@ version4uint8_handler(const TagInfo& taginfo, const TIFFDirEntry& dir,
341341
bool /*swapendian*/ = false, int offset_adjustment = 0)
342342
{
343343
const char* data = (const char*)dataptr(dir, buf, offset_adjustment);
344-
if (tiff_data_size(dir) == 4 && data != nullptr) // sanity check
345-
spec.attribute(taginfo.name, TypeDesc(TypeDesc::UINT8, 4),
346-
(const char*)data);
344+
if (tiff_data_size(dir) == 4 && data != nullptr) { // sanity check
345+
int val[4] = { (unsigned char)data[0], (unsigned char)data[1],
346+
(unsigned char)data[2], (unsigned char)data[3] };
347+
spec.attribute(taginfo.name, TypeDesc(TypeDesc::INT, 4), val);
348+
}
347349
}
348350

349351

src/python/py_imagespec.cpp

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,24 +50,7 @@ ImageSpec_getattribute_typed(const ImageSpec& spec, const std::string& name,
5050
const ParamValue* p = spec.find_attribute(name, tmpparam, type);
5151
if (!p)
5252
return py::none();
53-
type = p->type();
54-
if (type.basetype == TypeDesc::INT)
55-
return C_to_val_or_tuple((const int*)p->data(), type);
56-
if (type.basetype == TypeDesc::UINT)
57-
return C_to_val_or_tuple((const unsigned int*)p->data(), type);
58-
if (type.basetype == TypeDesc::INT16)
59-
return C_to_val_or_tuple((const short*)p->data(), type);
60-
if (type.basetype == TypeDesc::UINT16)
61-
return C_to_val_or_tuple((const unsigned short*)p->data(), type);
62-
if (type.basetype == TypeDesc::FLOAT)
63-
return C_to_val_or_tuple((const float*)p->data(), type);
64-
if (type.basetype == TypeDesc::DOUBLE)
65-
return C_to_val_or_tuple((const double*)p->data(), type);
66-
if (type.basetype == TypeDesc::HALF)
67-
return C_to_val_or_tuple((const half*)p->data(), type);
68-
if (type.basetype == TypeDesc::STRING)
69-
return C_to_val_or_tuple((const char**)p->data(), type);
70-
return py::none();
53+
return make_pyobject(p->data(), p->type(), p->nvalues());
7154
}
7255

7356

@@ -238,7 +221,7 @@ declare_imagespec(py::module& m)
238221
[](const ImageSpec& self, const std::string& key, py::object def) {
239222
ParamValue tmpparam;
240223
auto p = self.find_attribute(key, tmpparam);
241-
return p ? ParamValue_getitem(*p, false, def) : def;
224+
return p ? make_pyobject(p->data(), p->type(), 1, def) : def;
242225
},
243226
"key"_a, "default"_a = py::none())
244227
.def(
@@ -285,7 +268,7 @@ declare_imagespec(py::module& m)
285268
auto p = self.find_attribute(key, tmpparam);
286269
if (p == nullptr)
287270
throw py::key_error("key '" + key + "' does not exist");
288-
return ParamValue_getitem(*p);
271+
return make_pyobject(p->data(), p->type());
289272
})
290273
// __setitem__ is the dict-like `ImageSpec[key] = value` assignment
291274
.def("__setitem__",

src/python/py_oiio.cpp

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,47 @@ oiio_attribute_typed(const std::string& name, TypeDesc type,
219219

220220

221221

222+
py::object
223+
make_pyobject(const void* data, TypeDesc type, int nvalues,
224+
py::object defaultvalue)
225+
{
226+
if (type.basetype == TypeDesc::INT32)
227+
return C_to_val_or_tuple((const int*)data, type, nvalues);
228+
if (type.basetype == TypeDesc::FLOAT)
229+
return C_to_val_or_tuple((const float*)data, type, nvalues);
230+
if (type.basetype == TypeDesc::STRING)
231+
return C_to_val_or_tuple((const char**)data, type, nvalues);
232+
if (type.basetype == TypeDesc::UINT32)
233+
return C_to_val_or_tuple((const unsigned int*)data, type, nvalues);
234+
if (type.basetype == TypeDesc::INT16)
235+
return C_to_val_or_tuple((const short*)data, type, nvalues);
236+
if (type.basetype == TypeDesc::UINT16)
237+
return C_to_val_or_tuple((const unsigned short*)data, type, nvalues);
238+
if (type.basetype == TypeDesc::INT64)
239+
return C_to_val_or_tuple((const int64_t*)data, type, nvalues);
240+
if (type.basetype == TypeDesc::UINT64)
241+
return C_to_val_or_tuple((const uint64_t*)data, type, nvalues);
242+
if (type.basetype == TypeDesc::DOUBLE)
243+
return C_to_val_or_tuple((const double*)data, type, nvalues);
244+
if (type.basetype == TypeDesc::HALF)
245+
return C_to_val_or_tuple((const half*)data, type, nvalues);
246+
if (type.basetype == TypeDesc::UINT8 && type.arraylen > 0) {
247+
// Array of uint8 bytes
248+
// Have to make a copy of the data, because make_numpy_array will
249+
// take possession of it.
250+
uint8_t* ucdata(new uint8_t[type.arraylen * nvalues]);
251+
std::memcpy(ucdata, data, type.arraylen * nvalues);
252+
return make_numpy_array(ucdata, 1, 1, size_t(type.arraylen),
253+
size_t(nvalues));
254+
}
255+
if (type.basetype == TypeDesc::UINT8)
256+
return C_to_val_or_tuple((const unsigned char*)data, type, nvalues);
257+
debugfmt("Don't know how to handle type {}\n", type);
258+
return defaultvalue;
259+
}
260+
261+
262+
222263
static py::object
223264
oiio_getattribute_typed(const std::string& name, TypeDesc type = TypeUnknown)
224265
{
@@ -227,13 +268,7 @@ oiio_getattribute_typed(const std::string& name, TypeDesc type = TypeUnknown)
227268
char* data = OIIO_ALLOCA(char, type.size());
228269
if (!OIIO::getattribute(name, type, data))
229270
return py::none();
230-
if (type.basetype == TypeDesc::INT)
231-
return C_to_val_or_tuple((const int*)data, type);
232-
if (type.basetype == TypeDesc::FLOAT)
233-
return C_to_val_or_tuple((const float*)data, type);
234-
if (type.basetype == TypeDesc::STRING)
235-
return C_to_val_or_tuple((const char**)data, type);
236-
return py::none();
271+
return make_pyobject(data, type);
237272
}
238273

239274

src/python/py_oiio.h

Lines changed: 9 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,14 @@ attribute_typed(T& myobj, string_view name, TypeDesc type, const POBJ& dataobj)
508508

509509

510510

511+
// `data` points to values of `type`. Make a python object that represents
512+
// them.
513+
py::object
514+
make_pyobject(const void* data, TypeDesc type, int nvalues = 1,
515+
py::object defaultvalue = py::none());
516+
517+
518+
511519
template<typename T>
512520
py::object
513521
getattribute_typed(const T& obj, const std::string& name,
@@ -519,29 +527,7 @@ getattribute_typed(const T& obj, const std::string& name,
519527
bool ok = obj.getattribute(name, type, data);
520528
if (!ok)
521529
return py::none(); // None
522-
if (type.basetype == TypeDesc::INT)
523-
return C_to_val_or_tuple((const int*)data, type);
524-
if (type.basetype == TypeDesc::UINT)
525-
return C_to_val_or_tuple((const unsigned int*)data, type);
526-
if (type.basetype == TypeDesc::INT16)
527-
return C_to_val_or_tuple((const short*)data, type);
528-
if (type.basetype == TypeDesc::UINT16)
529-
return C_to_val_or_tuple((const unsigned short*)data, type);
530-
if (type.basetype == TypeDesc::UINT8)
531-
return C_to_val_or_tuple((const unsigned char*)data, type);
532-
if (type.basetype == TypeDesc::FLOAT)
533-
return C_to_val_or_tuple((const float*)data, type);
534-
if (type.basetype == TypeDesc::DOUBLE)
535-
return C_to_val_or_tuple((const double*)data, type);
536-
if (type.basetype == TypeDesc::HALF)
537-
return C_to_val_or_tuple((const half*)data, type);
538-
if (type.basetype == TypeDesc::STRING)
539-
return C_to_val_or_tuple((const char**)data, type);
540-
if (type.basetype == TypeDesc::INT64)
541-
return C_to_val_or_tuple((const int64_t*)data, type);
542-
if (type.basetype == TypeDesc::UINT64)
543-
return C_to_val_or_tuple((const uint64_t*)data, type);
544-
return py::none();
530+
return make_pyobject(data, type);
545531
}
546532

547533

@@ -620,40 +606,6 @@ make_numpy_array(TypeDesc format, void* data, int dims, size_t chans,
620606

621607

622608

623-
inline py::object
624-
ParamValue_getitem(const ParamValue& self, bool allitems = false,
625-
py::object defaultvalue = py::none())
626-
{
627-
TypeDesc t = self.type();
628-
int nvals = allitems ? self.nvalues() : 1;
629-
630-
#define ParamValue_convert_dispatch(TYPE) \
631-
case TypeDesc::TYPE: \
632-
return C_to_val_or_tuple((CType<TypeDesc::TYPE>::type*)self.data(), t, \
633-
nvals)
634-
635-
switch (t.basetype) {
636-
ParamValue_convert_dispatch(UCHAR);
637-
// ParamValue_convert_dispatch(CHAR);
638-
ParamValue_convert_dispatch(USHORT);
639-
ParamValue_convert_dispatch(SHORT);
640-
ParamValue_convert_dispatch(UINT);
641-
ParamValue_convert_dispatch(INT);
642-
// ParamValue_convert_dispatch(ULONGLONG);
643-
// ParamValue_convert_dispatch(LONGLONG);
644-
ParamValue_convert_dispatch(HALF);
645-
ParamValue_convert_dispatch(FLOAT);
646-
ParamValue_convert_dispatch(DOUBLE);
647-
case TypeDesc::STRING:
648-
return C_to_val_or_tuple((const char**)self.data(), t, nvals);
649-
default: return defaultvalue;
650-
}
651-
652-
#undef ParamValue_convert_dispatch
653-
}
654-
655-
656-
657609
template<typename C>
658610
inline void
659611
delegate_setitem(C& self, const std::string& key, py::object obj)

src/python/py_paramvalue.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,28 @@ ParamValue_from_pyobject(string_view name, TypeDesc type, int nvalues,
4545
pv.init(name, type, nvalues, interp, &u[0]);
4646
return pv;
4747
}
48+
} else if (type.basetype == TypeDesc::UINT8 && type.arraylen
49+
&& py::isinstance<py::bytes>(obj)) {
50+
// Special case: converting a "bytes" object to a byte array
51+
std::string s = obj.cast<py::bytes>();
52+
if (type.arraylen < 0) // convert un-specified length to real length
53+
type.arraylen = int(s.size()) / nvalues;
54+
if (type.arraylen * nvalues == int(s.size())) {
55+
std::vector<uint8_t> vals((const uint8_t*)s.data(),
56+
(const uint8_t*)s.data() + s.size());
57+
pv.init(name, type, nvalues, interp, vals.data());
58+
return pv;
59+
}
60+
} else if (type.basetype == TypeDesc::UINT8) {
61+
std::vector<uint8_t> vals;
62+
py_to_stdvector(vals, obj);
63+
if (vals.size() >= expected_size) {
64+
pv.init(name, type, nvalues, interp, vals.data());
65+
return pv;
66+
}
67+
} else {
68+
Strutil::print("ParamValue_from_pyobject not sure how to handle {} {}\n",
69+
name, type);
4870
}
4971

5072
// I think this is what we should do here when not enough data is
@@ -143,7 +165,9 @@ declare_paramvalue(py::module& m)
143165
#endif
144166
.def_property_readonly("value",
145167
[](const ParamValue& self) {
146-
return ParamValue_getitem(self, true);
168+
return make_pyobject(self.data(),
169+
self.type(),
170+
self.nvalues());
147171
})
148172
.def_property_readonly("__len__", &ParamValue::nvalues)
149173
.def(py::init<const std::string&, int>())
@@ -180,7 +204,7 @@ declare_paramvalue(py::module& m)
180204
auto p = self.find(key);
181205
if (p == self.end())
182206
throw py::key_error("key '" + key + "' does not exist");
183-
return ParamValue_getitem(*p);
207+
return make_pyobject(p->data(), p->type());
184208
},
185209
py::return_value_policy::reference_internal)
186210
// __setitem__ is the dict-like `pvl[key] = value` assignment

0 commit comments

Comments
 (0)