Commit 135b5439 authored by Baptiste Esteban's avatar Baptiste Esteban
Browse files

Merge branch 'development/fits-plugin' into 'next'

FITS plugin implementation

See merge request !122
parents c3203a3b dee83e97
Pipeline #30599 passed with stages
in 26 minutes and 56 seconds
......@@ -23,7 +23,7 @@ class Pylene(ConanFile):
exports_sources = ["pylene/*", "pylene-python/*", "cmake/*", "CMakeLists.txt", "LICENSE"]
build_requires = [
"gtest/[>=1.10]",
"gtest/[>=1.10.0]",
"benchmark/[>=1.5.0]",
]
......@@ -32,7 +32,8 @@ class Pylene(ConanFile):
"fmt/6.0.0",
"tbb/2020.0",
"xsimd/7.4.6",
"boost/1.75.0"
"boost/1.75.0",
"cfitsio/4.0.0"
]
def _build_python(self):
......@@ -71,7 +72,7 @@ class Pylene(ConanFile):
self.cpp_info.components["Core"].includedirs = ["include"]
self.cpp_info.components["Core"].requires = ["range-v3::range-v3", "fmt::fmt", "tbb::tbb", "xsimd::xsimd", "boost::headers"]
# IO component
# IO component (FreeImage)
self.cpp_info.components["IO-freeimage"].system_libs.append("freeimage")
self.cpp_info.components["IO-freeimage"].names["cmake_find_package"] = "IO-freeimage"
self.cpp_info.components["IO-freeimage"].names["cmake_find_package_multi"] = "IO-freeimage"
......@@ -79,6 +80,14 @@ class Pylene(ConanFile):
self.cpp_info.components["IO-freeimage"].includedirs = ["include"]
self.cpp_info.components["IO-freeimage"].requires = ["Core"]
# IO component (cfitsio)
self.cpp_info.components["IO-fits"].names["cmake_find_package"] = "IO-fits"
self.cpp_info.components["IO-fits"].names["cmake_find_package_multi"] = "IO-fits"
self.cpp_info.components["IO-fits"].libs = ["Pylene-io-fits"]
self.cpp_info.components["IO-fits"].includedirs = ["include"]
self.cpp_info.components["IO-fits"].requires = ["Core", "cfitsio::cfitsio"]
# Pylene-numpy component
if self._build_python():
self.cpp_info.components["Pylene-numpy"].names["cmake_find_pakage_multi"] = "Pylene-numpy"
......
File added
......@@ -7,6 +7,7 @@ find_package(TBB REQUIRED tbb)
find_package(range-v3 0.10.0 REQUIRED)
find_package(fmt 6.0 REQUIRED)
find_package(xsimd REQUIRED)
find_package(cfitsio)
set(PYLENE_USE_TBB YES CACHE BOOL "Set to NO to disable use of TBB and parallelization")
......@@ -95,12 +96,27 @@ target_sources(Pylene-io-freeimage PRIVATE
src/io/imread.cpp
src/io/io.cpp
)
target_compile_features(Pylene-io-freeimage PUBLIC cxx_std_20)
target_include_directories(Pylene-io-freeimage PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(Pylene-io-freeimage PUBLIC FreeImage::FreeImage Pylene-core)
target_link_libraries(Pylene-io-freeimage PUBLIC Pylene-core)
target_link_libraries(Pylene-io-freeimage PRIVATE FreeImage::FreeImage)
if (cfitsio_FOUND)
add_library(Pylene-io-fits)
add_library(Pylene::IO-fits ALIAS Pylene-io-fits)
target_sources(Pylene-io-fits PRIVATE
src/io/cfitsio_plugin.cpp
src/io/io.cpp
)
target_include_directories(Pylene-io-fits PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(Pylene-io-fits PUBLIC Pylene-core)
target_link_libraries(Pylene-io-fits PRIVATE cfitsio::cfitsio)
endif(cfitsio_FOUND)
# Compiler configurations
target_compile_features(Pylene-core PUBLIC cxx_std_20)
......
#pragma once
#include <mln/core/image/ndimage_fwd.hpp>
#include <string>
namespace mln::io::fits
{
mln::ndbuffer_image imread(const std::string& filename, int ind=0);
void imread(const std::string& filename, mln::ndbuffer_image& out, int ind=0);
}
\ No newline at end of file
......@@ -8,4 +8,5 @@ namespace mln::io
{
mln::ndbuffer_image imread(const std::string& filename);
void imread(const std::string& filename, mln::ndbuffer_image& out);
}
} // namespace mln::io
#pragma once
#include <mln/io/private/plugin.hpp>
namespace mln::io::fits::internal
{
class cfitsio_reader_plugin final : public mln::io::internal::plugin_reader
{
public:
cfitsio_reader_plugin() = delete;
cfitsio_reader_plugin(int ind);
~cfitsio_reader_plugin() final;
void open(const char* filename) final;
void close() final;
private:
const int m_image_index;
};
} // namespace mln::io::fist::internal
\ No newline at end of file
#include <mln/core/image/ndimage.hpp>
#include <mln/io/fits/imread.hpp>
#include <mln/io/private/cfitsio_plugin.hpp>
#include <mln/io/private/io.hpp>
#include <fitsio.h>
#include <fmt/format.h>
#include <stdexcept>
#include <tuple>
namespace mln::io::fits
{
namespace internal
{
namespace
{
std::pair<mln::sample_type_id, int> get_type_info(int type)
{
switch (type)
{
case BYTE_IMG:
return {mln::sample_type_id::UINT8, TBYTE};
case SHORT_IMG:
return {mln::sample_type_id::INT16, TSHORT};
case LONG_IMG:
return {mln::sample_type_id::INT32, TINT};
case LONGLONG_IMG:
return {mln::sample_type_id::INT64, TLONGLONG};
case FLOAT_IMG:
return {mln::sample_type_id::FLOAT, TFLOAT};
case DOUBLE_IMG:
return {mln::sample_type_id::DOUBLE, TDOUBLE};
break;
default:
throw std::runtime_error("Unhandled data type");
}
return {};
}
struct impl_cfitsio_t : mln::io::internal::plugin_base::impl_t
{
fitsfile* file = nullptr;
int datatype;
void read_next_line(std::byte* __restrict buffer) final
{
int anynul;
int nullpix = 0;
int status = 0;
fits_read_img(file, datatype, m_line * m_dims[0] + 1, m_dims[0], &nullpix, buffer, &anynul, &status);
if (status)
{
char msg[80];
fits_get_errstatus(status, msg);
throw std::runtime_error(fmt::format("Unable to read the image ({})", msg));
}
m_line++;
}
void write_next_line(const std::byte* /* buffer */) final { std::abort(); }
private:
int m_line = 0;
};
} // namespace
cfitsio_reader_plugin::cfitsio_reader_plugin(int ind)
: m_image_index(ind)
{
}
cfitsio_reader_plugin::~cfitsio_reader_plugin() { this->close(); }
void cfitsio_reader_plugin::open(const char* filename)
{
int status = 0;
// Open the file
fitsfile* file;
fits_open_file(&file, filename, READONLY, &status);
if (status)
throw std::runtime_error(fmt::format("Unable to read the file {}", filename));
// Go to the index of the image
fits_movrel_hdu(file, m_image_index, nullptr, &status);
if (status)
throw std::runtime_error(fmt::format("Could not find the image at index {}", m_image_index));
// Check if the HDU is an image
int hdu_type;
fits_get_hdu_type(file, &hdu_type, &status);
if (hdu_type != IMAGE_HDU)
throw std::runtime_error(fmt::format("HDU at index {} is not an image", m_image_index));
// Get the number of dimension
int ndim;
fits_get_img_dim(file, &ndim, &status);
if (ndim == 0 || ndim > 4)
throw std::runtime_error(fmt::format("Unhandled image number of dimension (Got {}, expected in [1 - 4])", ndim));
// Get the dimensions
long dims[4];
fits_get_img_size(file, ndim, dims, &status);
// Get the type info
int type;
fits_get_img_type(file, &type, &status);
const auto [sample_type, datatype] = get_type_info(type);
auto impl = std::make_unique<impl_cfitsio_t>();
impl->file = file;
impl->m_ndim = ndim;
for (int i = 0; i < ndim; i++)
impl->m_dims[i] = dims[i];
impl->m_sample_type_id = sample_type;
impl->datatype = datatype;
this->m_impl = std::move(impl);
}
void cfitsio_reader_plugin::close()
{
auto* impl = static_cast<impl_cfitsio_t*>(this->m_impl.get());
if (impl && impl->file)
{
int status;
fits_close_file(impl->file, &status);
impl->file = nullptr;
}
}
} // namespace internal
mln::ndbuffer_image imread(const std::string& filename, int ind)
{
mln::ndbuffer_image out;
imread(filename, out, ind);
return out;
}
void imread(const std::string& filename, mln::ndbuffer_image& out, int ind)
{
internal::cfitsio_reader_plugin p(ind);
mln::io::internal::load(&p, filename.c_str(), out);
}
} // namespace mln::io::fits
\ No newline at end of file
#include <mln/io/imread.hpp>
#include <mln/io/private/io.hpp>
#include <mln/io/private/freeimage_plugin.hpp>
#include <mln/core/image/ndbuffer_image.hpp>
#include <mln/io/private/freeimage_plugin.hpp>
#include <mln/io/private/io.hpp>
namespace mln::io
{
......@@ -18,4 +18,4 @@ namespace mln::io
internal::freeimage_reader_plugin p;
internal::load(&p, filename.c_str(), out);
}
}
} // namespace mln::io
find_package(cfitsio)
add_core_test(UTIo_freeimage freeimage.cpp)
add_core_test(UTIo_imprint imprint.cpp)
if (cfitsio_FOUND)
add_core_test(UTIo_cfitsio cfitsio.cpp)
target_link_libraries(UTIo_cfitsio PUBLIC Pylene::IO-fits)
endif(cfitsio_FOUND)
\ No newline at end of file
#include <mln/core/image/ndimage.hpp>
#include <mln/io/fits/imread.hpp>
#include <fixtures/ImageCompare/image_compare.hpp>
#include <fixtures/ImagePath/image_path.hpp>
#include <gtest/gtest.h>
#include <cstring>
static const auto filename = fixtures::ImagePath::concat_with_filename("test.fit");
TEST(IO, cfitsio_2D_uint8)
{
mln::image2d<std::uint8_t> ref = {
{0, 1, 2, 3, 4}, //
{5, 6, 7, 8, 9}, //
{10, 11, 12, 13, 14}, //
{15, 16, 17, 18, 19}, //
{20, 21, 22, 23, 24} //
};
auto img = mln::io::fits::imread(filename, 1);
ASSERT_EQ(img.sample_type(), mln::sample_type_id::UINT8);
ASSERT_EQ(img.pdim(), 2);
ASSERT_EQ(img.width(), 5);
ASSERT_EQ(img.height(), 5);
auto* casted = img.cast_to<std::uint8_t, 2>();
ASSERT_IMAGES_EQ_EXP(*casted, ref);
}
TEST(IO, cfitsio_2D_int16)
{
mln::image2d<std::int16_t> ref = {
{0, 1, 2, 3, 4}, //
{5, 6, 7, 8, 9}, //
{10, 11, 12, 13, 14}, //
{15, 16, 17, 18, 19}, //
{20, 21, 22, 23, 24} //
};
auto img = mln::io::fits::imread(filename, 2);
ASSERT_EQ(img.sample_type(), mln::sample_type_id::INT16);
ASSERT_EQ(img.pdim(), 2);
ASSERT_EQ(img.width(), 5);
ASSERT_EQ(img.height(), 5);
auto* casted = img.cast_to<std::int16_t, 2>();
ASSERT_IMAGES_EQ_EXP(*casted, ref);
}
TEST(IO, cfitsio_2D_int32)
{
mln::image2d<std::int32_t> ref = {
{0, 1, 2, 3, 4}, //
{5, 6, 7, 8, 9}, //
{10, 11, 12, 13, 14}, //
{15, 16, 17, 18, 19}, //
{20, 21, 22, 23, 24} //
};
auto img = mln::io::fits::imread(filename, 3);
ASSERT_EQ(img.sample_type(), mln::sample_type_id::INT32);
ASSERT_EQ(img.pdim(), 2);
ASSERT_EQ(img.width(), 5);
ASSERT_EQ(img.height(), 5);
auto* casted = img.cast_to<std::int32_t, 2>();
ASSERT_IMAGES_EQ_EXP(*casted, ref);
}
TEST(IO, cfitsio_2D_int64)
{
mln::image2d<std::int64_t> ref = {
{0, 1, 2, 3, 4}, //
{5, 6, 7, 8, 9}, //
{10, 11, 12, 13, 14}, //
{15, 16, 17, 18, 19}, //
{20, 21, 22, 23, 24} //
};
auto img = mln::io::fits::imread(filename, 4);
ASSERT_EQ(img.sample_type(), mln::sample_type_id::INT64);
ASSERT_EQ(img.pdim(), 2);
ASSERT_EQ(img.width(), 5);
ASSERT_EQ(img.height(), 5);
auto* casted = img.cast_to<std::int64_t, 2>();
ASSERT_IMAGES_EQ_EXP(*casted, ref);
}
TEST(IO, cfitsio_2D_float)
{
mln::image2d<float> ref = {
{0.f, 1.f, 2.f, 3.f, 4.f}, //
{5.f, 6.f, 7.f, 8.f, 9.f}, //
{10.f, 11.f, 12.f, 13.f, 14.f}, //
{15.f, 16.f, 17.f, 18.f, 19.f}, //
{20.f, 21.f, 22.f, 23.f, 24.f} //
};
auto img = mln::io::fits::imread(filename, 5);
ASSERT_EQ(img.sample_type(), mln::sample_type_id::FLOAT);
ASSERT_EQ(img.pdim(), 2);
ASSERT_EQ(img.width(), 5);
ASSERT_EQ(img.height(), 5);
auto* casted = img.cast_to<float, 2>();
ASSERT_IMAGES_EQ_EXP(*casted, ref);
}
TEST(IO, cfitsio_2D_double)
{
mln::image2d<double> ref = {
{0.0, 1.0, 2.0, 3.0, 4.0}, //
{5.0, 6.0, 7.0, 8.0, 9.0}, //
{10.0, 11.0, 12.0, 13.0, 14.0}, //
{15.0, 16.0, 17.0, 18.0, 19.0}, //
{20.0, 21.0, 22.0, 23.0, 24.0} //
};
auto img = mln::io::fits::imread(filename, 6);
ASSERT_EQ(img.sample_type(), mln::sample_type_id::DOUBLE);
ASSERT_EQ(img.pdim(), 2);
ASSERT_EQ(img.width(), 5);
ASSERT_EQ(img.height(), 5);
auto* casted = img.cast_to<double, 2>();
ASSERT_IMAGES_EQ_EXP(*casted, ref);
}
TEST(IO, cfitsio_3D)
{
auto img = mln::io::fits::imread(filename, 7);
ASSERT_EQ(img.sample_type(), mln::sample_type_id::UINT8);
ASSERT_EQ(img.pdim(), 3);
ASSERT_EQ(img.width(), 5);
ASSERT_EQ(img.height(), 4);
auto* casted = img.cast_to<std::uint8_t, 3>();
int cur = 0;
mln_foreach (auto p, casted->domain())
ASSERT_EQ(casted->operator()(p), cur++);
}
TEST(IO, cfitsio_read_to_argument)
{
mln::image2d<std::uint8_t> ref = {
{0, 1, 2, 3, 4}, //
{5, 6, 7, 8, 9}, //
{10, 11, 12, 13, 14}, //
{15, 16, 17, 18, 19}, //
{20, 21, 22, 23, 24} //
};
mln::image2d<std::uint8_t> img;
mln::io::fits::imread(filename, img, 1);
ASSERT_EQ(img.width(), 5);
ASSERT_EQ(img.height(), 5);
ASSERT_IMAGES_EQ_EXP(img, ref);
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment