Skip to content

Commit ad4e7c3

Browse files
authored
Add support for getting DICOM tag that is a sequence (#997)
This PR tries to address the issue raised 779 where it was not possible to get DICOM tag that is a sequence. This PR adds string tensor as an input, so that the following: ``` [0x0008,0x1115][0][0x0008,0x1140][0][0x0008,0x1155] ``` coudl be used to specify: ``` [sequence of 0x0008,0x1115] => [item 0 of the sequence] => [sequence of 0x0008,0x1140 in the item] => [item 0 of the sequence] => [value of 0x0008,0x1155 in the item] ``` This PR fixes 779 Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
1 parent fb1282d commit ad4e7c3

File tree

5 files changed

+4215
-4035
lines changed

5 files changed

+4215
-4035
lines changed

tensorflow_io/core/kernels/image_dicom_kernels.cc

Lines changed: 113 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ limitations under the License.
4747
#include "tensorflow/core/framework/op_kernel.h"
4848
#include "tensorflow/core/framework/shape_inference.h"
4949
#include "tensorflow/core/framework/types.h"
50+
#include "absl/strings/str_split.h"
51+
#include "absl/strings/numbers.h"
5052

5153
// clang-format on
5254

@@ -319,7 +321,6 @@ class DecodeDICOMDataOp : public OpKernel {
319321

320322
const Tensor *in_tags;
321323
OP_REQUIRES_OK(context, context->input("tags", &in_tags));
322-
auto in_tags_flat = in_tags->flat<uint32>();
323324

324325
// Create an output tensor
325326
Tensor *out_tag_values = NULL;
@@ -337,26 +338,128 @@ class DecodeDICOMDataOp : public OpKernel {
337338
OFCondition cond = dfile.read(dataBuf);
338339
dfile.transferEnd();
339340

340-
DcmDataset *dset = dfile.getDataset();
341+
DcmItem *item = static_cast<DcmItem *>(dfile.getDataset());
341342
DcmMetaInfo *meta = dfile.getMetaInfo();
342343

343-
for (unsigned int tag_i = 0; tag_i < in_tags_flat.size(); ++tag_i) {
344-
uint32 tag_value = in_tags_flat(tag_i);
345-
uint16 tag_group_number = (uint16)((tag_value & 0xFFFF0000) >> 16);
346-
uint16 tag_element_number = (uint16)((tag_value & 0x0000FFFF) >> 0);
347-
DcmTag tag(tag_group_number, tag_element_number);
344+
for (int64 tag_i = 0; tag_i < in_tags->NumElements(); ++tag_i) {
345+
DcmTag tag;
346+
if (in_tags->dtype() == DT_STRING) {
347+
uint16 tag_group_number, tag_element_number;
348+
const auto &in_tags_flat = in_tags->flat<tstring>();
349+
const tstring &tag_sequence = in_tags_flat(tag_i);
350+
std::vector<absl::string_view> tag_sequence_views;
351+
if (tag_sequence[0] == '[' &&
352+
tag_sequence[tag_sequence.size() - 1] == ']') {
353+
tag_sequence_views =
354+
absl::StrSplit(absl::string_view(tag_sequence.data() + 1,
355+
tag_sequence.size() - 2),
356+
"][");
357+
} else {
358+
tag_sequence_views.push_back(
359+
absl::string_view(tag_sequence.data(), tag_sequence.size()));
360+
}
361+
362+
OP_REQUIRES(
363+
context, (tag_sequence_views.size() % 2 == 1),
364+
errors::InvalidArgument(
365+
"tag sequences should have 2xn + 1 elements, received: ",
366+
tag_sequence_views.size()));
367+
368+
// Walk through before the last element of value
369+
for (size_t i = 0; i < tag_sequence_views.size() - 1; i += 2) {
370+
absl::string_view tag_value(tag_sequence_views[i].data(),
371+
tag_sequence_views[i].size());
372+
OP_REQUIRES_OK(context, GetDcmTag(tag_value, &tag));
373+
374+
absl::string_view number_value(tag_sequence_views[i + 1].data(),
375+
tag_sequence_views[i + 1].size());
376+
uint32 number = 0;
377+
OP_REQUIRES(
378+
context,
379+
absl::numbers_internal::safe_strtou32_base(number_value, &number,
380+
0),
381+
errors::InvalidArgument("number should be an integer, received ",
382+
number_value));
383+
384+
DcmItem *lookup;
385+
OFCondition condition =
386+
item->findAndGetSequenceItem(tag, lookup, number);
387+
OP_REQUIRES(context, condition.good(),
388+
errors::InvalidArgument("item findAndGetSequenceItem: ",
389+
condition.text()));
390+
item = lookup;
391+
}
392+
393+
// The last element of value
394+
absl::string_view tag_value(tag_sequence_views.back().data(),
395+
tag_sequence_views.back().size());
396+
OP_REQUIRES_OK(context, GetDcmTag(tag_value, &tag));
397+
} else {
398+
const auto &in_tags_flat = in_tags->flat<uint32>();
399+
uint32 tag_value = in_tags_flat(tag_i);
400+
OP_REQUIRES_OK(context, GetDcmTag(tag_value, &tag));
401+
}
348402

349403
OFString val;
350-
if (dset->tagExists(tag)) {
351-
dset->findAndGetOFStringArray(tag, val);
404+
if (item->tagExists(tag)) {
405+
OFCondition condition = item->findAndGetOFStringArray(tag, val);
406+
OP_REQUIRES(context, condition.good(),
407+
errors::InvalidArgument("item findAndGetOFStringArray: ",
408+
condition.text()));
352409
} else if (meta->tagExists(tag)) {
353-
meta->findAndGetOFStringArray(tag, val);
410+
OFCondition condition = meta->findAndGetOFStringArray(tag, val);
411+
OP_REQUIRES(context, condition.good(),
412+
errors::InvalidArgument("meta findAndGetOFStringArray: ",
413+
condition.text()));
354414
} else {
355415
val = OFString("");
356416
}
357417
out_tag_values_flat(tag_i) = val.c_str();
358418
}
359419
}
420+
421+
private:
422+
Status GetDcmTag(const uint32 tag_value, DcmTag *tag) {
423+
uint16 tag_group_number = (uint16)((tag_value & 0xFFFF0000) >> 16);
424+
uint16 tag_element_number = (uint16)((tag_value & 0x0000FFFF) >> 0);
425+
*tag = DcmTag(tag_group_number, tag_element_number);
426+
return Status::OK();
427+
}
428+
Status GetDcmTag(const absl::string_view tag_value, DcmTag *tag) {
429+
std::vector<absl::string_view> number_views =
430+
absl::StrSplit(tag_value, ',');
431+
if (number_views.size() != 2) {
432+
return errors::InvalidArgument(
433+
"sequence should consist of group and "
434+
"element numbers, received ",
435+
tag_value);
436+
}
437+
uint32 number = 0;
438+
if (!absl::numbers_internal::safe_strtou32_base(number_views[0], &number,
439+
0)) {
440+
return errors::InvalidArgument(
441+
"group number should be an integer, received ", number_views[0]);
442+
}
443+
if (number > std::numeric_limits<short>::max()) {
444+
return errors::InvalidArgument("group number should be uint16, received ",
445+
number_views[0]);
446+
}
447+
uint16 tag_group_number = number;
448+
449+
if (!absl::numbers_internal::safe_strtou32_base(number_views[1], &number,
450+
0)) {
451+
return errors::InvalidArgument(
452+
"element number should be an integer, received ", number_views[1]);
453+
}
454+
if (number > std::numeric_limits<short>::max()) {
455+
return errors::InvalidArgument(
456+
"element number should be uint16, received ", number_views[1]);
457+
}
458+
uint16 tag_element_number = number;
459+
460+
*tag = DcmTag(tag_group_number, tag_element_number);
461+
return Status::OK();
462+
}
360463
};
361464

362465
// Register the CPU kernels.

tensorflow_io/core/ops/image_ops.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ loads a dicom image file and returns its pixel information in the specified outp
162162

163163
REGISTER_OP("IO>DecodeDICOMData")
164164
.Input("contents: string")
165-
.Input("tags: uint32")
165+
.Input("tags: dtype")
166+
.Attr("dtype: {uint32,string}")
166167
.Output("tag_values: string")
167168
.SetShapeFn([](shape_inference::InferenceContext* c) {
168169
c->set_output(0, c->input(1));

0 commit comments

Comments
 (0)