Skip to content

Commit 8fd6881

Browse files
authored
Expose data type of CuImage object for interoperability with NumPy (#246)
- 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
1 parent 3292d89 commit 8fd6881

File tree

12 files changed

+216
-76
lines changed

12 files changed

+216
-76
lines changed

cpp/include/cucim/cuimage.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020-2021, NVIDIA CORPORATION.
2+
* Copyright (c) 2020-2022, NVIDIA CORPORATION.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,6 +37,14 @@
3737
#include <string>
3838
#include <vector>
3939

40+
41+
// Forward declarations for DLDataType's equality operator.
42+
template <>
43+
struct std::hash<DLDataType>;
44+
45+
EXPORT_VISIBLE bool operator==(const DLDataType& lhs, const DLDataType& rhs);
46+
EXPORT_VISIBLE bool operator!=(const DLDataType& lhs, const DLDataType& rhs);
47+
4048
namespace cucim
4149
{
4250

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

149157
DLDataType dtype() const;
150158

159+
std::string typestr() const;
160+
151161
std::vector<std::string> channel_names() const;
152162

153163
std::vector<float> spacing(std::string dim_order = std::string{}) const;

cpp/include/cucim/memory/dlpack.h

Lines changed: 68 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, NVIDIA CORPORATION.
2+
* Copyright (c) 2020-2022, NVIDIA CORPORATION.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,60 @@
2323
namespace cucim::memory
2424
{
2525

26+
/**
27+
* @brief Return a string providing the basic type of the homogenous array in NumPy.
28+
*
29+
* Note: This method assumes little-endian for now.
30+
*
31+
* @return A const character pointer that represents a string from a given `DLDataType` data.
32+
*/
33+
inline const char* to_numpy_dtype(const DLDataType& dtype) {
34+
// TODO: consider bfloat16: https://github.com/dmlc/dlpack/issues/45
35+
// TODO: consider other byte-order
36+
uint8_t code = dtype.code;
37+
uint8_t bits = dtype.bits;
38+
switch(code) {
39+
40+
case kDLInt:
41+
switch(bits) {
42+
case 8:
43+
return "|i1";
44+
case 16:
45+
return "<i2";
46+
case 32:
47+
return "<i4";
48+
case 64:
49+
return "<i8";
50+
}
51+
throw std::logic_error(fmt::format("DLDataType(code: kDLInt, bits: {}) is not supported!", bits));
52+
case kDLUInt:
53+
switch(bits) {
54+
case 8:
55+
return "|u1";
56+
case 16:
57+
return "<u2";
58+
case 32:
59+
return "<u4";
60+
case 64:
61+
return "<u8";
62+
}
63+
throw std::logic_error(fmt::format("DLDataType(code: kDLUInt, bits: {}) is not supported!", bits));
64+
case kDLFloat:
65+
switch(bits) {
66+
case 16:
67+
return "<f2";
68+
case 32:
69+
return "<f4";
70+
case 64:
71+
return "<f8";
72+
}
73+
break;
74+
case kDLBfloat:
75+
throw std::logic_error(fmt::format("DLDataType(code: kDLBfloat, bits: {}) is not supported!", bits));
76+
}
77+
throw std::logic_error(fmt::format("DLDataType(code: {}, bits: {}) is not supported!", code, bits));
78+
}
79+
2680
class DLTContainer
2781
{
2882
public:
@@ -36,7 +90,7 @@ class DLTContainer
3690
*
3791
* @return size_t Required size for the tensor.
3892
*/
39-
size_t size()
93+
size_t size() const
4094
{
4195
size_t size = 1;
4296
for (int i = 0; i < tensor_->ndim; ++i)
@@ -47,63 +101,31 @@ class DLTContainer
47101
return size;
48102
}
49103

104+
DLDataType dtype() const {
105+
if (!tensor_) {
106+
return DLDataType({ DLDataTypeCode::kDLUInt, 8, 1 });
107+
}
108+
return tensor_->dtype;
109+
}
50110
/**
51111
* @brief Return a string providing the basic type of the homogenous array in NumPy.
52112
*
53113
* Note: This method assumes little-endian for now.
54114
*
55115
* @return A const character pointer that represents a string
56116
*/
57-
const char* numpy_dtype() {
117+
const char* numpy_dtype() const {
58118
// TODO: consider bfloat16: https://github.com/dmlc/dlpack/issues/45
59119
// TODO: consider other byte-order
60120
if (!tensor_) {
61121
return "";
62122
}
123+
return to_numpy_dtype(tensor_->dtype);
124+
}
63125

64-
const DLDataType& dtype = tensor_->dtype;
65-
uint8_t code = dtype.code;
66-
uint8_t bits = dtype.bits;
67-
switch(code) {
68-
69-
case kDLInt:
70-
switch(bits) {
71-
case 8:
72-
return "|i1";
73-
case 16:
74-
return "<i2";
75-
case 32:
76-
return "<i4";
77-
case 64:
78-
return "<i8";
79-
}
80-
throw std::logic_error(fmt::format("DLDataType(code: kDLInt, bits: {}) is not supported!", bits));
81-
case kDLUInt:
82-
switch(bits) {
83-
case 8:
84-
return "|u1";
85-
case 16:
86-
return "<u2";
87-
case 32:
88-
return "<u4";
89-
case 64:
90-
return "<u8";
91-
}
92-
throw std::logic_error(fmt::format("DLDataType(code: kDLUInt, bits: {}) is not supported!", bits));
93-
case kDLFloat:
94-
switch(bits) {
95-
case 16:
96-
return "<f2";
97-
case 32:
98-
return "<f4";
99-
case 64:
100-
return "<f8";
101-
}
102-
break;
103-
case kDLBfloat:
104-
throw std::logic_error(fmt::format("DLDataType(code: kDLBfloat, bits: {}) is not supported!", bits));
105-
}
106-
throw std::logic_error(fmt::format("DLDataType(code: {}, bits: {}) is not supported!", code, bits));
126+
operator bool() const
127+
{
128+
return static_cast<bool>(tensor_);
107129
}
108130

109131
operator DLTensor() const

cpp/plugins/cucim.kit.cuslide/benchmarks/main.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020-2021, NVIDIA CORPORATION.
2+
* Copyright (c) 2020-2022, NVIDIA CORPORATION.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -85,10 +85,18 @@ static void test_basic(benchmark::State& state)
8585
return;
8686
}
8787

88-
auto handle = image_format->formats[0].image_parser.open(input_path.c_str());
88+
std::string input_path = g_config.get_input_path();
89+
std::shared_ptr<CuCIMFileHandle>* file_handle_shared = reinterpret_cast<std::shared_ptr<CuCIMFileHandle>*>(
90+
image_format->formats[0].image_parser.open(input_path.c_str()));
91+
92+
std::shared_ptr<CuCIMFileHandle> file_handle = *file_handle_shared;
93+
delete file_handle_shared;
94+
95+
// Set deleter to close the file handle
96+
file_handle->set_deleter(image_format->formats[0].image_parser.close);
8997

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

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

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

113-
image_format->formats[0].image_parser.close(&handle);
114-
115121
// auto end = std::chrono::high_resolution_clock::now();
116122
// auto elapsed_seconds = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);
117123
// state.SetIterationTime(elapsed_seconds.count());

cpp/src/cuimage.cpp

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020-2021, NVIDIA CORPORATION.
2+
* Copyright (c) 2020-2022, NVIDIA CORPORATION.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,6 +34,27 @@
3434
#define XSTR(x) STR(x)
3535
#define STR(x) #x
3636

37+
38+
// DLDataType's equality operator implementation
39+
template <>
40+
struct std::hash<DLDataType>
41+
{
42+
size_t operator()(const DLDataType& dtype) const
43+
{
44+
return (dtype.code * 1117) ^ (dtype.bits * 31) ^ (dtype.lanes);
45+
}
46+
};
47+
48+
bool operator==(const DLDataType& lhs, const DLDataType& rhs)
49+
{
50+
return (lhs.code == rhs.code) && (lhs.bits == rhs.bits) && (lhs.lanes == rhs.lanes);
51+
}
52+
53+
bool operator!=(const DLDataType& lhs, const DLDataType& rhs)
54+
{
55+
return (lhs.code != rhs.code) || (lhs.bits != rhs.bits) || (lhs.lanes != rhs.lanes);
56+
}
57+
3758
namespace cucim
3859
{
3960

@@ -443,9 +464,38 @@ std::vector<int64_t> CuImage::size(std::string dim_order) const
443464
}
444465
DLDataType CuImage::dtype() const
445466
{
446-
// TODO: support string conversion like Device class
467+
const memory::DLTContainer img_data = container();
468+
if (img_data)
469+
{
470+
const DLDataType dtype = img_data.dtype();
471+
return dtype;
472+
}
473+
else
474+
{
475+
if (image_metadata_)
476+
{
477+
return image_metadata_->dtype;
478+
}
479+
}
447480
return DLDataType({ DLDataTypeCode::kDLUInt, 8, 1 });
448481
}
482+
std::string CuImage::typestr() const
483+
{
484+
const memory::DLTContainer img_data = container();
485+
if (img_data)
486+
{
487+
const char* type_str = img_data.numpy_dtype();
488+
return std::string(type_str);
489+
}
490+
else
491+
{
492+
if (image_metadata_)
493+
{
494+
return std::string(memory::to_numpy_dtype(image_metadata_->dtype));
495+
}
496+
}
497+
return "|u1";
498+
}
449499
std::vector<std::string> CuImage::channel_names() const
450500
{
451501
std::vector<std::string> channel_names;

cpp/tests/test_metadata.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, NVIDIA CORPORATION.
2+
* Copyright (c) 2020-2022, NVIDIA CORPORATION.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -74,26 +74,31 @@ TEST_CASE("Verify metadata", "[test_metadata.cpp]")
7474
{
7575
cucim::Framework* framework = cucim::acquire_framework("sample.app");
7676
REQUIRE(framework != nullptr);
77-
77+
std::string plugin_path = g_config.get_plugin_path();
7878
cucim::io::format::IImageFormat* image_format =
79-
framework->acquire_interface_from_library<cucim::io::format::IImageFormat>(g_config.get_plugin_path().c_str());
79+
framework->acquire_interface_from_library<cucim::io::format::IImageFormat>(plugin_path.c_str());
8080
// fmt::print("{}\n", image_format->formats[0].get_format_name());
8181
REQUIRE(image_format != nullptr);
8282

83-
auto handle =
84-
image_format->formats[0].image_parser.open(g_config.get_input_path("private/philips_tiff_000.tif").c_str());
83+
std::string input_path = g_config.get_input_path();
84+
std::shared_ptr<CuCIMFileHandle>* file_handle_shared = reinterpret_cast<std::shared_ptr<CuCIMFileHandle>*>(
85+
image_format->formats[0].image_parser.open(input_path.c_str()));
86+
87+
std::shared_ptr<CuCIMFileHandle> file_handle = *file_handle_shared;
88+
delete file_handle_shared;
89+
90+
// Set deleter to close the file handle
91+
file_handle->set_deleter(image_format->formats[0].image_parser.close);
8592

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

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

95-
image_format->formats[0].image_parser.close(&handle);
96-
97102
// cucim::CuImage img{ g_config.get_input_path("private/philips_tiff_000.tif") };
98103
// const auto& img_metadata = img.metadata();
99104
// std::cout << fmt::format("metadata: {}\n", img_metadata);
@@ -131,8 +136,13 @@ TEST_CASE("Verify metadata", "[test_metadata.cpp]")
131136
TEST_CASE("Load test", "[test_metadata.cpp]")
132137
{
133138
cucim::CuImage img{ g_config.get_input_path("private/philips_tiff_000.tif") };
139+
REQUIRE(img.dtype() == DLDataType{ DLDataTypeCode::kDLUInt, 8, 1 });
140+
REQUIRE(img.typestr() == "|u1");
134141

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

144+
REQUIRE(test.dtype() == DLDataType{ DLDataTypeCode::kDLUInt, 8, 1 });
145+
REQUIRE(test.typestr() == "|u1");
146+
137147
fmt::print("{}", img.metadata());
138148
}

0 commit comments

Comments
 (0)