Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libXISF for openSUSE:Factory checked in at 2026-10-22 17:00:14 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libXISF (Old) and /work/SRC/openSUSE:Factory/.libXISF.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libXISF" Thu Oct 22 17:00:14 2026 rev:16 rq:1387053 version:0.2.14+git1.fa35ae2 Changes: -------- --- /work/SRC/openSUSE:Factory/libXISF/libXISF.changes 2025-12-05 16:58:06.828115570 +0100 +++ /work/SRC/openSUSE:Factory/.libXISF.new.2252680/libXISF.changes 2026-10-22 17:00:18.388989386 +0200 @@ -1,0 +2,10 @@ +Thu Oct 22 06:50:15 UTC 2026 - Paolo Stivanin <[email protected]> + +- Update to version 0.2.14+git1.fa35ae2: + * Add XISFModify::removeProperty() + * Use CROTA to calculate CD_i_j if it is not available + * Add support for AstrometricSolution + * Bug fixes from Pleiades Astrophoto + * Add std::filesystem::path methods variant + +------------------------------------------------------------------- Old: ---- libXISF-0.2.13+git5.7b70b6a.tar.xz New: ---- libXISF-0.2.14+git1.fa35ae2.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libXISF.spec ++++++ --- /var/tmp/diff_new_pack.PGi1TN/_old 2026-10-22 17:00:20.685085412 +0200 +++ /var/tmp/diff_new_pack.PGi1TN/_new 2026-10-22 17:00:20.687085495 +0200 @@ -1,7 +1,7 @@ # # spec file for package libXISF # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -21,10 +21,10 @@ %endif %define sover 0 -%define full_sover 0.2.13 +%define full_sover 0.2.14 Name: libXISF -Version: 0.2.13+git5.7b70b6a +Version: 0.2.14+git1.fa35ae2 Release: 0 Summary: Library to read/write PixInsight XISF files License: GPL-3.0-or-later ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.PGi1TN/_old 2026-10-22 17:00:20.733087419 +0200 +++ /var/tmp/diff_new_pack.PGi1TN/_new 2026-10-22 17:00:20.738087628 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://gitea.nouspiro.space/nou/libXISF</param> - <param name="changesrevision">7b70b6a08183241aad4fd825f16b75a391866a65</param></service></servicedata> + <param name="changesrevision">fa35ae21a8c0f05a915e47a1a3300877f33c5581</param></service></servicedata> (No newline at EOF) ++++++ libXISF-0.2.13+git5.7b70b6a.tar.xz -> libXISF-0.2.14+git1.fa35ae2.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libXISF-0.2.13+git5.7b70b6a/CMakeLists.txt new/libXISF-0.2.14+git1.fa35ae2/CMakeLists.txt --- old/libXISF-0.2.13+git5.7b70b6a/CMakeLists.txt 2025-11-02 23:00:46.000000000 +0100 +++ new/libXISF-0.2.14+git1.fa35ae2/CMakeLists.txt 2026-10-21 23:02:00.000000000 +0200 @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(libXISF VERSION 0.2.13 LANGUAGES CXX C +project(libXISF VERSION 0.2.14 LANGUAGES CXX C HOMEPAGE_URL https://gitea.nouspiro.space/nou/libXISF DESCRIPTION "LibXISF is C++ library that can read and write XISF files produced by PixInsight.") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libXISF-0.2.13+git5.7b70b6a/bytearray.cpp new/libXISF-0.2.14+git1.fa35ae2/bytearray.cpp --- old/libXISF-0.2.13+git5.7b70b6a/bytearray.cpp 2025-11-02 23:00:46.000000000 +0100 +++ new/libXISF-0.2.14+git1.fa35ae2/bytearray.cpp 2026-10-21 23:02:00.000000000 +0200 @@ -59,6 +59,13 @@ return (*_data)[i]; } +ByteArray& ByteArray::operator=(const ByteArray &d) +{ + if(this != &d) + _data = d._data; + return *this; +} + size_t ByteArray::size() const { return _data->size(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libXISF-0.2.13+git5.7b70b6a/libxisf.cpp new/libXISF-0.2.14+git1.fa35ae2/libxisf.cpp --- old/libXISF-0.2.13+git5.7b70b6a/libxisf.cpp 2025-11-02 23:00:46.000000000 +0100 +++ new/libXISF-0.2.14+git1.fa35ae2/libxisf.cpp 2026-10-21 23:02:00.000000000 +0200 @@ -17,21 +17,22 @@ ************************************************************************/ #include "libxisf.h" -#include <unordered_map> -#include <unordered_set> +#include "streambuffer.h" +#include <algorithm> +#include <cmath> +#include <cstdlib> #include <cstring> +#include <fstream> #include <iostream> -#include <sstream> -#include <cstdlib> -#include <cmath> #include <lz4.h> #include <lz4hc.h> #include <pugixml.hpp> +#include <sstream> +#include <unordered_map> #include <zlib.h> #ifdef HAVE_ZSTD #include <zstd.h> #endif -#include "streambuffer.h" namespace LibXISF { @@ -161,18 +162,36 @@ off = byteUnshuffleSSE<2>(input.data(), output.data(), num); #endif - const char *s = input.constData() + off; for(int i=0; i<itemSize; i++) { + const char *s = input.constData() + i * num + off / itemSize; char *u = output.data() + i + off; for(size_t o = off / itemSize; o < num; o++, s++, u += itemSize) *u = *s; } - memcpy(output.data() + num * itemSize, s, input.size() % itemSize); + memcpy(output.data() + num * itemSize, input.constData() + num * itemSize, input.size() % itemSize); data = output; } } +/* + * XISF 1.0 Revision 2, section 10.6.1. A subblock whose compressed and + * uncompressed lengths are equal is an uncompressed subblock, unless it + * decompresses to exactly its uncompressed length (encoders implementing + * earlier revisions could generate such subblocks). Every other subblock must + * decompress to exactly its uncompressed length. + */ +static void uncompressedSubblock(bool decompressed, const std::pair<uint64_t, uint64_t> &block, + const char *srcPtr, char *dstPtr, const char *errorMessage) +{ + if(!decompressed) + { + if(block.first != block.second) + throw Error(errorMessage); + std::memcpy(dstPtr, srcPtr, block.second); + } +} + void DataBlock::decompress(const ByteArray &input, const String &encoding) { ByteArray tmp = input; @@ -198,7 +217,8 @@ for(auto &block : subblocks) { uLongf size = block.second; - ::uncompress((Bytef*)dstPtr, &size, (const Bytef*)srcPtr, block.first); + bool ok = ::uncompress((Bytef*)dstPtr, &size, (const Bytef*)srcPtr, block.first) == Z_OK && size == block.second; + uncompressedSubblock(ok, block, srcPtr, dstPtr, "Zlib decompression failed"); srcPtr += block.first; dstPtr += block.second; } @@ -212,8 +232,8 @@ char *dstPtr = data.data(); for(auto &block : subblocks) { - if(LZ4_decompress_safe(srcPtr, dstPtr, block.first, block.second) < 0) - throw Error("LZ4 decompression failed"); + bool ok = LZ4_decompress_safe(srcPtr, dstPtr, block.first, block.second) == static_cast<int>(block.second); + uncompressedSubblock(ok, block, srcPtr, dstPtr, "LZ4 decompression failed"); srcPtr += block.first; dstPtr += block.second; } @@ -227,8 +247,9 @@ char *dstPtr = data.data(); for(auto &block : subblocks) { - if(ZSTD_isError(ZSTD_decompress(dstPtr, block.second, srcPtr, block.first))) - throw Error("ZSTD decompression failed"); + size_t size = ZSTD_decompress(dstPtr, block.second, srcPtr, block.first); + bool ok = !ZSTD_isError(size) && size == block.second; + uncompressedSubblock(ok, block, srcPtr, dstPtr, "ZSTD decompression failed"); srcPtr += block.first; dstPtr += block.second; } @@ -507,6 +528,58 @@ return false; } +bool Image::convertWCSToAstrometricSolution() +{ + auto findKeyword = [this](const String &key, String &value) -> bool { + auto keyEqual = [&key](const FITSKeyword &entry) { + return entry.name == key; + }; + auto it = std::find_if(_fitsKeywords.begin(), _fitsKeywords.end(), keyEqual); + if(it != _fitsKeywords.end()) + { + value = it->value; + return true; + } + return false; + }; + String crval1, crval2, crpix1, crpix2, cd1_1, cd1_2, cd2_1, cd2_2, ctype1, ctype2, crota1, cdelt1, cdelt2; + bool cdFound = findKeyword("CD1_1", cd1_1) && findKeyword("CD1_2", cd1_2) && findKeyword("CD2_1", cd2_1) && findKeyword("CD2_2", cd2_2); + bool crotaFound = findKeyword("CROTA1", crota1) && findKeyword("CDELT1", cdelt1) && findKeyword("CDELT2", cdelt2); + if(findKeyword("CRVAL1", crval1) && findKeyword("CRVAL2", crval2) && + findKeyword("CRPIX1", crpix1) && findKeyword("CRPIX2", crpix2) && + (cdFound || crotaFound) && + findKeyword("CTYPE1", ctype1) && findKeyword("CTYPE2", ctype2)) + { + Property ver("AstrometricSolution:Version", "1.0"); + Property ps("AstrometricSolution:ProjectionSystem", "Gnomonic"); + Property rcc("AstrometricSolution:ReferenceCelestialCoordinates", F64Vector{std::stod(crval1), std::stod(crval2)}); + Property ric("AstrometricSolution:ReferenceImageCoordinates", F64Vector{std::stod(crpix1), std::stod(crpix2)}); + F64Matrix mx(2, 2); + if(cdFound) + { + mx(0, 0) = std::stod(cd1_1); mx(0, 1) = std::stod(cd1_2); mx(1, 0) = std::stod(cd2_1); mx(1, 1) = std::stod(cd2_2); + } + if(crotaFound) + { + double cdeltx = std::stod(cdelt1); + double cdelty = std::stod(cdelt2); + double rotationRad = std::stod(crota1) / 180.0 * M_PI; + mx(0, 0) = std::cos(rotationRad) * cdeltx; + mx(0, 1) =-std::sin(rotationRad) * cdelty; + mx(1, 0) = std::sin(rotationRad) * cdeltx; + mx(1, 1) = std::cos(rotationRad) * cdelty; + } + Property ltm("AstrometricSolution:LinearTransformationMatrix", mx); + updateProperty(ver); + updateProperty(ps); + updateProperty(rcc); + updateProperty(ric); + updateProperty(ltm); + return true; + } + return false; +} + const ByteArray &Image::iccProfile() const { return _iccProfile; @@ -701,6 +774,7 @@ void open(const ByteArray &data); /** Open image from istream. This method takes ownership of *io pointer */ void open(std::istream *io); + void open(const std::filesystem::path &path); /** Close opended file release all data. */ void close(); /** Return number of images inside file */ @@ -754,6 +828,14 @@ readXISFHeader(); } +void XISFReaderPrivate::open(const std::filesystem::path &path) +{ + close(); + _io = std::make_unique<std::ifstream>(path, std::ios_base::in | std::ios_base::binary); + readSignature(); + readXISFHeader(); +} + void XISFReaderPrivate::close() { _io.reset(); @@ -1032,6 +1114,7 @@ void save(const String &name); void save(ByteArray &data); void save(std::ostream &io); + void save(const std::filesystem::path &path); void writeImage(const Image &image); static void writeFITSKeyword(pugi::xml_node &node, const FITSKeyword &keyword); static void writePropertyElement(pugi::xml_node &node, const Property &property); @@ -1084,6 +1167,16 @@ } } +void XISFWriterPrivate::save(const std::filesystem::path &path) +{ + std::ofstream fw(path, std::ios_base::out | std::ios_base::binary); + + if(fw.fail()) + throw Error("Failed to open file"); + + save(fw); +} + void XISFWriterPrivate::writeImage(const Image &image) { _images.push_back(image); @@ -1304,6 +1397,11 @@ p->open(io); } +void XISFReader::open(const std::filesystem::path &path) +{ + p->open(path); +} + void XISFReader::close() { p->close(); @@ -1349,6 +1447,11 @@ p->save(io); } +void XISFWriter::save(const std::filesystem::path &path) +{ + p->save(path); +} + void XISFWriter::writeImage(const Image &image) { p->writeImage(image); @@ -1361,17 +1464,20 @@ void open(const ByteArray &data); /** Open image from istream. This method takes ownership of *io pointer */ void open(std::istream *io); + void open(const std::filesystem::path &path); /** Close opended file release all data. */ void close(); void save(const String &name); void save(ByteArray &data); void save(std::ostream &io); + void save(const std::filesystem::path &path); void addFITSKeyword(uint32_t image, const FITSKeyword &keyword); void updateFITSKeyword(uint32_t image, const FITSKeyword &keyword, bool add); void removeFITSKeyword(uint32_t image, const String &name); void updateProperty(uint32_t image, const Property &property); + void removeProperty(uint32_t image, const String &id); private: void readXISFHeader(); void parseAttachmentPos(pugi::xml_node &root); @@ -1408,6 +1514,13 @@ readXISFHeader(); } +void XISFModifyPrivate::open(const std::filesystem::path &path) +{ + close(); + _io = std::make_unique<std::ifstream>(path, std::ios_base::in | std::ios_base::binary); + readXISFHeader(); +} + void XISFModifyPrivate::close() { _io.reset(); @@ -1487,6 +1600,16 @@ } } +void XISFModifyPrivate::save(const std::filesystem::path &path) +{ + std::ofstream fw(path, std::ios_base::out | std::ios_base::binary); + + if(fw.fail()) + throw Error("Failed to open file"); + + save(fw); +} + void XISFModifyPrivate::addFITSKeyword(uint32_t image, const FITSKeyword &keyword) { if(!_root) @@ -1574,6 +1697,25 @@ XISFWriterPrivate::writePropertyElement(imageNode, property); } +void XISFModifyPrivate::removeProperty(uint32_t image, const String &id) +{ + if(!_root) + throw Error("No input file opened"); + + pugi::xpath_node_set images = _root.select_nodes("//Image"); + + if(image >= images.size()) + throw Error("Out of bounds"); + + pugi::xpath_variable_set variables; + variables.set("id", id.c_str()); + pugi::xml_node imageNode = images[image].node(); + pugi::xml_node propertyNode = imageNode.select_node("Property[@id=string($id)]", &variables).node(); + + if(propertyNode) + imageNode.remove_child(propertyNode); +} + void XISFModifyPrivate::readXISFHeader() { char signature[8]; @@ -1657,6 +1799,11 @@ p->open(io); } +void XISFModify::open(const std::filesystem::path &path) +{ + p->open(path); +} + void XISFModify::close() { p->close(); @@ -1677,6 +1824,11 @@ p->save(io); } +void XISFModify::save(const std::filesystem::path &path) +{ + p->save(path); +} + void XISFModify::addFITSKeyword(uint32_t image, const FITSKeyword &keyword) { p->addFITSKeyword(image, keyword); @@ -1697,6 +1849,11 @@ p->updateProperty(image, property); } +void XISFModify::removeProperty(uint32_t image, const String &id) +{ + p->removeProperty(image, id); +} + #define STRING_ENUM(map, map2, c, e) { map.insert({#e, c::e}); map2.insert({c::e, #e}); } struct Init diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libXISF-0.2.13+git5.7b70b6a/libxisf.h new/libXISF-0.2.14+git1.fa35ae2/libxisf.h --- old/libXISF-0.2.13+git5.7b70b6a/libxisf.h 2025-11-02 23:00:46.000000000 +0100 +++ new/libXISF-0.2.14+git1.fa35ae2/libxisf.h 2026-10-21 23:02:00.000000000 +0200 @@ -20,15 +20,15 @@ #define LIBXISF_H #include "libXISF_global.h" -#include <memory> +#include <cstdint> +#include <cstring> +#include <ctime> +#include <filesystem> #include <map> +#include <memory> +#include <memory> #include <variant> -#include <fstream> -#include <cstring> #include <vector> -#include <cstdint> -#include <memory> -#include <ctime> namespace LibXISF { @@ -56,9 +56,10 @@ ByteArray(const ByteArray &d); char& operator[](size_t i); const char& operator[](size_t i) const; - char* data() { return &_data->at(0); } - const char* data() const { return &_data->at(0); } - const char* constData() const { return &_data->at(0); } + ByteArray& operator=(const ByteArray &d); + char* data() { return size() ? _data->data() : nullptr; } + const char* data() const { return size() ? _data->data() : nullptr; } + const char* constData() const { return size() ? _data->data() : nullptr; } size_t size() const; void resize(size_t newsize); void append(char c); @@ -204,6 +205,8 @@ Property(const String &_id, const T& _value) : id(_id), value(_value){} + + Property& operator=(const Property &d) = default; }; struct LIBXISF_EXPORT FITSKeyword @@ -306,6 +309,11 @@ * For example OBSERVER => Observer:Name, SITELAT => Observation:Location:Latitude */ bool addFITSKeywordAsProperty(const String &name, const String &value); + /** + * Convert FITS WCS keywords added previosly with addFITSKeyword() to XISF properties AstrometricSolution + * https://pixinsight.com/doc/docs/XISF-1.0-spec/XISF-1.0-spec.html#__XISF_Core_Elements_:_Image_Core_Element_:_Astronomical_Image_Properties_:_AstrometricSolution_Namespace__ + */ + bool convertWCSToAstrometricSolution(); const ByteArray& iccProfile() const; void setIccProfile(const ByteArray &iccProfile); @@ -370,6 +378,7 @@ void open(const ByteArray &data); /** Open image from istream. This method takes ownership of *io pointer */ void open(std::istream *io); + void open(const std::filesystem::path &path); /** Close opended file release all data. */ void close(); /** Return number of images inside file */ @@ -396,6 +405,7 @@ void save(const String &name); void save(ByteArray &data); void save(std::ostream &io); + void save(const std::filesystem::path &path); void writeImage(const Image &image); private: XISFWriterPrivate *p; @@ -419,10 +429,12 @@ void open(const String &name); void open(const ByteArray &data); void open(std::istream *io); + void open(const std::filesystem::path &path); void close(); void save(const String &name); void save(ByteArray &data); void save(std::ostream &io); + void save(const std::filesystem::path &path); /** * @brief addFITSKeyword append new keyword to image @@ -449,6 +461,12 @@ * @param property new value of property */ void updateProperty(uint32_t image, const Property &property); + /** + * @brief removeProperty remove existing property + * @param image index of image to update + * @param id of property to remove + */ + void removeProperty(uint32_t image, const String &id); private: XISFModifyPrivate *p; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libXISF-0.2.13+git5.7b70b6a/test/main.cpp new/libXISF-0.2.14+git1.fa35ae2/test/main.cpp --- old/libXISF-0.2.13+git5.7b70b6a/test/main.cpp 2025-11-02 23:00:46.000000000 +0100 +++ new/libXISF-0.2.14+git1.fa35ae2/test/main.cpp 2026-10-21 23:02:00.000000000 +0200 @@ -48,12 +48,19 @@ image.addProperty(Property("PropertyComplex64", Complex64{-3.0, 2.0})); image.addProperty(Property("VectorUInt16", UI16Vector({23, 45, 86}))); image.addProperty(Property("VectorComplex32", C32Vector({{1, 2}, {3, 4}, {5, 6}}))); + image.addProperty(Property("Vector64", F64Vector({1.0, 2.0, 3.0}))); + image.addProperty(Property("Vector64Empty", F64Vector())); UI16Matrix m(2, 3); m(0, 0) = 0; m(0, 1) = 1; m(0, 2) = 2; m(1, 0) = 10; image.addProperty(Property("UI16Matrix", m)); + uint16_t *imageData = image.imageData<uint16_t>(); + for(uint64_t i = 0; i < image.height() * image.width(); i++) + { + imageData[i] = i; + } std::tm tm = {12, 22, 23, 1, 2, 2023, 0, 0, 0}; image.addProperty(Property("TimeObs", tm)); image.addFITSKeyword({"RA", "226.9751163116387", "Right ascension of the center of the image (deg)"}); @@ -102,7 +109,7 @@ else { LibXISF::XISFReader reader; - reader.open(argv[1]); + reader.open(LibXISF::String(argv[1])); TEST(reader.imagesCount() != 1, "No image"); const LibXISF::Image &image = reader.getImage(0); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libXISF-0.2.13+git5.7b70b6a/variant.cpp new/libXISF-0.2.14+git1.fa35ae2/variant.cpp --- old/libXISF-0.2.13+git5.7b70b6a/variant.cpp 2025-11-02 23:00:46.000000000 +0100 +++ new/libXISF-0.2.14+git1.fa35ae2/variant.cpp 2026-10-21 23:02:00.000000000 +0200 @@ -139,7 +139,8 @@ v = T(); v.value<T>().resize(len); size_t size = len * sizeof(typename T::value_type); - std::memcpy(&v.value<T>()[0], data.data(), size); + if(size) + std::memcpy(&v.value<T>()[0], data.data(), size); } template<typename T> @@ -147,7 +148,8 @@ { v = T(rows, cols); size_t size = rows * cols * sizeof(typename T::value_type); - std::memcpy(&v.value<T>()(0, 0), data.data(), size); + if(size) + std::memcpy(&v.value<T>()(0, 0), data.data(), size); } template<typename T> @@ -173,7 +175,8 @@ len = v.value<T>().size(); size_t size = len * sizeof(typename T::value_type); data.resize(size); - std::memcpy(data.data(), &v.value<T>()[0], size); + if(size) + std::memcpy(data.data(), &v.value<T>()[0], size); data.encodeBase64(); data.append('\0'); } @@ -185,7 +188,8 @@ cols = v.value<T>().cols(); size_t size = rows * cols * sizeof(typename T::value_type); data.resize(size); - std::memcpy(data.data(), &v.value<T>()(0, 0), size); + if(size) + std::memcpy(data.data(), &v.value<T>()(0, 0), size); data.encodeBase64(); data.append('\0'); }
