Add HDF5 Image file support

This commit is contained in:
Li-Ta Lo 2020-12-25 16:02:08 -07:00
parent d2dd60e4ed
commit da7d6aaf19
7 changed files with 351 additions and 4 deletions

@ -92,6 +92,11 @@ endif()
vtkm_option(VTKm_USE_DOUBLE_PRECISION "Use double precision for floating point calculations" OFF) vtkm_option(VTKm_USE_DOUBLE_PRECISION "Use double precision for floating point calculations" OFF)
vtkm_option(VTKm_USE_64BIT_IDS "Use 64-bit indices." ON) vtkm_option(VTKm_USE_64BIT_IDS "Use 64-bit indices." ON)
vtkm_option(VTKm_ENABLE_HDF5_IO "Enable HDF5 support" OFF)
if (VTKm_ENABLE_HDF5_IO)
find_package(HDF5 COMPONENTS HL)
endif()
# VTK-m will turn on logging by default, but will set the default # VTK-m will turn on logging by default, but will set the default
# logging level to WARN. This option should not be visible by default # logging level to WARN. This option should not be visible by default
# in the GUI, as ERROR and WARN level logging should not interfere # in the GUI, as ERROR and WARN level logging should not interfere

@ -30,17 +30,17 @@ set(headers
VTKStructuredGridReader.h VTKStructuredGridReader.h
VTKStructuredPointsReader.h VTKStructuredPointsReader.h
VTKUnstructuredGridReader.h VTKUnstructuredGridReader.h
) )
set(template_sources set(template_sources
PixelTypes.hxx PixelTypes.hxx
) )
set(sources set(sources
FileUtils.cxx FileUtils.cxx
DecodePNG.cxx DecodePNG.cxx
EncodePNG.cxx EncodePNG.cxx
) )
# TODO: None of these codes actually use a device. Rather, they access ArrayHandle, and we # TODO: None of these codes actually use a device. Rather, they access ArrayHandle, and we
# currently need to ensure that ArrayHandle is correctly compiled for all devices. This is # currently need to ensure that ArrayHandle is correctly compiled for all devices. This is
@ -65,6 +65,17 @@ set(device_sources
VTKUnstructuredGridReader.cxx VTKUnstructuredGridReader.cxx
) )
if (VTKm_ENABLE_HDF5_IO)
set(headers
${headers}
ImageReaderHDF5_IM.h
ImageWriterHDF5_IM.h)
set(device_sources
${device_sources}
ImageReaderHDF5_IM.cxx
ImageWriterHDF5_IM.cxx)
endif ()
vtkm_declare_headers( vtkm_declare_headers(
${headers} ${headers}
${template_sources} ${template_sources}
@ -79,7 +90,9 @@ vtkm_library(
) )
target_link_libraries(vtkm_io PUBLIC vtkm_cont PRIVATE vtkm_lodepng) target_link_libraries(vtkm_io PUBLIC vtkm_cont PRIVATE vtkm_lodepng)
if (VTKm_ENABLE_HDF5_IO)
target_link_libraries(vtkm_io PRIVATE ${HDF5_HL_LIBRARIES})
endif()
add_subdirectory(internal) add_subdirectory(internal)
add_subdirectory(reader) add_subdirectory(reader)
add_subdirectory(writer) add_subdirectory(writer)

@ -0,0 +1,83 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/io/ErrorIO.h>
#include <vtkm/io/ImageReaderHDF5_IM.h>
#include <vtkm/io/PixelTypes.h>
namespace vtkm
{
namespace io
{
ImageReaderHDF5::~ImageReaderHDF5() noexcept = default;
void ImageReaderHDF5::Read()
{
// need to find width, height and pixel type.
auto fileid = H5Fopen(this->FileName.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
if (!H5IMis_image(fileid, "image"))
throw vtkm::io::ErrorIO{ "Not an HDF5 image file" };
hsize_t width, height, nplanes;
hssize_t npals;
char interlace[16];
if (H5IMget_image_info(fileid, "image", &width, &height, &nplanes, interlace, &npals) < 0)
throw vtkm::io ::ErrorIO{ "Can not get image info" };
// We don't use the H5IMread_image() since it only supports 8 bit pixel.
hid_t did;
if ((did = H5Dopen2(fileid, "image", H5P_DEFAULT)) < 0)
throw vtkm::io::ErrorIO{ "Can not open image dataset" };
if (strncmp(interlace, "INTERLACE_PIXEL", 15) != 0)
throw vtkm::io::ErrorIO{ "Unsupported interlace mode" };
std::vector<unsigned char> buffer;
auto type_size = H5LDget_dset_type_size(did, nullptr);
buffer.resize(width * height * 3 * type_size);
switch (type_size)
{
case 1:
H5Dread(did, H5T_NATIVE_UCHAR, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer.data());
break;
case 2:
H5Dread(did, H5T_NATIVE_UINT16, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer.data());
break;
default:
throw vtkm::io::ErrorIO{ "Unsupported pixel type" };
}
H5Dclose(did);
H5Fclose(fileid);
// convert PixelType to Vec4f_32
vtkm::cont::ArrayHandle<vtkm::Vec4f_32> pixelArray;
pixelArray.Allocate(width * height);
auto portal = pixelArray.WritePortal();
vtkm::Id vtkmIndex = 0;
for (vtkm::Id yIndex = 0; yIndex < static_cast<vtkm::Id>(height); yIndex++)
{
for (vtkm::Id xIndex = 0; xIndex < static_cast<vtkm::Id>(width); xIndex++)
{
vtkm::Id hdfIndex = static_cast<vtkm::Id>(yIndex * width + xIndex);
if (type_size == 1)
portal.Set(vtkmIndex, vtkm::io::RGBPixel_8(buffer.data(), hdfIndex).ToVec4f());
else
portal.Set(vtkmIndex, vtkm::io::RGBPixel_16(buffer.data(), hdfIndex).ToVec4f());
vtkmIndex++;
}
}
this->InitializeImageDataSet(width, height, pixelArray);
} // Read()
}
}

@ -0,0 +1,39 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_io_ImageReaderHDF5_IM_h
#define vtk_m_io_ImageReaderHDF5_IM_h
#include <vtkm/io/ImageReaderBase.h>
#include <hdf5.h>
#include <hdf5_hl.h>
namespace vtkm
{
namespace io
{
class VTKM_IO_EXPORT ImageReaderHDF5 : public ImageReaderBase
{
using Superclass = ImageReaderBase;
public:
using Superclass::Superclass;
VTKM_CONT ~ImageReaderHDF5() noexcept override;
ImageReaderHDF5(const ImageReaderHDF5&) = delete;
ImageReaderHDF5& operator=(const ImageReaderHDF5&) = delete;
protected:
VTKM_CONT void Read() override;
private:
};
}
}
#endif // vvtk_m_io_ImageReaderHDF5_IM_h

@ -0,0 +1,108 @@
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/io/ErrorIO.h>
#include <vtkm/io/ImageWriterHDF5_IM.h>
#include <vtkm/io/PixelTypes.h>
namespace
{
// FIXME: why does HDF5 want to call H5open() for something macro?
template <typename PixelType>
struct hdf5_type_trait
{
};
template <>
struct hdf5_type_trait<vtkm::io::RGBPixel_8>
{
auto operator()() { return H5T_NATIVE_UCHAR; }
};
template <>
struct hdf5_type_trait<vtkm::io::RGBPixel_16>
{
// static constexpr auto type_id = H5T_NATIVE_UINT16;
auto operator()() { return H5T_NATIVE_UINT16; }
};
} //namespace
namespace vtkm
{
namespace io
{
ImageWriterHDF5::~ImageWriterHDF5() noexcept = default;
template <typename PixelType>
herr_t ImageWriterHDF5::WriteToFile(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels)
{
constexpr auto BYTES_PER_PIXEL = PixelType::BYTES_PER_PIXEL;
auto pixelPortal = pixels.ReadPortal();
std::vector<unsigned char> imageData(pixels.GetNumberOfValues() * BYTES_PER_PIXEL);
// copy from pixelPortal to imageData, FIXME: do we need this copy?
vtkm::Id pixelIndex = 0;
for (vtkm::Id yindex = 0; yindex < height; ++yindex)
{
for (vtkm::Id xindex = 0; xindex < width; ++xindex)
{
vtkm::Id vtkmIndex = yindex * width + xindex;
PixelType(pixelPortal.Get(vtkmIndex)).FillImageAtIndexWithPixel(imageData.data(), pixelIndex);
pixelIndex++;
}
}
// Shamelessly copied from H5IMmake_image_24bit() implementation.
auto dset_name = "image";
// The image is stored as height*width*3 array of UCHAR/UINT16, i.e. INTERLACE_PIXEL
hsize_t dims[] = { hsize_t(height), hsize_t(width), 3 };
// TODO: change it to exception based error handling.
// Create a HDF5 DataSet
if (H5LTmake_dataset(
this->fileid, dset_name, 3, dims, hdf5_type_trait<PixelType>{}(), imageData.data()) < 0)
return -1;
/* Attach the CLASS attribute */
if (H5LTset_attribute_string(fileid, dset_name, "CLASS", IMAGE_CLASS) < 0)
return -1;
/* Attach the VERSION attribute */
if (H5LTset_attribute_string(fileid, dset_name, "IMAGE_VERSION", IMAGE_VERSION) < 0)
return -1;
/* Attach the IMAGE_SUBCLASS attribute */
if (H5LTset_attribute_string(fileid, dset_name, "IMAGE_SUBCLASS", "IMAGE_TRUECOLOR") < 0)
return -1;
/* Attach the INTERLACE_MODE attribute. This attributes is only for true color images */
if (H5LTset_attribute_string(fileid, dset_name, "INTERLACE_MODE", "INTERLACE_PIXEL") < 0)
return -1;
return 0;
}
void ImageWriterHDF5::Write(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels)
{
this->fileid = H5Fcreate(this->FileName.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
if (this->fileid < 0)
throw vtkm::io::ErrorIO{ "Can not create HDF5 image file" };
switch (this->Depth)
{
case PixelDepth::PIXEL_8:
this->WriteToFile<vtkm::io::RGBPixel_8>(width, height, pixels);
break;
case PixelDepth::PIXEL_16:
this->WriteToFile<vtkm::io::RGBPixel_16>(width, height, pixels);
break;
}
H5Fclose(this->fileid);
}
}
}

@ -0,0 +1,52 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_io_ImageWriterHDF5_IM_H
#define vtk_m_io_ImageWriterHDF5_IM_H
#include <vtkm/io/ImageWriterBase.h>
#include <hdf5.h>
#include <hdf5_hl.h>
namespace vtkm
{
namespace io
{
/// \brief Writing images using HDF5 Image format
///
/// \c ImageWriterHDF5 extends vtkm::io::ImageWriterBase and implements writing image
/// HDF5 file format. It conforms to the HDF5 Image Specification
/// https://portal.hdfgroup.org/display/HDF5/HDF5+Image+and+Palette+Specification%2C+Version+1.2
class VTKM_IO_EXPORT ImageWriterHDF5 : public vtkm::io::ImageWriterBase
{
using Superclass = vtkm::io::ImageWriterBase;
public:
using Superclass::Superclass;
VTKM_CONT ~ImageWriterHDF5() noexcept override;
ImageWriterHDF5(const ImageWriterHDF5&) = delete;
ImageWriterHDF5& operator=(const ImageWriterHDF5&) = delete;
protected:
VTKM_CONT void Write(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels) override;
private:
template <typename PixelType>
VTKM_CONT herr_t WriteToFile(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels);
hid_t fileid = 0;
static constexpr auto IMAGE_CLASS = "IMAGE";
static constexpr auto IMAGE_VERSION = "1.2";
};
}
}
#endif //vtk_m_io_ImageWriterHDF5_IM_H

@ -11,6 +11,7 @@
#include <vtkm/cont/testing/Testing.h> #include <vtkm/cont/testing/Testing.h>
#include <vtkm/io/ImageReaderPNG.h> #include <vtkm/io/ImageReaderPNG.h>
#include <vtkm/io/ImageReaderPNM.h> #include <vtkm/io/ImageReaderPNM.h>
#include <vtkm/io/ImageWriterHDF5_IM.h>
#include <vtkm/io/ImageWriterPNG.h> #include <vtkm/io/ImageWriterPNG.h>
#include <vtkm/io/ImageWriterPNM.h> #include <vtkm/io/ImageWriterPNM.h>
#include <vtkm/io/PixelTypes.h> #include <vtkm/io/PixelTypes.h>
@ -18,6 +19,7 @@
#include <vtkm/rendering/Color.h> #include <vtkm/rendering/Color.h>
#include <string> #include <string>
#include <vtkm/io/ImageReaderHDF5_IM.h>
namespace namespace
{ {
@ -129,6 +131,44 @@ void TestReadAndWritePNM(const vtkm::rendering::Canvas& canvas,
} }
} }
void TestReadAndWriteHDF5(const vtkm::rendering::Canvas& canvas,
std::string filename,
vtkm::io::ImageWriterBase::PixelDepth pixelDepth)
{
std::cout << "TestReadAndWriteHDF5 - " << filename << std::endl;
bool throws = false;
try
{
vtkm::io::ImageWriterHDF5 writer(filename);
vtkm::cont::DataSet dataSet;
writer.WriteDataSet(dataSet);
}
catch (const vtkm::cont::Error&)
{
throws = true;
}
VTKM_TEST_ASSERT(throws, "Fill Image did not throw with empty data");
{
vtkm::io::ImageWriterHDF5 writer(filename);
writer.SetPixelDepth(pixelDepth);
writer.WriteDataSet(canvas.GetDataSet());
}
{
vtkm::io::ImageReaderHDF5 reader(filename);
vtkm::cont::DataSet dataSet = reader.ReadDataSet();
}
{
vtkm::io::ImageWriterHDF5 writer(filename);
writer.SetPixelDepth(pixelDepth);
writer.WriteDataSet(canvas.GetDataSet());
}
{
vtkm::io::ImageReaderHDF5 reader(filename);
vtkm::cont::DataSet dataSet = reader.ReadDataSet();
TestFilledImage(dataSet, reader.GetPointFieldName(), canvas);
}
}
void TestBaseImageMethods(const vtkm::rendering::Canvas& canvas) void TestBaseImageMethods(const vtkm::rendering::Canvas& canvas)
{ {
@ -147,6 +187,12 @@ void TestPNGImage(const vtkm::rendering::Canvas& canvas)
TestReadAndWritePNG(canvas, "pngRGB16Test.png", vtkm::io::ImageWriterBase::PixelDepth::PIXEL_16); TestReadAndWritePNG(canvas, "pngRGB16Test.png", vtkm::io::ImageWriterBase::PixelDepth::PIXEL_16);
} }
void TestHDF5Image(const vtkm::rendering::Canvas& canvas)
{
TestReadAndWriteHDF5(canvas, "hdf5RGB8Test.h5", vtkm::io::ImageWriterBase::PixelDepth::PIXEL_8);
TestReadAndWriteHDF5(canvas, "hdf5RGB16Test.h5", vtkm::io::ImageWriterBase::PixelDepth::PIXEL_16);
}
void TestImage() void TestImage()
{ {
vtkm::rendering::Canvas canvas(16, 16); vtkm::rendering::Canvas canvas(16, 16);
@ -162,6 +208,7 @@ void TestImage()
TestBaseImageMethods(canvas); TestBaseImageMethods(canvas);
TestPNMImage(canvas); TestPNMImage(canvas);
TestPNGImage(canvas); TestPNGImage(canvas);
TestHDF5Image(canvas);
} }
} }