NDArray generated in C++ failing to open in Python #596
-
Hello, I am creating an NDArray of shape System information:
Python code: import blosc2
a = blosc2.open("test_image_dataset.b2nd", "r")
a[0, ...] Python error: File ...python3.10/site-packages/blosc2/ndarray.py:120, in NDArray.__getitem__(self, key)
115 # arr = np.zeros(shape, dtype=self.dtype)
116 # Benchmarking shows that empty is faster than zeros
117 # (besides we don't need to fill padding with zeros)
118 arr = np.empty(shape, dtype=self.dtype)
--> 120 return super().get_slice_numpy(arr, key)
File blosc2_ext.pyx:2048, in blosc2.blosc2_ext.NDArray.get_slice_numpy()
File blosc2_ext.pyx:1911, in blosc2.blosc2_ext._check_rc()
RuntimeError: Error while getting the buffer Sample code: #include <xiApi.h>
#include <gtest/gtest.h>
#include <blosc2.h>
#include <b2nd.h>
#define HandleBLOSCResult(res, place) \
if (res != 0) { \
std::stringstream errormsg; \
errormsg << "Error after " << place << " " << res << "\n"; \
throw std::runtime_error(errormsg.str()); \
}
TEST(BLOSC, BloscAppend) {
blosc2_init();
auto* image = new XI_IMG;
int width = 4 * 512;
int height = 4 * 272;
image->width = width;
image->height = height;
image->bp = new unsigned char[image->width * image->height * 2]();
int N_images = 10;
char *urlpath = strdup("test_image_dataset.b2nd");
blosc2_remove_urlpath(urlpath);
blosc2_cparams cparams = BLOSC2_CPARAMS_DEFAULTS;
cparams.typesize = sizeof(UINT16);
cparams.compcode = BLOSC_ZSTD;
cparams.filters[BLOSC2_MAX_FILTERS - 2] = BLOSC_BITSHUFFLE;
cparams.filters[BLOSC2_MAX_FILTERS - 1] = BLOSC_SHUFFLE;
cparams.clevel = 5;
cparams.nthreads = 4;
blosc2_storage storage = BLOSC2_STORAGE_DEFAULTS;
storage.contiguous = true;
storage.cparams = &cparams;
storage.urlpath = urlpath;
// Shape of the ndarray
int64_t shape[] = {N_images, image->height, image->width};
// Set Chunk shape and BlockShape as per your requirement.
int32_t chunk_shape[] = {1, height, width};
int32_t block_shape[] = {1, height, width};
b2nd_context_t *ctx = b2nd_create_ctx(&storage, 3, shape, chunk_shape, block_shape, NULL, 0, NULL, 0);
blosc2_storage storage_single_image = BLOSC2_STORAGE_DEFAULTS;
storage_single_image.cparams = &cparams;
b2nd_context_t *ctx_single_image = b2nd_create_ctx(&storage_single_image, 3, shape, chunk_shape, block_shape, NULL, 0, NULL, 0);
// loop through all images
for(int i = 0; i < N_images; i++) {
printf("Saving image #: %d\n", i);
// Generate random image data
for(int j = 0; j < image->width * image->height; j++){
reinterpret_cast<uint16_t*>(image->bp)[j] = rand() % 65536; // generate random pixels
}
// Determine the buffer size (in bytes)
const int64_t buffer_size = image->width * image->height * sizeof(uint16_t);
b2nd_array_t *src;
int result;
if (i==0) {
result = b2nd_full(ctx, &src, image->bp);
HandleBLOSCResult(result, "b2nd_full");
} else {
result = b2nd_open(urlpath, &src);
HandleBLOSCResult(result, "b2nd_append");
result = b2nd_append(src, image->bp, buffer_size, 0);
HandleBLOSCResult(result, "b2nd_append");
}
result = b2nd_free(src);
HandleBLOSCResult(result, "b2nd_free");
}
b2nd_free_ctx(ctx);
b2nd_free_ctx(ctx_single_image);
// blosc2_remove_urlpath(urlpath);
blosc2_destroy();
delete[] static_cast<unsigned char*>(image->bp);
delete image;
} |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 10 replies
-
Hi Leonardo. Fortunately, there is a solution that is simpler than your attempts. First you need to create the b2nd array, with nothing inside ( I did a small example showing these steps in: https://github.com/Blosc/c-blosc2/blob/main/examples/b2nd/example_stack_images.c The metadata for the output can be read normally with msgpack tools: francesc@Francescs-MacBook-Air ~/b/c-blosc2 (main)> ll test_image_dataset.b2nd (python-blosc2)
-rw-r--r-- 1 francesc staff 43M Mar 29 16:56 test_image_dataset.b2nd
francesc@Francescs-MacBook-Air ~/b/c-blosc2 (main)> msgpack2json -Bi test_image_dataset.b2nd (python-blosc2)
["b2frame\u0000",184,44565103,"\u0012\u0000P\u0003",44564480,44564800,2,4456448,4456448,4,1,false,"ext:6:base64:AAAAAAABAAAAAAAAAAAAAA==",[17,{"b2nd":107},["lwADk9MAAAAAAAAACtMAAAAAAAAEQNMAAAAAAAAIAJPSAAAAAdIAAARA0gAACACT0gAAAAHSAAAEQNIAAAgAANsAAAADfHUy"]]]⏎
francesc@Francescs-MacBook-Air ~/b/c-blosc2 (main)> dd bs=1 skip=112 count=1000 < test_image_dataset.b2nd | msgpack2json -B
1000+0 records in
1000+0 records out
1000 bytes transferred in 0.003125 secs (320000 bytes/sec)
[0,3,[10,1088,2048],[1,1088,2048],[1,1088,2048],0,"|u2"] Or better yet, using the Python wrappers:
As you can see, the size of the file is 43 MB, so actual compression has not been possible, but that's because we have been using random() values in the example. I hope that, for real images, you can get significant compression ratios by using different combinations of lossless codecs and filters inside Blosc2. If that is not the case, Blosc2 recently has got support for a relatively rich variety of lossy codecs and filters; these should be useful for images, most specially JPEG2000 support. |
Beta Was this translation helpful? Give feedback.
-
Yes. B2ND arrays can be resized (both enlarged and shrinked) easily with b2nd_resize. |
Beta Was this translation helpful? Give feedback.
-
While trying to do the same with |
Beta Was this translation helpful? Give feedback.
-
In case someone else searches for this in the future. This is how I am appending the metadata at the moment. Dependency: sudo apt install libmsgpack-dev Exemplary use: PackAndAppendMetadata(src, "exposure_us", static_cast<int>(17));
PackAndAppendMetadata(src, "acq_nframe", static_cast<std::string>("sampleString")); PackAndAppendMetadata: #include <blosc2.h>
#include <b2nd.h>
#include <msgpack.hpp>
template<typename T>
void PackAndAppendMetadata(b2nd_array_t *src, const char *key, const T& data) {
// create an array and pack it.
std::vector<T> dataArray = {data};
msgpack::sbuffer sbuf;
msgpack::pack(sbuf, dataArray);
AppendBLOSCVLMetadata(src, key, sbuf);
}
void AppendBLOSCVLMetadata(b2nd_array_t *src, const char *key, msgpack::sbuffer &newData){
// Get the existing data
uint8_t *content = nullptr;
int32_t content_len;
int result;
int metadataExists = blosc2_vlmeta_exists(src->sc, key);
if (metadataExists < 0){
result = blosc2_vlmeta_add(src->sc, key,
reinterpret_cast<uint8_t *>(newData.data()), newData.size(),
nullptr);
if (result < 0){
throw std::runtime_error("Error when using blosc2_vlmeta_add");
}
return;
} else {
result = blosc2_vlmeta_get(src->sc, key, &content, &content_len);
if (result < 0){
throw std::runtime_error("Error when using blosc2_vlmeta_get");
}
// Unpack the existing data
msgpack::unpacker oldpac;
oldpac.reserve_buffer(content_len);
memcpy(oldpac.buffer(), content, content_len);
oldpac.buffer_consumed(content_len);
// Unpack the new data
msgpack::unpacker newpac;
newpac.reserve_buffer(newData.size());
memcpy(newpac.buffer(), newData.data(), newData.size());
newpac.buffer_consumed(newData.size());
msgpack::object_handle oldoh;
oldpac.next(oldoh);
msgpack::object_handle newoh;
newpac.next(newoh);
msgpack::sbuffer sbuf;
// Assume that the existing and new data should have the same type
if (oldoh.get().type == msgpack::type::ARRAY && oldoh.get().via.array.size > 0 && oldoh.get().via.array.ptr[0].type == msgpack::type::STR) {
// It's a vector of strings
auto oldData = oldoh.get().as<std::vector<std::string>>();
auto appendData = newoh.get().as<std::vector<std::string>>();
oldData.insert(oldData.end(), appendData.begin(), appendData.end());
msgpack::pack(sbuf, oldData);
}
else {
// It's a vector of ints
auto oldData = oldoh.get().as<std::vector<int>>();
auto appendData = newoh.get().as<std::vector<int>>();
oldData.insert(oldData.end(), appendData.begin(), appendData.end());
msgpack::pack(sbuf, oldData);
}
// Update the metadata with the new data
result = blosc2_vlmeta_update(src->sc, key, reinterpret_cast<uint8_t *>(sbuf.data()), sbuf.size(), NULL);
if (result < 0){
throw std::runtime_error("Error when using blosc2_vlmeta_update");
}
}
} |
Beta Was this translation helpful? Give feedback.
-
It is worth mentioning that updating vlmeta does not need the new data to be of the same length than the previous one. This is one of the niceties of vlmeta (as compared with metalayers in the header). |
Beta Was this translation helpful? Give feedback.
Hi Leonardo. Fortunately, there is a solution that is simpler than your attempts. First you need to create the b2nd array, with nothing inside (
b2nd_empty()
), then set the images one by one (withb2nd_set_slice_cbuffer()
).I did a small example showing these steps in: https://github.com/Blosc/c-blosc2/blob/main/examples/b2nd/example_stack_images.c
The metadata for the output can be read normally with msgpack tools: