Add testing
This commit is contained in:
+18
-2
@@ -1,6 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 3.14)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
|
||||||
project(libXISF LANGUAGES CXX)
|
project(libXISF VERSION 0.1.0 LANGUAGES CXX C)
|
||||||
|
|
||||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
@@ -22,8 +22,24 @@ add_library(XISF SHARED
|
|||||||
lz4/lz4hc.h
|
lz4/lz4hc.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(XISF PRIVATE Qt${QT_VERSION_MAJOR}::Core)
|
target_link_libraries(XISF PUBLIC Qt${QT_VERSION_MAJOR}::Core)
|
||||||
|
|
||||||
target_compile_definitions(XISF PRIVATE LIBXISF_LIBRARY)
|
target_compile_definitions(XISF PRIVATE LIBXISF_LIBRARY)
|
||||||
|
|
||||||
set_target_properties(XISF PROPERTIES PUBLIC_HEADER libxisf.h)
|
set_target_properties(XISF PROPERTIES PUBLIC_HEADER libxisf.h)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
install(TARGETS XISF PUBLIC_HEADER)
|
||||||
|
|
||||||
|
#testing
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(LibXISFTest test/main.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(LibXISFTest XISF)
|
||||||
|
|
||||||
|
add_test(NAME LibXISFTest COMMAND LibXISFTest)
|
||||||
|
add_test(NAME LibXISFTestRead COMMAND LibXISFTest "${CMAKE_CURRENT_LIST_DIR}/test/test.xisf")
|
||||||
|
add_test(NAME LibXISFTestReadLZ4 COMMAND LibXISFTest "${CMAKE_CURRENT_LIST_DIR}/test/test_lz4.xisf")
|
||||||
|
|||||||
+161
-124
@@ -18,18 +18,16 @@
|
|||||||
|
|
||||||
#include "libxisf.h"
|
#include "libxisf.h"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <QXmlStreamReader>
|
#include <QXmlStreamReader>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QtEndian>
|
#include <QtEndian>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
#include <QDebug>
|
|
||||||
#include "lz4/lz4.h"
|
#include "lz4/lz4.h"
|
||||||
#include "lz4/lz4hc.h"
|
#include "lz4/lz4hc.h"
|
||||||
|
|
||||||
#define STRING_ENUM(e) {#e, e}
|
|
||||||
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||||
template<> struct std::hash<QString>
|
template<> struct std::hash<QString>
|
||||||
{
|
{
|
||||||
@@ -43,8 +41,14 @@ template<> struct std::hash<QString>
|
|||||||
namespace LibXISF
|
namespace LibXISF
|
||||||
{
|
{
|
||||||
|
|
||||||
static std::unordered_map<const char*, int> typeToId;
|
static std::unordered_map<QString, int> typeToId;
|
||||||
static std::unordered_map<int, const char*> idToType;
|
static std::unordered_map<int, QString> idToType;
|
||||||
|
static std::unordered_map<QString, Image::Type> imageTypeToEnum;
|
||||||
|
static std::unordered_map<Image::Type, QString> imageTypeToString;
|
||||||
|
static std::unordered_map<QString, Image::SampleFormat> sampleFormatToEnum;
|
||||||
|
static std::unordered_map<Image::SampleFormat, QString> sampleFormatToString;
|
||||||
|
static std::unordered_map<QString, Image::ColorSpace> colorSpaceToEnum;
|
||||||
|
static std::unordered_map<Image::ColorSpace, QString> colorSpaceToString;
|
||||||
|
|
||||||
static void byteShuffle(QByteArray &data, int itemSize)
|
static void byteShuffle(QByteArray &data, int itemSize)
|
||||||
{
|
{
|
||||||
@@ -84,16 +88,9 @@ static void byteUnshuffle(QByteArray &data, int itemSize)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString sampleFormatToString(Image::SampleFormat format)
|
bool isString(QMetaType::Type type)
|
||||||
{
|
{
|
||||||
static QStringList sampleFormats = {"UInt8", "UInt16", "UInt32", "UInt64", "Float32", "Float64", "Complex32", "Complex64"};
|
return type == QMetaType::QString;
|
||||||
return sampleFormats[format];
|
|
||||||
}
|
|
||||||
|
|
||||||
QString colorSpaceToString(Image::ColorSpace colorSpace)
|
|
||||||
{
|
|
||||||
static QStringList colorSpaces = {"Gray", "RGB", "CIELab"};
|
|
||||||
return colorSpaces[colorSpace];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataBlock::decompress(const QByteArray &input, const QString &encoding)
|
void DataBlock::decompress(const QByteArray &input, const QString &encoding)
|
||||||
@@ -122,7 +119,7 @@ void DataBlock::decompress(const QByteArray &input, const QString &encoding)
|
|||||||
case LZ4HC:
|
case LZ4HC:
|
||||||
data.resize(uncompressedSize);
|
data.resize(uncompressedSize);
|
||||||
if(LZ4_decompress_safe(tmp.constData(), data.data(), tmp.size(), data.size()) < 0)
|
if(LZ4_decompress_safe(tmp.constData(), data.data(), tmp.size(), data.size()) < 0)
|
||||||
throw std::runtime_error("LZ4 decompression failed");
|
throw Error("LZ4 decompression failed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +154,7 @@ void DataBlock::compress()
|
|||||||
compSize = LZ4_compress_HC(tmp.constData(), data.data(), tmp.size(), data.size(), LZ4HC_CLEVEL_DEFAULT);
|
compSize = LZ4_compress_HC(tmp.constData(), data.data(), tmp.size(), data.size(), LZ4HC_CLEVEL_DEFAULT);
|
||||||
|
|
||||||
if(compSize <= 0)
|
if(compSize <= 0)
|
||||||
throw std::runtime_error("LZ4 compression failed");
|
throw Error("LZ4 compression failed");
|
||||||
|
|
||||||
data.resize(compSize);
|
data.resize(compSize);
|
||||||
break;
|
break;
|
||||||
@@ -165,6 +162,12 @@ void DataBlock::compress()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Property::Property(const QString &_id, const char *_value) :
|
||||||
|
id(_id),
|
||||||
|
value(_value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void planarToNormal(void *_in, void *_out, size_t channels, size_t size)
|
void planarToNormal(void *_in, void *_out, size_t channels, size_t size)
|
||||||
{
|
{
|
||||||
@@ -187,8 +190,11 @@ void normalToPlanar(void *_in, void *_out, size_t channels, size_t size)
|
|||||||
|
|
||||||
void Image::convertPixelStorageTo(PixelStorage storage)
|
void Image::convertPixelStorageTo(PixelStorage storage)
|
||||||
{
|
{
|
||||||
if(pixelStorage == storage)
|
if(pixelStorage == storage || channelCount <= 1)
|
||||||
|
{
|
||||||
|
pixelStorage = storage;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray tmp;
|
QByteArray tmp;
|
||||||
tmp.resize(dataBlock.data.size());
|
tmp.resize(dataBlock.data.size());
|
||||||
@@ -231,22 +237,14 @@ void Image::convertPixelStorageTo(PixelStorage storage)
|
|||||||
|
|
||||||
Image::Type Image::imageTypeEnum(const QString &type)
|
Image::Type Image::imageTypeEnum(const QString &type)
|
||||||
{
|
{
|
||||||
static const std::unordered_map<QString, Image::Type> imageTypeMap = {STRING_ENUM(Bias),
|
auto t = imageTypeToEnum.find(type);
|
||||||
STRING_ENUM(Dark),
|
return t != imageTypeToEnum.end() ? t->second : Image::Light;
|
||||||
STRING_ENUM(Flat),
|
}
|
||||||
STRING_ENUM(Light),
|
|
||||||
STRING_ENUM(MasterBias),
|
QString Image::imageTypeString(Type type)
|
||||||
STRING_ENUM(MasterDark),
|
{
|
||||||
STRING_ENUM(MasterFlat),
|
auto t = imageTypeToString.find(type);
|
||||||
STRING_ENUM(DefectMap),
|
return t != imageTypeToString.end() ? t->second : "Light";
|
||||||
STRING_ENUM(RejectionMapHigh),
|
|
||||||
STRING_ENUM(RejectionMapLow),
|
|
||||||
STRING_ENUM(BinaryRejectionMapHigh),
|
|
||||||
STRING_ENUM(BinaryRejectionMapLow),
|
|
||||||
STRING_ENUM(SlopeMap),
|
|
||||||
STRING_ENUM(WeightMap});
|
|
||||||
auto t = imageTypeMap.find(type);
|
|
||||||
return t != imageTypeMap.end() ? t->second : Image::Light;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Image::PixelStorage Image::pixelStorageEnum(const QString &storage)
|
Image::PixelStorage Image::pixelStorageEnum(const QString &storage)
|
||||||
@@ -255,25 +253,34 @@ Image::PixelStorage Image::pixelStorageEnum(const QString &storage)
|
|||||||
return Image::Planar;
|
return Image::Planar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Image::pixelStorageString(PixelStorage storage)
|
||||||
|
{
|
||||||
|
if(storage == Normal)return "Normal";
|
||||||
|
return "Planar";
|
||||||
|
}
|
||||||
|
|
||||||
Image::SampleFormat Image::sampleFormatEnum(const QString &format)
|
Image::SampleFormat Image::sampleFormatEnum(const QString &format)
|
||||||
{
|
{
|
||||||
static const std::unordered_map<QString, SampleFormat> sampleFormatMap = {STRING_ENUM(UInt8),
|
auto t = sampleFormatToEnum.find(format);
|
||||||
STRING_ENUM(UInt16),
|
return t != sampleFormatToEnum.end() ? t->second : Image::UInt16;
|
||||||
STRING_ENUM(UInt32),
|
}
|
||||||
STRING_ENUM(UInt64),
|
|
||||||
STRING_ENUM(Float32),
|
QString Image::sampleFormatString(SampleFormat format)
|
||||||
STRING_ENUM(Float64),
|
{
|
||||||
STRING_ENUM(Complex32),
|
auto t = sampleFormatToString.find(format);
|
||||||
STRING_ENUM(Complex64)};
|
return t != sampleFormatToString.end() ? t->second : "UInt16";
|
||||||
auto t = sampleFormatMap.find(format);
|
|
||||||
return t != sampleFormatMap.end() ? t->second : Image::UInt16;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Image::ColorSpace Image::colorSpaceEnum(const QString &colorSpace)
|
Image::ColorSpace Image::colorSpaceEnum(const QString &colorSpace)
|
||||||
{
|
{
|
||||||
static const std::unordered_map<QString, ColorSpace> colorSpaceMap = { STRING_ENUM(Gray), STRING_ENUM(RGB), STRING_ENUM(CIELab) };
|
auto t = colorSpaceToEnum.find(colorSpace);
|
||||||
auto t = colorSpaceMap.find(colorSpace);
|
return t != colorSpaceToEnum.end() ? t->second : Image::Gray;
|
||||||
return t != colorSpaceMap.end() ? t->second : Image::Gray;
|
}
|
||||||
|
|
||||||
|
QString Image::colorSpaceString(ColorSpace colorSpace)
|
||||||
|
{
|
||||||
|
auto t = colorSpaceToString.find(colorSpace);
|
||||||
|
return t != colorSpaceToString.end() ? t->second : "Gray";
|
||||||
}
|
}
|
||||||
|
|
||||||
XISFReader::XISFReader()
|
XISFReader::XISFReader()
|
||||||
@@ -299,7 +306,7 @@ void XISFReader::open(QIODevice *io)
|
|||||||
close();
|
close();
|
||||||
_io.reset(io);
|
_io.reset(io);
|
||||||
if(!_io->open(QIODevice::ReadOnly))
|
if(!_io->open(QIODevice::ReadOnly))
|
||||||
throw std::runtime_error("Failed to open file");
|
throw Error("Failed to open file");
|
||||||
|
|
||||||
readSignature();
|
readSignature();
|
||||||
readXISFHeader();
|
readXISFHeader();
|
||||||
@@ -321,7 +328,7 @@ int XISFReader::imagesCount() const
|
|||||||
const Image& XISFReader::getImage(uint32_t n)
|
const Image& XISFReader::getImage(uint32_t n)
|
||||||
{
|
{
|
||||||
if(n >= _images.size())
|
if(n >= _images.size())
|
||||||
throw std::runtime_error("Out of bounds");
|
throw Error("Out of bounds");
|
||||||
|
|
||||||
Image &img = _images[n];
|
Image &img = _images[n];
|
||||||
if(img.dataBlock.attachmentPos)
|
if(img.dataBlock.attachmentPos)
|
||||||
@@ -355,20 +362,20 @@ void XISFReader::readXISFHeader()
|
|||||||
_properties.push_back(readPropertyElement());
|
_properties.push_back(readPropertyElement());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else throw std::runtime_error("Unknown root XML element");
|
else throw Error("Unknown root XML element");
|
||||||
|
|
||||||
if(_xml->hasError())
|
if(_xml->hasError())
|
||||||
throw std::runtime_error(_xml->errorString().toStdString());
|
throw Error(_xml->errorString().toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void XISFReader::readSignature()
|
void XISFReader::readSignature()
|
||||||
{
|
{
|
||||||
char signature[8];
|
char signature[8];
|
||||||
if(_io->read(signature, sizeof(signature)) != sizeof(signature))
|
if(_io->read(signature, sizeof(signature)) != sizeof(signature))
|
||||||
throw std::runtime_error("Failed to read from file");
|
throw Error("Failed to read from file");
|
||||||
|
|
||||||
if(memcmp(signature, "XISF0100", sizeof(signature)) != 0)
|
if(memcmp(signature, "XISF0100", sizeof(signature)) != 0)
|
||||||
throw std::runtime_error("Not valid XISF 1.0 file");
|
throw Error("Not valid XISF 1.0 file");
|
||||||
}
|
}
|
||||||
|
|
||||||
void XISFReader::readImageElement()
|
void XISFReader::readImageElement()
|
||||||
@@ -378,11 +385,11 @@ void XISFReader::readImageElement()
|
|||||||
Image image;
|
Image image;
|
||||||
|
|
||||||
QVector<QStringRef> geometry = attributes.value("geometry").split(":");
|
QVector<QStringRef> geometry = attributes.value("geometry").split(":");
|
||||||
if(geometry.size() != 3)throw std::runtime_error("We support only 2D images");
|
if(geometry.size() != 3)throw Error("We support only 2D images");
|
||||||
image.width = geometry[0].toULongLong();
|
image.width = geometry[0].toULongLong();
|
||||||
image.height = geometry[1].toULongLong();
|
image.height = geometry[1].toULongLong();
|
||||||
image.channelCount = geometry[2].toULongLong();
|
image.channelCount = geometry[2].toULongLong();
|
||||||
if(!image.width || !image.height || !image.channelCount)throw std::runtime_error("Invalid image geometry");
|
if(!image.width || !image.height || !image.channelCount)throw Error("Invalid image geometry");
|
||||||
|
|
||||||
QVector<QStringRef> bounds = attributes.value("bounds").split(":");
|
QVector<QStringRef> bounds = attributes.value("bounds").split(":");
|
||||||
if(bounds.size() == 2)
|
if(bounds.size() == 2)
|
||||||
@@ -403,8 +410,6 @@ void XISFReader::readImageElement()
|
|||||||
{
|
{
|
||||||
if(_xml->name() == "Property")
|
if(_xml->name() == "Property")
|
||||||
image.properties.push_back(readPropertyElement());
|
image.properties.push_back(readPropertyElement());
|
||||||
else if(image.dataBlock.embedded && _xml->name() == "Data")
|
|
||||||
readDataElement(image.dataBlock);
|
|
||||||
else if(_xml->name() == "ICCProfile")
|
else if(_xml->name() == "ICCProfile")
|
||||||
{
|
{
|
||||||
DataBlock icc = readDataBlock();
|
DataBlock icc = readDataBlock();
|
||||||
@@ -426,53 +431,29 @@ Property XISFReader::readPropertyElement()
|
|||||||
property.format = attributes.value("format").toString();
|
property.format = attributes.value("format").toString();
|
||||||
property.comment = attributes.value("comment").toString();
|
property.comment = attributes.value("comment").toString();
|
||||||
|
|
||||||
QStringRef type = attributes.value("type");
|
QString type = attributes.value("type").toString();
|
||||||
QStringRef value = attributes.value("value");
|
if(typeToId.count(type) == 0)
|
||||||
if(type == "Int8")
|
throw Error("Invalid type in property");
|
||||||
property.value.setValue((Int8)value.toInt());
|
|
||||||
else if(type == "Int16")
|
|
||||||
property.value.setValue((Int16)value.toInt());
|
|
||||||
else if(type == "Int32")
|
|
||||||
property.value.setValue((Int32)value.toInt());
|
|
||||||
else if(type == "Int64")
|
|
||||||
property.value.setValue((Int64)value.toLongLong());
|
|
||||||
else if(type == "UInt8")
|
|
||||||
property.value.setValue((Int8)value.toInt());
|
|
||||||
else if(type == "UInt16")
|
|
||||||
property.value.setValue((Int16)value.toInt());
|
|
||||||
else if(type == "UInt32")
|
|
||||||
property.value.setValue<UInt32>(value.toUInt());
|
|
||||||
else if(type == "UInt64")
|
|
||||||
property.value.setValue<UInt64>(value.toULongLong());
|
|
||||||
else if(type == "Float32")
|
|
||||||
property.value = value.toFloat();
|
|
||||||
else if(type == "Float64")
|
|
||||||
property.value = value.toDouble();
|
|
||||||
else if(type == "TimePoint")
|
|
||||||
property.value = QDateTime::fromString(value.toString(), Qt::ISODate);
|
|
||||||
else if(type == "String")
|
|
||||||
{
|
|
||||||
if(attributes.hasAttribute("location"))
|
|
||||||
{
|
|
||||||
DataBlock dataBlock = readDataBlock();
|
|
||||||
if(dataBlock.embedded)
|
|
||||||
readDataElement(dataBlock);
|
|
||||||
property.value = QString::fromUtf8(dataBlock.data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
property.value = _xml->readElementText();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
property.value = value.toString();
|
|
||||||
|
|
||||||
qDebug() << property.id << type << property.value.typeName() << property.value;
|
QVariant value = attributes.value("value").toString();
|
||||||
|
value.convert(typeToId[type]);
|
||||||
|
property.value = value;
|
||||||
|
|
||||||
return property;
|
return property;
|
||||||
}
|
}
|
||||||
|
|
||||||
void XISFReader::readDataElement(DataBlock &dataBlock)
|
void XISFReader::readDataElement(DataBlock &dataBlock)
|
||||||
{
|
{
|
||||||
readCompression(dataBlock);
|
_xml->readNextStartElement();
|
||||||
|
if(_xml->name() == "Data")
|
||||||
|
{
|
||||||
|
readCompression(dataBlock);
|
||||||
|
QString encoding = _xml->attributes().value("encoding").toString();
|
||||||
|
QByteArray text = _xml->readElementText().toUtf8();
|
||||||
|
dataBlock.decompress(text, encoding);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw Error("Unexpected XML element");
|
||||||
}
|
}
|
||||||
|
|
||||||
DataBlock XISFReader::readDataBlock()
|
DataBlock XISFReader::readDataBlock()
|
||||||
@@ -497,13 +478,16 @@ DataBlock XISFReader::readDataBlock()
|
|||||||
bool ok1, ok2;
|
bool ok1, ok2;
|
||||||
dataBlock.attachmentPos = location[1].toULongLong(&ok1);
|
dataBlock.attachmentPos = location[1].toULongLong(&ok1);
|
||||||
dataBlock.attachmentSize = location[2].toULongLong(&ok2);
|
dataBlock.attachmentSize = location[2].toULongLong(&ok2);
|
||||||
if(!ok1 || !ok2)throw std::runtime_error("Invalid attachment");
|
if(!ok1 || !ok2)throw Error("Invalid attachment");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Invalid data block");
|
throw Error("Invalid data block");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(dataBlock.embedded)
|
||||||
|
readDataElement(dataBlock);
|
||||||
|
|
||||||
return dataBlock;
|
return dataBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,7 +503,7 @@ void XISFReader::readCompression(DataBlock &dataBlock)
|
|||||||
else if(compression[0].startsWith("lz4"))
|
else if(compression[0].startsWith("lz4"))
|
||||||
dataBlock.codec = DataBlock::LZ4;
|
dataBlock.codec = DataBlock::LZ4;
|
||||||
else
|
else
|
||||||
throw std::runtime_error("Unknown compression codec");
|
throw Error("Unknown compression codec");
|
||||||
|
|
||||||
dataBlock.uncompressedSize = compression[1].toULongLong();
|
dataBlock.uncompressedSize = compression[1].toULongLong();
|
||||||
|
|
||||||
@@ -528,7 +512,7 @@ void XISFReader::readCompression(DataBlock &dataBlock)
|
|||||||
if(compression.size() == 3)
|
if(compression.size() == 3)
|
||||||
dataBlock.byteShuffling = compression[2].toInt();
|
dataBlock.byteShuffling = compression[2].toInt();
|
||||||
else
|
else
|
||||||
throw std::runtime_error("Missing byte shuffling size");
|
throw Error("Missing byte shuffling size");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -536,6 +520,7 @@ void XISFReader::readCompression(DataBlock &dataBlock)
|
|||||||
XISFWriter::XISFWriter()
|
XISFWriter::XISFWriter()
|
||||||
{
|
{
|
||||||
_xml = std::make_unique<QXmlStreamWriter>();
|
_xml = std::make_unique<QXmlStreamWriter>();
|
||||||
|
_xml->setAutoFormatting(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void XISFWriter::save(const QString &name)
|
void XISFWriter::save(const QString &name)
|
||||||
@@ -543,7 +528,7 @@ void XISFWriter::save(const QString &name)
|
|||||||
QFile fw(name);
|
QFile fw(name);
|
||||||
|
|
||||||
if(!fw.open(QIODevice::WriteOnly))
|
if(!fw.open(QIODevice::WriteOnly))
|
||||||
throw std::runtime_error("Failed to open file");
|
throw Error("Failed to open file");
|
||||||
|
|
||||||
save(fw);
|
save(fw);
|
||||||
}
|
}
|
||||||
@@ -567,6 +552,15 @@ void XISFWriter::save(QIODevice &io)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void XISFWriter::saveXML(const QString &name)
|
||||||
|
{
|
||||||
|
QFile fw(name);
|
||||||
|
fw.open(QIODevice::WriteOnly);
|
||||||
|
QByteArray header = _xisfHeader.mid(16);
|
||||||
|
header.truncate(header.indexOf('\0'));
|
||||||
|
fw.write(header);
|
||||||
|
}
|
||||||
|
|
||||||
void XISFWriter::writeImage(const Image &image)
|
void XISFWriter::writeImage(const Image &image)
|
||||||
{
|
{
|
||||||
_images.push_back(image);
|
_images.push_back(image);
|
||||||
@@ -599,29 +593,42 @@ void XISFWriter::writeHeader()
|
|||||||
writeMetadata();
|
writeMetadata();
|
||||||
|
|
||||||
_xml->writeEndElement();
|
_xml->writeEndElement();
|
||||||
|
_xml->writeEndDocument();
|
||||||
|
|
||||||
uint32_t size = _xisfHeader.size();
|
uint32_t size = _xisfHeader.size();
|
||||||
QByteArray blockPos = QByteArray::number(size);
|
|
||||||
_xisfHeader.replace("#########", blockPos);
|
uint32_t offset = 0;
|
||||||
|
const char replace[] = "attachment:2147483648";
|
||||||
|
for(auto &image : _images)
|
||||||
|
{
|
||||||
|
QByteArray blockPos = QByteArray("attachment:") + QByteArray::number(size + offset);
|
||||||
|
_xisfHeader.replace(_xisfHeader.indexOf(replace), sizeof(replace) - 1, blockPos);
|
||||||
|
offset += image.dataBlock.data.size();
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t headerSize = _xisfHeader.size() - sizeof(signature);
|
uint32_t headerSize = _xisfHeader.size() - sizeof(signature);
|
||||||
_xisfHeader.append(size - _xisfHeader.size(), '\0');
|
_xisfHeader.append(size - _xisfHeader.size(), '\0');
|
||||||
|
|
||||||
|
|
||||||
buffer.seek(8);
|
buffer.seek(8);
|
||||||
buffer.write((char*)&headerSize, sizeof(size));
|
buffer.write((char*)&headerSize, sizeof(size));
|
||||||
|
|
||||||
_xml->writeEndDocument();
|
|
||||||
|
|
||||||
if(_xml->hasError())
|
if(_xml->hasError())
|
||||||
throw std::runtime_error("Failed to write XML header");
|
throw Error("Failed to write XML header");
|
||||||
}
|
}
|
||||||
|
|
||||||
void XISFWriter::writeImageElement(const Image &image)
|
void XISFWriter::writeImageElement(const Image &image)
|
||||||
{
|
{
|
||||||
_xml->writeStartElement("Image");
|
_xml->writeStartElement("Image");
|
||||||
_xml->writeAttribute("geometry", QString("%1:%2:%3").arg(image.width).arg(image.height).arg(image.channelCount));
|
_xml->writeAttribute("geometry", QString("%1:%2:%3").arg(image.width).arg(image.height).arg(image.channelCount));
|
||||||
_xml->writeAttribute("sampleFormat", sampleFormatToString(image.sampleFormat));
|
_xml->writeAttribute("sampleFormat", Image::sampleFormatString(image.sampleFormat));
|
||||||
_xml->writeAttribute("colorSpace", colorSpaceToString(image.colorSpace));
|
_xml->writeAttribute("colorSpace", Image::colorSpaceString(image.colorSpace));
|
||||||
|
_xml->writeAttribute("imageType", Image::imageTypeString(image.imageType));
|
||||||
|
if((image.sampleFormat == Image::Float32 || image.sampleFormat == Image::Float64) ||
|
||||||
|
image.bounds[0] != 0.0 || image.bounds[1] != 1.0)
|
||||||
|
{
|
||||||
|
_xml->writeAttribute("bounds", QString("%1:%2").arg(image.bounds[0]));
|
||||||
|
}
|
||||||
|
|
||||||
writeDataBlockAttributes(image.dataBlock);
|
writeDataBlockAttributes(image.dataBlock);
|
||||||
for(auto &property : image.properties)
|
for(auto &property : image.properties)
|
||||||
writePropertyElement(property);
|
writePropertyElement(property);
|
||||||
@@ -643,7 +650,7 @@ void XISFWriter::writeDataBlockAttributes(const DataBlock &dataBlock)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_xml->writeAttribute("location", QString("attachment:#########:%1").arg(dataBlock.data.size()));
|
_xml->writeAttribute("location", QString("attachment:2147483648:%1").arg(dataBlock.data.size()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,9 +680,11 @@ void XISFWriter::writeCompressionAttributes(const DataBlock &dataBlock)
|
|||||||
|
|
||||||
void XISFWriter::writePropertyElement(const Property &property)
|
void XISFWriter::writePropertyElement(const Property &property)
|
||||||
{
|
{
|
||||||
|
int type = property.value.userType();
|
||||||
|
|
||||||
_xml->writeStartElement("Property");
|
_xml->writeStartElement("Property");
|
||||||
_xml->writeAttribute("id", property.id);
|
_xml->writeAttribute("id", property.id);
|
||||||
_xml->writeAttribute("type", idToType[property.value.type()]);
|
_xml->writeAttribute("type", idToType[type]);
|
||||||
|
|
||||||
if(!property.format.isEmpty())
|
if(!property.format.isEmpty())
|
||||||
_xml->writeAttribute("format", property.format);
|
_xml->writeAttribute("format", property.format);
|
||||||
@@ -683,34 +692,34 @@ void XISFWriter::writePropertyElement(const Property &property)
|
|||||||
if(!property.comment.isEmpty())
|
if(!property.comment.isEmpty())
|
||||||
_xml->writeAttribute("comment", property.comment);
|
_xml->writeAttribute("comment", property.comment);
|
||||||
|
|
||||||
if((QMetaType::Type)property.value.type() == QMetaType::QString)
|
if(type == QMetaType::QString)
|
||||||
_xml->writeCharacters(property.value.toString());
|
_xml->writeCharacters(property.value.toString());
|
||||||
|
else if(type == QMetaType::SChar || type == QMetaType::UChar)
|
||||||
|
_xml->writeAttribute("value", QString::number(property.value.toInt()));
|
||||||
else
|
else
|
||||||
_xml->writeAttribute("value", property.value.toString());
|
_xml->writeAttribute("value", property.value.toString());
|
||||||
_xml->writeEndElement();
|
_xml->writeEndElement();
|
||||||
|
if(_xml->hasError())
|
||||||
|
throw Error("Failed to write property");
|
||||||
}
|
}
|
||||||
|
|
||||||
void XISFWriter::writeMetadata()
|
void XISFWriter::writeMetadata()
|
||||||
{
|
{
|
||||||
_xml->writeStartElement("Metadata");
|
_xml->writeStartElement("Metadata");
|
||||||
|
writePropertyElement(Property("XISF:CreationTime", QDateTime::currentDateTimeUtc().toString(Qt::ISODate)));
|
||||||
writePropertyElement({"XISF:CreationTime", QDateTime::currentDateTimeUtc().toString(Qt::ISODate), QString(), QString()});
|
writePropertyElement(Property("XISF:CreatorApplication", "LibXISF"));
|
||||||
|
|
||||||
_xml->writeStartElement("Property");
|
|
||||||
_xml->writeAttribute("id", "XISF:CreatorApplication");
|
|
||||||
_xml->writeAttribute("type", "String");
|
|
||||||
_xml->writeCharacters("LibXISF");
|
|
||||||
_xml->writeEndElement();
|
|
||||||
|
|
||||||
_xml->writeEndElement();
|
_xml->writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
#define REGISTER_METATYPE(type) { int id = qRegisterMetaType<type>("LibXISF::"#type); \
|
#define REGISTER_METATYPE(type) { int id = qRegisterMetaType<type>("LibXISF::"#type); \
|
||||||
typeToId.insert({#type, id}); idToType.insert({id, #type}); }
|
typeToId.insert({#type, id}); idToType.insert({id, #type}); }
|
||||||
|
|
||||||
struct TypesInit
|
#define STRING_ENUM(map, map2, c, e) { map.insert({#e, c::e}); map2.insert({c::e, #e}); }
|
||||||
|
//#define ENUM_STRING(e) {#e, e}
|
||||||
|
|
||||||
|
struct Init
|
||||||
{
|
{
|
||||||
TypesInit()
|
Init()
|
||||||
{
|
{
|
||||||
REGISTER_METATYPE(Boolean);
|
REGISTER_METATYPE(Boolean);
|
||||||
REGISTER_METATYPE(Int8);
|
REGISTER_METATYPE(Int8);
|
||||||
@@ -751,6 +760,34 @@ struct TypesInit
|
|||||||
REGISTER_METATYPE(C64Matrix);
|
REGISTER_METATYPE(C64Matrix);
|
||||||
REGISTER_METATYPE(String);
|
REGISTER_METATYPE(String);
|
||||||
|
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, Bias);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, Dark);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, Flat);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, Light);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, MasterBias);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, MasterDark);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, MasterFlat);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, DefectMap);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, RejectionMapHigh);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, RejectionMapLow);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, BinaryRejectionMapHigh);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, BinaryRejectionMapLow);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, SlopeMap);
|
||||||
|
STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, WeightMap);
|
||||||
|
|
||||||
|
STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, UInt8);
|
||||||
|
STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, UInt16);
|
||||||
|
STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, UInt32);
|
||||||
|
STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, UInt64);
|
||||||
|
STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, Float32);
|
||||||
|
STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, Float64);
|
||||||
|
STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, Complex32);
|
||||||
|
STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, Complex64);
|
||||||
|
|
||||||
|
STRING_ENUM(colorSpaceToEnum, colorSpaceToString, Image, Gray);
|
||||||
|
STRING_ENUM(colorSpaceToEnum, colorSpaceToString, Image, RGB);
|
||||||
|
STRING_ENUM(colorSpaceToEnum, colorSpaceToString, Image, CIELab);
|
||||||
|
|
||||||
QMetaType::registerConverter<Complex32, QString>([](const Complex32 &c){ return QString("(%1,%2)").arg(c.real).arg(c.imag); });
|
QMetaType::registerConverter<Complex32, QString>([](const Complex32 &c){ return QString("(%1,%2)").arg(c.real).arg(c.imag); });
|
||||||
QMetaType::registerConverter<Complex64, QString>([](const Complex64 &c){ return QString("(%1,%2)").arg(c.real).arg(c.imag); });
|
QMetaType::registerConverter<Complex64, QString>([](const Complex64 &c){ return QString("(%1,%2)").arg(c.real).arg(c.imag); });
|
||||||
QMetaType::registerConverter<QString, Complex32>([](QString s)
|
QMetaType::registerConverter<QString, Complex32>([](QString s)
|
||||||
@@ -776,6 +813,6 @@ struct TypesInit
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static TypesInit typesInit;
|
static Init init;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ struct Property
|
|||||||
QVariant value;
|
QVariant value;
|
||||||
QString comment;
|
QString comment;
|
||||||
QString format;
|
QString format;
|
||||||
|
|
||||||
|
Property() = default;
|
||||||
|
Property(const Property &) = default;
|
||||||
|
Property(const QString &_id, const char *_value);
|
||||||
|
template<typename T>
|
||||||
|
Property(const QString &_id, const T& _value) :
|
||||||
|
id(_id),
|
||||||
|
value(QVariant::fromValue<T>(_value)){}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Image
|
struct Image
|
||||||
@@ -102,7 +110,7 @@ struct Image
|
|||||||
|
|
||||||
uint64_t width = 0;
|
uint64_t width = 0;
|
||||||
uint64_t height = 0;
|
uint64_t height = 0;
|
||||||
uint64_t channelCount = 0;
|
uint64_t channelCount = 1;
|
||||||
double bounds[2] = {0.0, 1.0};
|
double bounds[2] = {0.0, 1.0};
|
||||||
Type imageType = Light;
|
Type imageType = Light;
|
||||||
PixelStorage pixelStorage = Planar;
|
PixelStorage pixelStorage = Planar;
|
||||||
@@ -115,9 +123,13 @@ struct Image
|
|||||||
void convertPixelStorageTo(PixelStorage storage);
|
void convertPixelStorageTo(PixelStorage storage);
|
||||||
|
|
||||||
static Type imageTypeEnum(const QString &type);
|
static Type imageTypeEnum(const QString &type);
|
||||||
|
static QString imageTypeString(Type type);
|
||||||
static PixelStorage pixelStorageEnum(const QString &storage);
|
static PixelStorage pixelStorageEnum(const QString &storage);
|
||||||
|
static QString pixelStorageString(PixelStorage storage);
|
||||||
static SampleFormat sampleFormatEnum(const QString &format);
|
static SampleFormat sampleFormatEnum(const QString &format);
|
||||||
|
static QString sampleFormatString(SampleFormat format);
|
||||||
static ColorSpace colorSpaceEnum(const QString &colorSpace);
|
static ColorSpace colorSpaceEnum(const QString &colorSpace);
|
||||||
|
static QString colorSpaceString(ColorSpace colorSpace);
|
||||||
};
|
};
|
||||||
|
|
||||||
class LIBXISF_EXPORT XISFReader
|
class LIBXISF_EXPORT XISFReader
|
||||||
@@ -126,6 +138,7 @@ public:
|
|||||||
XISFReader();
|
XISFReader();
|
||||||
void open(const QString &name);
|
void open(const QString &name);
|
||||||
void open(const QByteArray &data);
|
void open(const QByteArray &data);
|
||||||
|
/** Open image from */
|
||||||
void open(QIODevice *io);
|
void open(QIODevice *io);
|
||||||
void close();
|
void close();
|
||||||
int imagesCount() const;
|
int imagesCount() const;
|
||||||
@@ -152,6 +165,7 @@ public:
|
|||||||
void save(const QString &name);
|
void save(const QString &name);
|
||||||
void save(QByteArray &data);
|
void save(QByteArray &data);
|
||||||
void save(QIODevice &io);
|
void save(QIODevice &io);
|
||||||
|
void saveXML(const QString &name);
|
||||||
void writeImage(const Image &image);
|
void writeImage(const Image &image);
|
||||||
private:
|
private:
|
||||||
void writeHeader();
|
void writeHeader();
|
||||||
@@ -224,9 +238,17 @@ typedef Matrix<Complex32> C32Matrix;
|
|||||||
typedef Matrix<Complex64> C64Matrix;
|
typedef Matrix<Complex64> C64Matrix;
|
||||||
typedef QString String;
|
typedef QString String;
|
||||||
|
|
||||||
}
|
class LIBXISF_EXPORT Error : public std::exception
|
||||||
|
{
|
||||||
|
std::string _msg;
|
||||||
|
public:
|
||||||
|
Error() = default;
|
||||||
|
explicit Error(const char *msg) : Error(std::string(msg)) {}
|
||||||
|
explicit Error(const std::string &msg) : std::exception(), _msg(msg) {}
|
||||||
|
const char* what() const noexcept { return _msg.c_str(); }
|
||||||
|
};
|
||||||
|
|
||||||
QDebug operator<<(QDebug dbg, const LibXISF::Complex32 &c);
|
}
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(LibXISF::Boolean);
|
Q_DECLARE_METATYPE(LibXISF::Boolean);
|
||||||
Q_DECLARE_METATYPE(LibXISF::Int8);
|
Q_DECLARE_METATYPE(LibXISF::Int8);
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/************************************************************************
|
||||||
|
* LibXISF - library to load and save XISF files *
|
||||||
|
* Copyright (C) 2023 Dušan Poizl *
|
||||||
|
* *
|
||||||
|
* This program is free software: you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or *
|
||||||
|
* (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This program is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* GNU General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU General Public License *
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
|
||||||
|
************************************************************************/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include "libxisf.h"
|
||||||
|
|
||||||
|
using namespace LibXISF;
|
||||||
|
|
||||||
|
#define TEST(cond, msg) if(cond){ std::cerr << msg << std::endl; return 1; }
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
XISFWriter writer;
|
||||||
|
Image image;
|
||||||
|
image.width = 5;
|
||||||
|
image.height = 7;
|
||||||
|
image.imageType = Image::Light;
|
||||||
|
image.dataBlock.data.resize(image.width*image.height*2);
|
||||||
|
image.properties.push_back(Property("PropertyString", "Hello XISF"));
|
||||||
|
image.properties.push_back(Property("PropertyBoolean", (Boolean)true));
|
||||||
|
image.properties.push_back(Property("PropertyInt8", (Int8)(8)));
|
||||||
|
image.properties.push_back(Property("PropertyInt16", (Int16)16));
|
||||||
|
image.properties.push_back(Property("PropertyInt32", 32));
|
||||||
|
image.properties.push_back(Property("PropertyUInt8", (UInt8)8));
|
||||||
|
image.properties.push_back(Property("PropertyUInt16", (UInt16)(16)));
|
||||||
|
image.properties.push_back(Property("PropertyUInt32", (uint32_t)32));
|
||||||
|
image.properties.push_back(Property("PropertyFloat32", (Float32) 0.32));
|
||||||
|
image.properties.push_back(Property("PropertyFloat64", (Float64) 0.64));
|
||||||
|
image.properties.push_back(Property("PropertyComplex32", Complex32{3.0, -2.0}));
|
||||||
|
image.properties.push_back(Property("PropertyComplex64", Complex64{-3.0, 2.0}));
|
||||||
|
writer.writeImage(image);
|
||||||
|
|
||||||
|
image.imageType = Image::Flat;
|
||||||
|
image.dataBlock.codec = DataBlock::LZ4;
|
||||||
|
image.dataBlock.byteShuffling = 2;
|
||||||
|
writer.writeImage(image);
|
||||||
|
QByteArray data;
|
||||||
|
std::cout << "Saving image" << std::endl;
|
||||||
|
writer.save(data);
|
||||||
|
|
||||||
|
XISFReader reader;
|
||||||
|
std::cout << "Loading image" << std::endl;
|
||||||
|
reader.open(data);
|
||||||
|
const Image &img0 = reader.getImage(0);
|
||||||
|
const Image &img1 = reader.getImage(1);
|
||||||
|
TEST(image.properties.size() != img0.properties.size(), "Property count doesn't match");
|
||||||
|
TEST(image.dataBlock.data != img0.dataBlock.data, "Images doesn't match");
|
||||||
|
TEST(img0.dataBlock.data != img1.dataBlock.data, "Images doesn't match");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LibXISF::XISFReader reader;
|
||||||
|
reader.open(QString(argv[1]));
|
||||||
|
TEST(reader.imagesCount() != 1, "No image");
|
||||||
|
|
||||||
|
const LibXISF::Image &image = reader.getImage(0);
|
||||||
|
TEST(image.width != 8, "Invalid width")
|
||||||
|
TEST(image.height != 10, "Invalid height");
|
||||||
|
TEST(image.colorSpace != LibXISF::Image::Gray, "Invalid color space");
|
||||||
|
TEST(image.pixelStorage != LibXISF::Image::Planar, "Invalid pixel storage");
|
||||||
|
//TEST(image.dataBlock.codec != LibXISF::DataBlock::None, "Invalid compression codec");
|
||||||
|
TEST(!image.dataBlock.embedded, "Not embedded");
|
||||||
|
TEST(image.dataBlock.data.size() != 80*2, "Invalid data size");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const LibXISF::Error &e)
|
||||||
|
{
|
||||||
|
std::cout << e.what() << std::endl;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user