Skip to content

Commit

Permalink
Expose data type of CuImage object for interoperability with NumPy (#246
Browse files Browse the repository at this point in the history
)

- Expose typestr property on CuImage object
- Expose DLDataType and DLDataTypeCode under cucim.clara

Without this patch,
DLDataType and DLDataTypecode can be accessible with the below workaround.

```python
>>> from cucim import CuImage
>>> a = CuImage("notebooks/input/image.tif")
>>> b = a.read_region((0,0), (10,10))
>>> import numpy as np
>>> np.dtype(b.__array_interface__["typestr"]) # b would expose `__cuda_array_interface__` if memory is in GPU.
dtype('uint8')
```

With this patch, we can convert data type to NumPy's dtype easily, and also can access cuCIM's DLDataType.

```python
>>> from cucim import CuImage
>>> a = CuImage("notebooks/input/image.tif")
>>> b = a.read_region((0,0), (10,10))
>>> import numpy as np
>>> b.typestr
'|u1'
>>> np.dtype(b.typestr) == np.uint8
True
>>> from cucim.clara import DLDataType, DLDataTypeCode
>>> b.dtype == DLDataType(DLDataTypeCode.DLUInt, 8, 1)
True
```

Fixes #243

Authors:
  - Gigon Bae (https://github.com/gigony)

Approvers:
  - https://github.com/jakirkham

URL: #246
  • Loading branch information
gigony authored Mar 31, 2022
1 parent 3292d89 commit 8fd6881
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 76 deletions.
12 changes: 11 additions & 1 deletion cpp/include/cucim/cuimage.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2021, NVIDIA CORPORATION.
* Copyright (c) 2020-2022, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -37,6 +37,14 @@
#include <string>
#include <vector>


// Forward declarations for DLDataType's equality operator.
template <>
struct std::hash<DLDataType>;

EXPORT_VISIBLE bool operator==(const DLDataType& lhs, const DLDataType& rhs);
EXPORT_VISIBLE bool operator!=(const DLDataType& lhs, const DLDataType& rhs);

namespace cucim
{

Expand Down Expand Up @@ -148,6 +156,8 @@ class EXPORT_VISIBLE CuImage : public std::enable_shared_from_this<CuImage>

DLDataType dtype() const;

std::string typestr() const;

std::vector<std::string> channel_names() const;

std::vector<float> spacing(std::string dim_order = std::string{}) const;
Expand Down
114 changes: 68 additions & 46 deletions cpp/include/cucim/memory/dlpack.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, NVIDIA CORPORATION.
* Copyright (c) 2020-2022, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,6 +23,60 @@
namespace cucim::memory
{

/**
* @brief Return a string providing the basic type of the homogenous array in NumPy.
*
* Note: This method assumes little-endian for now.
*
* @return A const character pointer that represents a string from a given `DLDataType` data.
*/
inline const char* to_numpy_dtype(const DLDataType& dtype) {
// TODO: consider bfloat16: https://github.com/dmlc/dlpack/issues/45
// TODO: consider other byte-order
uint8_t code = dtype.code;
uint8_t bits = dtype.bits;
switch(code) {

case kDLInt:
switch(bits) {
case 8:
return "|i1";
case 16:
return "<i2";
case 32:
return "<i4";
case 64:
return "<i8";
}
throw std::logic_error(fmt::format("DLDataType(code: kDLInt, bits: {}) is not supported!", bits));
case kDLUInt:
switch(bits) {
case 8:
return "|u1";
case 16:
return "<u2";
case 32:
return "<u4";
case 64:
return "<u8";
}
throw std::logic_error(fmt::format("DLDataType(code: kDLUInt, bits: {}) is not supported!", bits));
case kDLFloat:
switch(bits) {
case 16:
return "<f2";
case 32:
return "<f4";
case 64:
return "<f8";
}
break;
case kDLBfloat:
throw std::logic_error(fmt::format("DLDataType(code: kDLBfloat, bits: {}) is not supported!", bits));
}
throw std::logic_error(fmt::format("DLDataType(code: {}, bits: {}) is not supported!", code, bits));
}

class DLTContainer
{
public:
Expand All @@ -36,7 +90,7 @@ class DLTContainer
*
* @return size_t Required size for the tensor.
*/
size_t size()
size_t size() const
{
size_t size = 1;
for (int i = 0; i < tensor_->ndim; ++i)
Expand All @@ -47,63 +101,31 @@ class DLTContainer
return size;
}

DLDataType dtype() const {
if (!tensor_) {
return DLDataType({ DLDataTypeCode::kDLUInt, 8, 1 });
}
return tensor_->dtype;
}
/**
* @brief Return a string providing the basic type of the homogenous array in NumPy.
*
* Note: This method assumes little-endian for now.
*
* @return A const character pointer that represents a string
*/
const char* numpy_dtype() {
const char* numpy_dtype() const {
// TODO: consider bfloat16: https://github.com/dmlc/dlpack/issues/45
// TODO: consider other byte-order
if (!tensor_) {
return "";
}
return to_numpy_dtype(tensor_->dtype);
}

const DLDataType& dtype = tensor_->dtype;
uint8_t code = dtype.code;
uint8_t bits = dtype.bits;
switch(code) {

case kDLInt:
switch(bits) {
case 8:
return "|i1";
case 16:
return "<i2";
case 32:
return "<i4";
case 64:
return "<i8";
}
throw std::logic_error(fmt::format("DLDataType(code: kDLInt, bits: {}) is not supported!", bits));
case kDLUInt:
switch(bits) {
case 8:
return "|u1";
case 16:
return "<u2";
case 32:
return "<u4";
case 64:
return "<u8";
}
throw std::logic_error(fmt::format("DLDataType(code: kDLUInt, bits: {}) is not supported!", bits));
case kDLFloat:
switch(bits) {
case 16:
return "<f2";
case 32:
return "<f4";
case 64:
return "<f8";
}
break;
case kDLBfloat:
throw std::logic_error(fmt::format("DLDataType(code: kDLBfloat, bits: {}) is not supported!", bits));
}
throw std::logic_error(fmt::format("DLDataType(code: {}, bits: {}) is not supported!", code, bits));
operator bool() const
{
return static_cast<bool>(tensor_);
}

operator DLTensor() const
Expand Down
18 changes: 12 additions & 6 deletions cpp/plugins/cucim.kit.cuslide/benchmarks/main.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2021, NVIDIA CORPORATION.
* Copyright (c) 2020-2022, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -85,10 +85,18 @@ static void test_basic(benchmark::State& state)
return;
}

auto handle = image_format->formats[0].image_parser.open(input_path.c_str());
std::string input_path = g_config.get_input_path();
std::shared_ptr<CuCIMFileHandle>* file_handle_shared = reinterpret_cast<std::shared_ptr<CuCIMFileHandle>*>(
image_format->formats[0].image_parser.open(input_path.c_str()));

std::shared_ptr<CuCIMFileHandle> file_handle = *file_handle_shared;
delete file_handle_shared;

// Set deleter to close the file handle
file_handle->set_deleter(image_format->formats[0].image_parser.close);

cucim::io::format::ImageMetadata metadata{};
image_format->formats[0].image_parser.parse(&handle, &metadata.desc());
image_format->formats[0].image_parser.parse(file_handle.get(), &metadata.desc());

cucim::io::format::ImageReaderRegionRequestDesc request{};
int64_t request_location[2] = { 0, 0 };
Expand All @@ -107,11 +115,9 @@ static void test_basic(benchmark::State& state)
cucim::io::format::ImageDataDesc image_data;

image_format->formats[0].image_reader.read(
&handle, &metadata.desc(), &request, &image_data, nullptr /*out_metadata*/);
file_handle.get(), &metadata.desc(), &request, &image_data, nullptr /*out_metadata*/);
cucim_free(image_data.container.data);

image_format->formats[0].image_parser.close(&handle);

// auto end = std::chrono::high_resolution_clock::now();
// auto elapsed_seconds = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);
// state.SetIterationTime(elapsed_seconds.count());
Expand Down
54 changes: 52 additions & 2 deletions cpp/src/cuimage.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2021, NVIDIA CORPORATION.
* Copyright (c) 2020-2022, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -34,6 +34,27 @@
#define XSTR(x) STR(x)
#define STR(x) #x


// DLDataType's equality operator implementation
template <>
struct std::hash<DLDataType>
{
size_t operator()(const DLDataType& dtype) const
{
return (dtype.code * 1117) ^ (dtype.bits * 31) ^ (dtype.lanes);
}
};

bool operator==(const DLDataType& lhs, const DLDataType& rhs)
{
return (lhs.code == rhs.code) && (lhs.bits == rhs.bits) && (lhs.lanes == rhs.lanes);
}

bool operator!=(const DLDataType& lhs, const DLDataType& rhs)
{
return (lhs.code != rhs.code) || (lhs.bits != rhs.bits) || (lhs.lanes != rhs.lanes);
}

namespace cucim
{

Expand Down Expand Up @@ -443,9 +464,38 @@ std::vector<int64_t> CuImage::size(std::string dim_order) const
}
DLDataType CuImage::dtype() const
{
// TODO: support string conversion like Device class
const memory::DLTContainer img_data = container();
if (img_data)
{
const DLDataType dtype = img_data.dtype();
return dtype;
}
else
{
if (image_metadata_)
{
return image_metadata_->dtype;
}
}
return DLDataType({ DLDataTypeCode::kDLUInt, 8, 1 });
}
std::string CuImage::typestr() const
{
const memory::DLTContainer img_data = container();
if (img_data)
{
const char* type_str = img_data.numpy_dtype();
return std::string(type_str);
}
else
{
if (image_metadata_)
{
return std::string(memory::to_numpy_dtype(image_metadata_->dtype));
}
}
return "|u1";
}
std::vector<std::string> CuImage::channel_names() const
{
std::vector<std::string> channel_names;
Expand Down
26 changes: 18 additions & 8 deletions cpp/tests/test_metadata.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, NVIDIA CORPORATION.
* Copyright (c) 2020-2022, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -74,26 +74,31 @@ TEST_CASE("Verify metadata", "[test_metadata.cpp]")
{
cucim::Framework* framework = cucim::acquire_framework("sample.app");
REQUIRE(framework != nullptr);

std::string plugin_path = g_config.get_plugin_path();
cucim::io::format::IImageFormat* image_format =
framework->acquire_interface_from_library<cucim::io::format::IImageFormat>(g_config.get_plugin_path().c_str());
framework->acquire_interface_from_library<cucim::io::format::IImageFormat>(plugin_path.c_str());
// fmt::print("{}\n", image_format->formats[0].get_format_name());
REQUIRE(image_format != nullptr);

auto handle =
image_format->formats[0].image_parser.open(g_config.get_input_path("private/philips_tiff_000.tif").c_str());
std::string input_path = g_config.get_input_path();
std::shared_ptr<CuCIMFileHandle>* file_handle_shared = reinterpret_cast<std::shared_ptr<CuCIMFileHandle>*>(
image_format->formats[0].image_parser.open(input_path.c_str()));

std::shared_ptr<CuCIMFileHandle> file_handle = *file_handle_shared;
delete file_handle_shared;

// Set deleter to close the file handle
file_handle->set_deleter(image_format->formats[0].image_parser.close);

cucim::io::format::ImageMetadata metadata{};
image_format->formats[0].image_parser.parse(&handle, &metadata.desc());
image_format->formats[0].image_parser.parse(file_handle.get(), &metadata.desc());

// Using fmt::print() has a problem with TestMate VSCode plugin (output is not caught by the plugin)
std::cout << fmt::format("metadata: {}\n", metadata.desc().raw_data);
const uint8_t* buf = metadata.get_buffer();
const uint8_t* buf2 = static_cast<uint8_t*>(metadata.allocate(1));
std::cout << fmt::format("test: {}\n", buf2 - buf);

image_format->formats[0].image_parser.close(&handle);

// cucim::CuImage img{ g_config.get_input_path("private/philips_tiff_000.tif") };
// const auto& img_metadata = img.metadata();
// std::cout << fmt::format("metadata: {}\n", img_metadata);
Expand Down Expand Up @@ -131,8 +136,13 @@ TEST_CASE("Verify metadata", "[test_metadata.cpp]")
TEST_CASE("Load test", "[test_metadata.cpp]")
{
cucim::CuImage img{ g_config.get_input_path("private/philips_tiff_000.tif") };
REQUIRE(img.dtype() == DLDataType{ DLDataTypeCode::kDLUInt, 8, 1 });
REQUIRE(img.typestr() == "|u1");

auto test = img.read_region({ -10, -10 }, { 100, 100 });

REQUIRE(test.dtype() == DLDataType{ DLDataTypeCode::kDLUInt, 8, 1 });
REQUIRE(test.typestr() == "|u1");

fmt::print("{}", img.metadata());
}
Loading

0 comments on commit 8fd6881

Please sign in to comment.