From d5e10ad31f6eb13f0f71ee8a6bd0c93d399d67f9 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 9 Aug 2026 17:48:19 -0700 Subject: [PATCH 1/5] fix(fits): read/write color FITS image using the correct convention FITS has no formal notion of color channels, just numbered axes, and OIIO's guess for a 3D color image (NAXIS1 = nchannels, interleaved) was wrong: research while investigating issue #5384 turned up no real-world FITS software using that layout. The universal convention is NAXIS3 = nchannels, one full-resolution plane per channel, the same layout astronomical software uses for spectral and Stokes cubes. Reader and writer now both use only that convention. The old NAXIS1-interleaved guess is dropped entirely rather than kept for compatibility: nothing but OIIO itself ever produced or consumed it, and it silently misread genuine external color cubes as volumes, which was the actual bug in #5384. Such old files now read as a plain grayscale volume instead. We feel comfortable with that choice because (a) no software but OIIO wrote the other convention, (b) no other software could have properly read or displayed the incorrect files OIIO was writing, yet nobody ever pointed out the error or complained in any way until #5384. I am interpreting that as very strong circumstantial evidence that there are none of these wrong image OIIO produced that people still need to read with the wrong conventions. Also fix stale, meaningless channel names, and add real names where the header supports it (STOKES axis codes, or informal FILTERn/BANDn keywords). If there are multiple channels and none of these conventions are apparent, default to just calling it R, G, B. Document the convention and channel-naming rules in builtinplugins.rst. For the rarer NAXIS=4 (volume+color) case, apply the same pattern: the channel axis is NAXIS4, the slowest-varying one, not NAXIS1. There's no known real-world convention to confirm this against like there was for NAXIS=3, but it's the natural extension of the same reasoning. Fixes #5384 Assisted-by: Claude Code / Sonnet 5 Signed-off-by: Larry Gritz --- src/doc/builtinplugins.rst | 31 +++++- src/fits.imageio/fits_pvt.h | 15 +++ src/fits.imageio/fitsinput.cpp | 167 +++++++++++++++++++++++++++----- src/fits.imageio/fitsoutput.cpp | 50 +++++++--- 4 files changed, 223 insertions(+), 40 deletions(-) diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index d77ee5c652..402040aa9f 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -522,10 +522,33 @@ found at: http://fits.gsfc.nasa.gov/ OpenImageIO supports multiple images in FITS files, and supports the following pixel data types: UINT8, UINT16, UINT32, FLOAT, DOUBLE. -FITS files can store various kinds of arbitrary data arrays, but -OpenImageIO's support of FITS is mostly limited using FITS for image -storage. Currently, OpenImageIO only supports 2D FITS data (images), not 3D -(volume) data, nor 1-D or higher-dimensional arrays. +**Inferring dimensions and color channels** + +FITS files can store arbitrary N-dimensional data arrays (``NAXIS`` / +``NAXISn``), but say nothing about which axes, if any, are "color." +OpenImageIO infers a role for each axis from ``NAXIS`` (trailing axes of +length 1 are dropped first): + +* ``NAXIS`` = 1: a single row (width = NAXIS1, height = 1). +* ``NAXIS`` = 2: an ordinary 2D grayscale image (width = NAXIS1, height = NAXIS2). +* ``NAXIS`` = 3: if NAXIS3 <= 4, a color image, stored as one full-resolution + plane per channel (width = NAXIS1, height = NAXIS2, NAXIS3 = nchannels). + Otherwise, if NAXIS3 is more than 3, the file is interpreted as a grayscale + volume (width = NAXIS1, height = NAXIS2, depth = NAXIS3). +* ``NAXIS`` = 4: a color volume (width = NAXIS1, height = NAXIS2, depth = + NAXIS3, NAXIS4 = nchannels, again one full-resolution volume per channel). + +This is only a heuristic -- FITS has no way to formally declare an axis as +"color" -- so a genuinely non-color NAXIS3 that happens to be <= 4 long can be +misclassified as color. + +Channel names default to the usual OpenImageIO conventions (``R,G,B``, +``R,G,B,A``, ``Y``, etc.), improved when the header says more: if the +channel axis is a FITS WCS ``STOKES`` axis, channels are named from the +standard Stokes codes (``I``, ``Q``, ``U``, ``V``, ``RR``, ``LL``, ``RL``, +``LR``, ``pp``, ``qq``, ``pq``, ``qp``); otherwise, if every plane has a +``FILTERn`` or ``BANDn`` keyword (an informal convention, not part of the +FITS standard), those values are used instead. diff --git a/src/fits.imageio/fits_pvt.h b/src/fits.imageio/fits_pvt.h index aa27bbbcf5..88d4d592f6 100644 --- a/src/fits.imageio/fits_pvt.h +++ b/src/fits.imageio/fits_pvt.h @@ -114,6 +114,21 @@ class FitsInput final : public ImageInput { // converts date in FITS format (YYYY-MM-DD or DD/MM/YY) // to DateTime format std::string convert_date(const std::string& date); + + // Give m_spec.channelnames sensible values: generic defaults (R,G,B,...), + // improved with real per-plane names when the header tells us what the + // channel axis actually is (a STOKES polarization axis, or informal + // FILTERn/BANDn keywords). + void assign_channel_names(); + + // True if multi-channel pixel data is stored as separate per-channel + // blocks -- one full width x height (x depth, for NAXIS=4) block per + // channel -- rather than interleaved. This is the only multi-channel + // layout we recognize; see the comments in read_fits_header(). + bool planar_channels() const + { + return (m_naxes == 3 || m_naxes == 4) && m_spec.nchannels > 1; + } }; diff --git a/src/fits.imageio/fitsinput.cpp b/src/fits.imageio/fitsinput.cpp index e09e46aa40..adf5712459 100644 --- a/src/fits.imageio/fitsinput.cpp +++ b/src/fits.imageio/fitsinput.cpp @@ -4,11 +4,13 @@ #include +#include #include #include "fits_pvt.h" #include +#include OIIO_PLUGIN_NAMESPACE_BEGIN @@ -100,16 +102,46 @@ FitsInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, return true; std::vector data_tmp(m_spec.scanline_bytes()); - long scanline_off = (m_spec.height - y) * m_spec.scanline_bytes(); - fseek(m_fd, scanline_off, SEEK_CUR); - size_t n = fread(&data_tmp[0], 1, m_spec.scanline_bytes(), m_fd); - if (n != m_spec.scanline_bytes()) { - if (feof(m_fd)) - errorfmt("Hit end of file unexpectedly (offset={}, scanline {})", - ftell(m_fd), y); - else - errorfmt("read error"); - return false; // Read failed + + if (!planar_channels()) { + long scanline_off = (m_spec.height - y) * m_spec.scanline_bytes(); + fseek(m_fd, scanline_off, SEEK_CUR); + size_t n = fread(&data_tmp[0], 1, m_spec.scanline_bytes(), m_fd); + if (n != m_spec.scanline_bytes()) { + if (feof(m_fd)) + errorfmt("Hit end of file unexpectedly (offset={}, scanline {})", + ftell(m_fd), y); + else + errorfmt("read error"); + return false; // Read failed + } + } else { + // Channels are stored as separate contiguous width x height (x depth, + // for NAXIS=4) blocks rather than interleaved, so we must gather one + // row from each block and interleave them into the scanline buffer. + size_t comp_size = m_spec.format.size(); + size_t row_bytes = size_t(m_spec.width) * comp_size; + size_t plane_bytes = row_bytes * size_t(m_spec.height) + * size_t(m_spec.depth); + long row_off = (m_spec.height - y) * long(row_bytes); + std::vector chan_row(row_bytes); + for (int c = 0; c < m_spec.nchannels; ++c) { + fsetpos(m_fd, &m_filepos); + fseek(m_fd, long(c * plane_bytes) + row_off, SEEK_CUR); + size_t n = fread(&chan_row[0], 1, row_bytes, m_fd); + if (n != row_bytes) { + if (feof(m_fd)) + errorfmt( + "Hit end of file unexpectedly (offset={}, scanline {})", + ftell(m_fd), y); + else + errorfmt("read error"); + return false; // Read failed + } + for (int x = 0; x < m_spec.width; ++x) + memcpy(&data_tmp[(size_t(x) * m_spec.nchannels + c) * comp_size], + &chan_row[size_t(x) * comp_size], comp_size); + } } // in FITS image data is stored in big-endian so we have to switch to @@ -199,7 +231,7 @@ FitsInput::set_spec_info() errorfmt("Unsupported FITS BITPIX value {}", m_bitpix); return false; } - if (!check_open(m_spec, { 0, 1 << 20, 0, 1 << 20, 0, 1 << 16, 0, 4 })) + if (!check_open(m_spec, { 0, 1 << 20, 0, 1 << 20, 0, 1 << 16, 0, 12 })) return false; if (!check_compression_ratio(m_spec, Filesystem::file_size(m_filename))) return false; @@ -344,22 +376,29 @@ FitsInput::read_fits_header(void) } else if (m_naxes == 2) { m_spec.width = m_naxis[0]; m_spec.height = m_naxis[1]; - } else if (m_naxes == 3 && m_naxis[0] <= 4) { - // 3D, small number of most-rapidly changing dimension: color image? - m_spec.nchannels = m_naxis[0]; - m_spec.width = m_naxis[1]; - m_spec.height = m_naxis[2]; + } else if (m_naxes == 3 && m_naxis[2] <= 4) { + // 3D, small number of most-slowly changing dimension: color + // image, one full width x height plane per channel (NAXIS3 = + // nchannels). This is the only real-world FITS color-cube + // convention -- see the git history for how we know that. + m_spec.width = m_naxis[0]; + m_spec.height = m_naxis[1]; + m_spec.nchannels = m_naxis[2]; } else if (m_naxes == 3) { - // 3D, large number of most-rapidly changing dimension: volume? + // 3D, no small axis: volume m_spec.width = m_naxis[0]; m_spec.height = m_naxis[1]; m_spec.depth = m_naxis[2]; } else if (m_naxes == 4) { - // 4D... volume + color? - m_spec.nchannels = m_naxis[0]; - m_spec.width = m_naxis[1]; - m_spec.height = m_naxis[2]; - m_spec.depth = m_naxis[3]; + // 4D volume+color: NAXIS1-3 are x,y,z and NAXIS4 = nchannels, + // one full-resolution volume per channel. No real-world + // convention to confirm this against (unlike the 3D color case + // above), but it's the natural extension of the same pattern: + // the slowest-varying axis is the channel axis. + m_spec.width = m_naxis[0]; + m_spec.height = m_naxis[1]; + m_spec.depth = m_naxis[2]; + m_spec.nchannels = m_naxis[3]; } else { errorfmt("Don't know now to read {}-channel FITS image", m_naxes); return false; @@ -369,6 +408,7 @@ FitsInput::read_fits_header(void) m_spec.full_depth = m_spec.depth; m_spec.attribute("oiio:subimages", (int)m_subimages.size()); + assign_channel_names(); // if (m_spec.width < 1 || m_spec.height < 1 || m_spec.depth < 1 || // m_spec.nchannels < 1) { @@ -416,6 +456,89 @@ FitsInput::add_to_spec(const std::string& keyname, const std::string& value) +void +FitsInput::assign_channel_names() +{ + m_spec.default_channel_names(); + if (m_spec.nchannels <= 1) + return; + + // Which physical FITS axis (1-based) is the channel axis, so we know + // which CTYPEn/FILTERn-style keywords describe it? + int axis = 0; + if (m_naxes == 3) + axis = 3; + else if (m_naxes == 4) + axis = 4; // volume+color: channel is the slowest-varying axis + if (!axis) + return; + + // The FITS WCS standard defines a STOKES axis (polarization) with fixed + // integer codes per plane: 1=I, 2=Q, 3=U, 4=V, -1=RR, -2=LL, -3=RL, + // -4=LR, -5=pp, -6=qq, -7=pq, -8=qp. If that's what this axis is, decode + // the per-plane values via the standard linear WCS mapping and use the + // Stokes names as channel names. + std::string ctype = m_spec.get_string_attribute( + Strutil::format("Ctype{}", axis)); + if (Strutil::iequals(ctype, "STOKES")) { + // Standard FITS WCS linear axis mapping and Stokes codes, see: + // https://fits.gsfc.nasa.gov/standard40/fits_standard40aa.pdf + static const std::map stokes_names + = { { 1, "I" }, { 2, "Q" }, { 3, "U" }, { 4, "V" }, + { -1, "RR" }, { -2, "LL" }, { -3, "RL" }, { -4, "LR" }, + { -5, "pp" }, { -6, "qq" }, { -7, "pq" }, { -8, "qp" } }; + float crval + = m_spec.get_float_attribute(Strutil::format("Crval{}", axis), + 1.0f); + float crpix + = m_spec.get_float_attribute(Strutil::format("Crpix{}", axis), + 1.0f); + float cdelt + = m_spec.get_float_attribute(Strutil::format("Cdelt{}", axis), + 1.0f); + bool all_found = true; + std::vector names(m_spec.nchannels); + for (int c = 0; c < m_spec.nchannels && all_found; ++c) { + int code = int(std::lround(crval + (c + 1 - crpix) * cdelt)); + auto find = stokes_names.find(code); + if (find == stokes_names.end()) + all_found = false; + else + names[c] = find->second; + } + if (all_found) { + for (int c = 0; c < m_spec.nchannels; ++c) + m_spec.channelnames[c] = names[c]; + return; + } + } + + // No standard WCS description of the axis. Some pipelines instead tag + // each plane with an informal FILTERn or BANDn keyword (e.g. FILTER1, + // FILTER2, FILTER3 giving the name of the filter used for that plane). + // This isn't part of the FITS standard, just a convention we've seen in + // the wild, so only trust it if every plane has one. + for (const char* prefix : { "Filter", "Band" }) { + std::vector names(m_spec.nchannels); + bool all_found = true; + for (int c = 0; c < m_spec.nchannels && all_found; ++c) { + std::string key = Strutil::format("{}{}", prefix, c + 1); + auto* param = m_spec.find_attribute(key, TypeDesc::STRING); + if (!param) + all_found = false; + else + names[c] = *(const char**)param->data(); + } + if (all_found) { + for (int c = 0; c < m_spec.nchannels; ++c) + m_spec.channelnames[c] = names[c]; + return; + } + } +} + + + void FitsInput::subimage_search() { diff --git a/src/fits.imageio/fitsoutput.cpp b/src/fits.imageio/fitsoutput.cpp index 9c74215b15..b7d9bc7c63 100644 --- a/src/fits.imageio/fitsoutput.cpp +++ b/src/fits.imageio/fitsoutput.cpp @@ -3,6 +3,7 @@ // https://github.com/AcademySoftwareFoundation/OpenImageIO #include +#include #include "fits_pvt.h" @@ -95,10 +96,6 @@ FitsOutput::write_scanline(int y, int /*z*/, TypeDesc format, const void* data, std::vector data_tmp(m_spec.scanline_bytes(), 0); memcpy(&data_tmp[0], data, m_spec.scanline_bytes()); - // computing scanline offset - long scanline_off = (m_spec.height - y) * m_spec.scanline_bytes(); - fseek(m_fd, scanline_off, SEEK_CUR); - // in FITS image data is stored in big-endian so we have to switch to // big-endian on little-endian machines if (littleendian()) { @@ -115,12 +112,37 @@ FitsOutput::write_scanline(int y, int /*z*/, TypeDesc format, const void* data, data_tmp.size() / sizeof(double)); } - size_t byte_count = fwrite(&data_tmp[0], 1, data_tmp.size(), m_fd); + if (m_spec.nchannels == 1) { + // computing scanline offset + long scanline_off = (m_spec.height - y) * m_spec.scanline_bytes(); + fseek(m_fd, scanline_off, SEEK_CUR); + size_t byte_count = fwrite(&data_tmp[0], 1, data_tmp.size(), m_fd); + fsetpos(m_fd, &m_filepos); + return byte_count == data_tmp.size(); + } + // Channels are stored as separate contiguous width x height planes + // (NAXIS3 = nchannels), the only real-world FITS color-cube convention, + // so de-interleave this scanline and write each channel's row into its + // own plane. + size_t comp_size = m_spec.format.size(); + size_t row_bytes = size_t(m_spec.width) * comp_size; + size_t plane_bytes = row_bytes * size_t(m_spec.height); + long row_off = (m_spec.height - y) * long(row_bytes); + std::vector chan_row(row_bytes); + bool ok = true; + for (int c = 0; c < m_spec.nchannels; ++c) { + for (int x = 0; x < m_spec.width; ++x) + memcpy(&chan_row[size_t(x) * comp_size], + &data_tmp[(size_t(x) * m_spec.nchannels + c) * comp_size], + comp_size); + fsetpos(m_fd, &m_filepos); + fseek(m_fd, long(c * plane_bytes) + row_off, SEEK_CUR); + size_t byte_count = fwrite(&chan_row[0], 1, row_bytes, m_fd); + ok &= (byte_count == row_bytes); + } fsetpos(m_fd, &m_filepos); - - //byte_count == data.size --> all written - return byte_count == data_tmp.size(); + return ok; } @@ -264,16 +286,16 @@ FitsOutput::create_basic_header(std::string& header) axes += 1; header += create_card("NAXIS", num2str(axes)); - // now we save NAXIS1 and NAXIS2 - // this keywords represents width and height + // now we save NAXIS1, NAXIS2 (and NAXIS3 for multi-channel images) if (m_spec.nchannels == 1) { header += create_card("NAXIS1", num2str(m_spec.width)); header += create_card("NAXIS2", num2str(m_spec.height)); } else { - // 3D image for color - header += create_card("NAXIS1", num2str(m_spec.nchannels)); - header += create_card("NAXIS2", num2str(m_spec.width)); - header += create_card("NAXIS3", num2str(m_spec.height)); + // One full-resolution plane per channel (NAXIS3 = nchannels), the + // only real-world FITS color-cube convention. + header += create_card("NAXIS1", num2str(m_spec.width)); + header += create_card("NAXIS2", num2str(m_spec.height)); + header += create_card("NAXIS3", num2str(m_spec.nchannels)); } } From 1c641eaea06edb9318b400dad179d39ca73251e0 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 11 Aug 2026 21:31:20 -0700 Subject: [PATCH 2/5] Fix Stokes channel names and documentation Signed-off-by: Larry Gritz --- src/doc/builtinplugins.rst | 8 ++++---- src/fits.imageio/fits_pvt.h | 7 ++++--- src/fits.imageio/fitsinput.cpp | 16 ++++++++-------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 402040aa9f..a38b7422a6 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -532,11 +532,11 @@ length 1 are dropped first): * ``NAXIS`` = 1: a single row (width = NAXIS1, height = 1). * ``NAXIS`` = 2: an ordinary 2D grayscale image (width = NAXIS1, height = NAXIS2). * ``NAXIS`` = 3: if NAXIS3 <= 4, a color image, stored as one full-resolution - plane per channel (width = NAXIS1, height = NAXIS2, NAXIS3 = nchannels). - Otherwise, if NAXIS3 is more than 3, the file is interpreted as a grayscale + plane per channel (width = NAXIS1, height = NAXIS2, nchannels = NAXIS3). + Otherwise, if NAXIS3 is more than 4, the file is interpreted as a grayscale volume (width = NAXIS1, height = NAXIS2, depth = NAXIS3). * ``NAXIS`` = 4: a color volume (width = NAXIS1, height = NAXIS2, depth = - NAXIS3, NAXIS4 = nchannels, again one full-resolution volume per channel). + NAXIS3, nchannels = NAXIS4, again one full-resolution volume per channel). This is only a heuristic -- FITS has no way to formally declare an axis as "color" -- so a genuinely non-color NAXIS3 that happens to be <= 4 long can be @@ -546,7 +546,7 @@ Channel names default to the usual OpenImageIO conventions (``R,G,B``, ``R,G,B,A``, ``Y``, etc.), improved when the header says more: if the channel axis is a FITS WCS ``STOKES`` axis, channels are named from the standard Stokes codes (``I``, ``Q``, ``U``, ``V``, ``RR``, ``LL``, ``RL``, -``LR``, ``pp``, ``qq``, ``pq``, ``qp``); otherwise, if every plane has a +``LR``, ``XX``, ``YY``, ``XY``, ``YX``); otherwise, if every plane has a ``FILTERn`` or ``BANDn`` keyword (an informal convention, not part of the FITS standard), those values are used instead. diff --git a/src/fits.imageio/fits_pvt.h b/src/fits.imageio/fits_pvt.h index 88d4d592f6..5dcfc30e0f 100644 --- a/src/fits.imageio/fits_pvt.h +++ b/src/fits.imageio/fits_pvt.h @@ -122,9 +122,10 @@ class FitsInput final : public ImageInput { void assign_channel_names(); // True if multi-channel pixel data is stored as separate per-channel - // blocks -- one full width x height (x depth, for NAXIS=4) block per - // channel -- rather than interleaved. This is the only multi-channel - // layout we recognize; see the comments in read_fits_header(). + // blocks -- one full width * height (or width * height * depth for + // NAXIS=4) block per channel -- rather than interleaved. This is the only + // multi-channel layout we recognize; see the comments in + // read_fits_header(). bool planar_channels() const { return (m_naxes == 3 || m_naxes == 4) && m_spec.nchannels > 1; diff --git a/src/fits.imageio/fitsinput.cpp b/src/fits.imageio/fitsinput.cpp index adf5712459..3a470180ed 100644 --- a/src/fits.imageio/fitsinput.cpp +++ b/src/fits.imageio/fitsinput.cpp @@ -116,9 +116,10 @@ FitsInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, return false; // Read failed } } else { - // Channels are stored as separate contiguous width x height (x depth, - // for NAXIS=4) blocks rather than interleaved, so we must gather one - // row from each block and interleave them into the scanline buffer. + // Channels are stored as separate contiguous width * height (or + // width * height * depth for NAXIS=4) blocks rather than interleaved, + // so we must gather one row from each block and interleave them into + // the scanline buffer. size_t comp_size = m_spec.format.size(); size_t row_bytes = size_t(m_spec.width) * comp_size; size_t plane_bytes = row_bytes * size_t(m_spec.height) @@ -474,10 +475,9 @@ FitsInput::assign_channel_names() return; // The FITS WCS standard defines a STOKES axis (polarization) with fixed - // integer codes per plane: 1=I, 2=Q, 3=U, 4=V, -1=RR, -2=LL, -3=RL, - // -4=LR, -5=pp, -6=qq, -7=pq, -8=qp. If that's what this axis is, decode - // the per-plane values via the standard linear WCS mapping and use the - // Stokes names as channel names. + // integer codes per plane. If that's what this axis is, decode the + // per-plane values via the standard linear WCS mapping and use the Stokes + // names as channel names. std::string ctype = m_spec.get_string_attribute( Strutil::format("Ctype{}", axis)); if (Strutil::iequals(ctype, "STOKES")) { @@ -486,7 +486,7 @@ FitsInput::assign_channel_names() static const std::map stokes_names = { { 1, "I" }, { 2, "Q" }, { 3, "U" }, { 4, "V" }, { -1, "RR" }, { -2, "LL" }, { -3, "RL" }, { -4, "LR" }, - { -5, "pp" }, { -6, "qq" }, { -7, "pq" }, { -8, "qp" } }; + { -5, "XX" }, { -6, "YY" }, { -7, "XY" }, { -8, "YX" } }; float crval = m_spec.get_float_attribute(Strutil::format("Crval{}", axis), 1.0f); From 55f2cdf56d7c70f9c99ec90f2a1299c3311e6fdd Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 11 Aug 2026 21:54:25 -0700 Subject: [PATCH 3/5] Add color test images Signed-off-by: Larry Gritz --- testsuite/fits/ref/out.txt | 231 ++++++++++++++++++++++++++++++++++++ testsuite/fits/run.py | 4 +- testsuite/fits/src/rgb.fits | 41 +++++++ 3 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 testsuite/fits/src/rgb.fits diff --git a/testsuite/fits/ref/out.txt b/testsuite/fits/ref/out.txt index d42768aeac..7184e01571 100644 --- a/testsuite/fits/ref/out.txt +++ b/testsuite/fits/ref/out.txt @@ -491,6 +491,37 @@ AIPS CLEAN NITER= 12000 PRODUCT=1" oiio:subimages: 1 Comparing "../fits-images/ftt4b/file003.fits" and "file003.fits" PASS +Reading ../fits-images/ftt4b/file006.fits +../fits-images/ftt4b/file006.fits : 512 x 512, 3 channel, uint8 fits + SHA-1: 9FF70B84DD1529A9404B0E80BDE7B334F691F90A + channel list: R, G, B + Blank: 0 + Bscale: 1 + Bzero: 0 + Cdelt3: 1 + Comment: "THIS IS A TEST OF THE 8-BIT PIXEL TYPE. +THIS FULL COLOR IMAGE WAS TAKEN FROM A TAPE OF TEST IMAGES +DISTRIBUTED BY THE IMAGE PROCESSING INSTITUTE AT THE UNIVERSITY OF +SOUTHERN CALIFORNIA. +I SUGGEST 'RGB' BE ADDED TO FITS COORDINATE TYPES. DON WELLS. 05SEP79. +THIS IS INTENDED TO BE USED FOR SPECIFYING TRIPLETS OF IMAGES WHICH +ARE INTENDED TO BE VIEWED WITH COLOR TELEVISION MONITORS OR RECORDED +WITH COLOR HARDCOPY DEVICES (E.G., DICOMED D47)." + Crota3: 0 + Crpix3: 1 + Crval3: 1 + Ctype3: "RGB" + DateTime: "1980:04:18 00:00:00" + Ipps-b/p: 12 + Ipps-id: "RED MANDRILL ("BABOON")" + Ipps-max: 255 + Ipps-min: 0 + Ipps-rf: "D /001" + Object: "MANDRILL" + Origin: "KPNO -- WFITS OF 04/17/80." + oiio:subimages: 1 +Comparing "../fits-images/ftt4b/file006.fits" and "file006.fits" +PASS Reading ../fits-images/ftt4b/file009.fits ../fits-images/ftt4b/file009.fits : 0 x 0, 1 channel, int16 fits SHA-1: DA39A3EE5E6B4B0D3255BFEF95601890AFD80709 @@ -712,6 +743,206 @@ AIPS CLEAN NITER= 12000 PRODUCT=1" oiio:subimages: 1 Comparing "../fits-images/ftt4b/file012.fits" and "file012.fits" PASS +Reading src/rgb.fits +src/rgb.fits : 32 x 32, 3 channel, float fits + SHA-1: F7E36E977DEA527FE023283D9A6E6941BA439AE1 + channel list: R, G, B + A_0_0: 0 + A_0_1: 0 + A_0_2: 6.08313e-08 + A_0_3: 3.51185e-11 + A_1_0: 0 + A_1_1: -7.16346e-09 + A_1_2: -6.52948e-10 + A_2_0: -8.57221e-08 + A_2_1: 6.14197e-13 + A_3_0: -6.39624e-10 + A_order: 3 + Ap_0_0: -0.000178267 + Ap_0_1: 2.44714e-07 + Ap_0_2: -6.08969e-08 + Ap_0_3: -3.54786e-11 + Ap_1_0: -8.42448e-06 + Ap_1_1: 7.38336e-09 + Ap_1_2: 6.61491e-10 + Ap_2_0: 8.6651e-08 + Ap_2_1: -7.33107e-13 + Ap_3_0: 6.46638e-10 + Ap_order: 3 + B_0_0: 0 + B_0_1: 0 + B_0_2: -8.27428e-08 + B_0_3: -6.11542e-10 + B_1_0: 0 + B_1_1: -9.90564e-08 + B_1_2: -1.68675e-11 + B_2_0: -1.88226e-08 + B_2_1: -6.48505e-10 + B_3_0: -2.02193e-12 + B_order: 3 + Bp_0_0: -0.000204257 + Bp_0_1: -5.3092e-06 + Bp_0_2: 8.32734e-08 + Bp_0_3: 6.16666e-10 + Bp_1_0: 1.05632e-08 + Bp_1_1: 9.96989e-08 + Bp_1_2: 1.68664e-11 + Bp_2_0: 1.90495e-08 + Bp_2_1: 6.55252e-10 + Bp_3_0: 2.05389e-12 + Bp_order: 3 + Bscale: 1 + Bzero: 0 + Cdelt1: -0.000776386 + Cdelt2: 0.000776814 + Comment: "FITS (Flexible Image Transport System) format is defined in 'Astronomy +and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H" + Crpix1: -74.5 + Crpix2: -549.5 + Crval1: 85.3218 + Crval2: -2.49444 + Ctype1: "RA---TAN-SIP" + Ctype2: "DEC--TAN-SIP" + Cunit1: "deg" + Cunit2: "deg" + Date-obs: 2025 + DateTime: "2026:08:09 12:28:17" + Dec: -1.93685 + Equinox: 2000 + Expend: 2461030 + Expstart: 2461030 + Exptime: 15 + Extend: "T" + Filter: "Duo-Band" + Focallen: 147.555 + Gain: 60 + History: "mean stacking without rejection, additive+scaling normalized input, norm +alized output, no image weighting, equalized RGB, filter selected +Rotation (0.0deg, cropped=TRUE, clamped=TRUE)" + Instrume: "DWARF 3" + Livetime: 2265 + Lonpole: 180 + Mips-flo: 0 + Objctdec: -1 + Objctra: 5 + Object: "Horsehead Nebula" + Pc1_1: 0.996079 + Pc1_2: -0.0884675 + Pc2_1: 0.0885425 + Pc2_2: 0.996072 + Pltsolvd: "T" + Program: "Siril 1.4.3" + Ra: 85.1877 + Software: "OpenImageIO 3.2.0.2dev : F437187DB9769DA2652357EC017687DCC21D6FCD" + Stackcnt: 151 + Telescop: "DWARF 3" + Xbinning: 1 + Xpixsz: 2 + Ybinning: 1 + Ypixsz: 2 + Oiio:sub: 1 + oiio:subimages: 1 +Reading src/rgb.fits +src/rgb.fits : 32 x 32, 3 channel, float fits + SHA-1: F7E36E977DEA527FE023283D9A6E6941BA439AE1 + channel list: R, G, B + A_0_0: 0 + A_0_1: 0 + A_0_2: 6.08313e-08 + A_0_3: 3.51185e-11 + A_1_0: 0 + A_1_1: -7.16346e-09 + A_1_2: -6.52948e-10 + A_2_0: -8.57221e-08 + A_2_1: 6.14197e-13 + A_3_0: -6.39624e-10 + A_order: 3 + Ap_0_0: -0.000178267 + Ap_0_1: 2.44714e-07 + Ap_0_2: -6.08969e-08 + Ap_0_3: -3.54786e-11 + Ap_1_0: -8.42448e-06 + Ap_1_1: 7.38336e-09 + Ap_1_2: 6.61491e-10 + Ap_2_0: 8.6651e-08 + Ap_2_1: -7.33107e-13 + Ap_3_0: 6.46638e-10 + Ap_order: 3 + B_0_0: 0 + B_0_1: 0 + B_0_2: -8.27428e-08 + B_0_3: -6.11542e-10 + B_1_0: 0 + B_1_1: -9.90564e-08 + B_1_2: -1.68675e-11 + B_2_0: -1.88226e-08 + B_2_1: -6.48505e-10 + B_3_0: -2.02193e-12 + B_order: 3 + Bp_0_0: -0.000204257 + Bp_0_1: -5.3092e-06 + Bp_0_2: 8.32734e-08 + Bp_0_3: 6.16666e-10 + Bp_1_0: 1.05632e-08 + Bp_1_1: 9.96989e-08 + Bp_1_2: 1.68664e-11 + Bp_2_0: 1.90495e-08 + Bp_2_1: 6.55252e-10 + Bp_3_0: 2.05389e-12 + Bp_order: 3 + Bscale: 1 + Bzero: 0 + Cdelt1: -0.000776386 + Cdelt2: 0.000776814 + Comment: "FITS (Flexible Image Transport System) format is defined in 'Astronomy +and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H" + Crpix1: -74.5 + Crpix2: -549.5 + Crval1: 85.3218 + Crval2: -2.49444 + Ctype1: "RA---TAN-SIP" + Ctype2: "DEC--TAN-SIP" + Cunit1: "deg" + Cunit2: "deg" + Date-obs: 2025 + DateTime: "2026:08:09 12:28:17" + Dec: -1.93685 + Equinox: 2000 + Expend: 2461030 + Expstart: 2461030 + Exptime: 15 + Extend: "T" + Filter: "Duo-Band" + Focallen: 147.555 + Gain: 60 + History: "mean stacking without rejection, additive+scaling normalized input, norm +alized output, no image weighting, equalized RGB, filter selected +Rotation (0.0deg, cropped=TRUE, clamped=TRUE)" + Instrume: "DWARF 3" + Livetime: 2265 + Lonpole: 180 + Mips-flo: 0 + Objctdec: -1 + Objctra: 5 + Object: "Horsehead Nebula" + Pc1_1: 0.996079 + Pc1_2: -0.0884675 + Pc2_1: 0.0885425 + Pc2_2: 0.996072 + Pltsolvd: "T" + Program: "Siril 1.4.3" + Ra: 85.1877 + Software: "OpenImageIO 3.2.0.2dev : F437187DB9769DA2652357EC017687DCC21D6FCD" + Stackcnt: 151 + Telescop: "DWARF 3" + Xbinning: 1 + Xpixsz: 2 + Ybinning: 1 + Ypixsz: 2 + Oiio:sub: 1 + oiio:subimages: 1 +Comparing "src/rgb.fits" and "rgb.fits" +PASS oiiotool ERROR: read : "src/broken_no_END.fits": Hit end of file unexpectedly (offset=5760) Full command line was: > oiiotool --info --hash src/broken_no_END.fits diff --git a/testsuite/fits/run.py b/testsuite/fits/run.py index aaf9bd68bf..36b12c2570 100755 --- a/testsuite/fits/run.py +++ b/testsuite/fits/run.py @@ -21,10 +21,12 @@ imagedir = OIIO_TESTSUITE_IMAGEDIR + "/ftt4b" files = [ "file001.fits", "file002.fits", "file003.fits", - "file009.fits", "file012.fits" ] + "file006.fits", "file009.fits", "file012.fits" ] for f in files : command += rw_command (imagedir, f) +command += info_command ("src/rgb.fits") +command += rw_command ("src", "rgb.fits") # Regression tests for broken files command += info_command ("src/broken_no_END.fits", verbose=False, failureok=True) diff --git a/testsuite/fits/src/rgb.fits b/testsuite/fits/src/rgb.fits new file mode 100644 index 0000000000..57da81b732 --- /dev/null +++ b/testsuite/fits/src/rgb.fits @@ -0,0 +1,41 @@ +SIMPLE = T BITPIX = -32 NAXIS = 3 NAXIS1 = 32 NAXIS2 = 32 NAXIS3 = 3 EXTEND = T BZERO = 0 BSCALE = 1 MIPS-FLO= 0 PROGRAM = Siril 1.4.3 DATE = 2026-08-09T12:28:17 DATE-OBS= 2025 EXPTIME = 15 TELESCOP= DWARF 3 FILTER = Duo-Band FOCALLEN= 147.555 XBINNING= 1 YBINNING= 1 XPIXSZ = 2 YPIXSZ = 2 INSTRUME= DWARF 3 GAIN = 60 STACKCNT= 151 LIVETIME= 2265 EXPSTART= 2.46103e+06 EXPEND = 2.46103e+06 OBJECT = Horsehead Nebula OBJCTRA = 5 OBJCTDEC= -1 RA = 85.1877 DEC = -1.93685 CTYPE1 = RA---TAN-SIP CTYPE2 = DEC--TAN-SIP OIIO:SUB= 1 CUNIT1 = deg CUNIT2 = deg EQUINOX = 2000 CRPIX1 = -74.5 CRPIX2 = -549.5 CRVAL1 = 85.3218 CRVAL2 = -2.49444 LONPOLE = 180 CDELT1 = -0.000776386 CDELT2 = 0.000776814 PC1_1 = 0.996079 PC1_2 = -0.0884675 PC2_1 = 0.0885425 PC2_2 = 0.996072 A_ORDER = 3 A_0_0 = 0 A_1_0 = 0 A_0_1 = 0 A_2_0 = -8.57221e-08 A_1_1 = -7.16346e-09 A_0_2 = 6.08313e-08 A_3_0 = -6.39624e-10 A_2_1 = 6.14197e-13 A_1_2 = -6.52948e-10 A_0_3 = 3.51185e-11 B_ORDER = 3 B_0_0 = 0 B_1_0 = 0 B_0_1 = 0 B_2_0 = -1.88226e-08 B_1_1 = -9.90564e-08 B_0_2 = -8.27428e-08 B_3_0 = -2.02193e-12 B_2_1 = -6.48505e-10 B_1_2 = -1.68675e-11 B_0_3 = -6.11542e-10 AP_ORDER= 3 AP_0_0 = -0.000178267 AP_1_0 = -8.42448e-06 AP_0_1 = 2.44714e-07 AP_2_0 = 8.6651e-08 AP_1_1 = 7.38336e-09 AP_0_2 = -6.08969e-08 AP_3_0 = 6.46638e-10 AP_2_1 = -7.33107e-13 AP_1_2 = 6.61491e-10 AP_0_3 = -3.54786e-11 BP_ORDER= 3 BP_0_0 = -0.000204257 BP_1_0 = 1.05632e-08 BP_0_1 = -5.3092e-06 BP_2_0 = 1.90495e-08 BP_1_1 = 9.96989e-08 BP_0_2 = 8.32734e-08 BP_3_0 = 2.05389e-12 BP_2_1 = 6.55252e-10 BP_1_2 = 1.68664e-11 BP_0_3 = 6.16666e-10 PLTSOLVD= T COMMENT FITS (Flexible Image Transport System) format is defined in 'Astronomy COMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H HISTORY mean stacking without rejection, additive+scaling normalized input, normHISTORY alized output, no image weighting, equalized RGB, filter selected HISTORY Rotation (0.0deg, cropped=TRUE, clamped=TRUE) SOFTWARE= OpenImageIO 3.2.0.2dev : F437187DB9769DA2652357EC017687DCC21D6FCD END ;,;7N;=;G%r;M?;N-;Z:;R<;[[#;i";me;{;);a;|;;4;K;=;/;9;;;;;{;.f;p;bn;R;FF;:+;2;Cc;>I;@;F;V;`;h.;p;;;Fb;U;};;d;P(;);J;;;Q;;0i;6; ;;;}7;eA;P;L;v;;%;B;K;Y(-;^`;cO;m4;Gw;A;;;;{;8@;;jo;a;;[I;O;;;;\B;;U;:;;;;al;U;EN;G;P;^;ah;t4;j/;2; ;̤;I;^;x;B;ŕ;Ϻ;?;;ʡ;;;;Ǭ;4F;x;(;;I;;;>u;8;^ ;C;M;_;mZ2;n;o;5;p;};{;b;Ģ;;ɢD;؆;;! ; N;Ke;;8;Z;鮓;;˺;P";?t;;`;y;%;t ;R<;Wp;do;{;E;S;%9;=;);L;܆;];K;U;<h< $< << 6\< q<<<[;쿪;އ;;oo;;v; +;;]qn;n;q;;5;li;8;;\;eC;>F;;;)< <<(<}<Ѫ<"<#v;ܮF;;Hf;c;?;;d`;v;;R;N;l;VA;o;;;< ^"<<<"'W<.{<51gm;9<5<$W <@o H>K w>x>o>^C>- =j>==Zd= >d$>}>>ͽ>ǒ>>Q>Rc=@=G>j>>g? V|?j?+?>> >; ==e= <<~<IY_>l>3E??8?FK?B?-? i>W>Qw> =Ɵ=@?=M~;}O< +,>z>A5?r?8?[M?l?h!?P@5?)>>)>+=~=R}= x,<'8;Ȩ;y;ڏG<<kJ>4>(??F^p?l??|[?bv?9? +N>M>D)=KZ=V = <ޖ<<)T T>]>ȏg?[?@?f%?zc?w?_?6غ? "g>>B==Z=<<<)Ss> ?~?)"?K:??]h?\?F?#k>>>&O=)=S=<ކp<.>)>??"F?2! ?1a?!?r7>>x==F=<=# <Ǵ)t>}>ר>Ƭ??%7> >>>.P,=}=ZP="<<6$V>QL>)>>#>y>><=#=h=:A= +aW<─<*f<<9>10>3*>u=4~=0=a= O< <(<5;:|;D;8;T;Q`;w;;;ܼ;ְ;X<< +;< <$R<#\<'<,OQ<-<)a<)<0+<1<)}<*6<&*<7<H<.< R0;>;i;; L;;J9;{T;;[;p;V< +<%2<^<*(<2;9;ZP;O<"< <<1>>%ʭ>=#>G>Qvh>D>1n>E=t=+=c=d=9/=L<8<Ϸk< +K>>3>\>ʶ>=>>5>0>ϐ>_L>+{)= =R=iP=Vc=/A=+~>C7>>>ɮ#>-x>W>>d>"T>a >i?>i=߁w==~H=L7='#=O<E5c>|>^>ߨ??x?1?? 4>/>~>`>I>h=6=F=b +=7|=<<<7s>hk >C>ߒ.? +DN?a?.!?4@?28?&y?q>I>ľ=>a>(Q=ݺ=|=oI=BU<=N:1>u>???60?Gt?O?M"?@AV?*?dD>>&> B>L>rp=TW=6=QD=)P=FX>>ۻ? ?*?Es?YB?bd?` j?Qs?:?>U> >n>R=Γ=Q=e=6=gQ>>>'?p_?.ʞ?J?_j?i?gA?Yh=?A,?$*?>P>r}U>=ּ\==o;=<=<'<1 F>s>? +?)?E0?Yõ?c?b3?Tp?>?!?ͱ>q>pz>"=n=E=ct=0x= +<ݹ<-r8T">]>>??5?H?Rs?Q?EY?0{?>>0>>\>'=Ŏ='0=M= A=R<<)[w>o>>;]?L?` ?/T?7'?7V?-"??>z><>>N>=:o=~D=7=<<ŋ<G&<@pw>:Zr>3>t>d?~???2?d?>>+>q>'+===k=4= <` +U>@/>n>X>n>> >K>>>١>O>F> =A=?=]=$M =L< i<=< +<6j<_<<mJ>&>J0V>p>)>> />^>4>p>O9>>=_={=_=?_="=>>$ +>@M>]%>fm>S>3> #====Y~=)= ->X>.=ጀ====X +=3_=j<#<v=:=9=-3= 3 = ;< :<7<1?<<<%j0=C=;==t=n=6= : +>%->D>S'$>^>Hă>+>=o==^k="=R{<<%!>a|'>TG>;'>! >>"B>|q>PE> Aw=p=W=E8=<<~G<9<<^< <^~<==8x8==!>+ >p>>B>l4>Ŝ> >*>k> +>B=c=z*=f )=.=m<<<(I>o[>->x?5?? +?-?}>Ƶ>^L>0>%=>B=&)=?=p<'0U>y>B?6?!r?5?=?:Z?*?Ҫ>>{>R>N=4=TB=U<4z<< r<&;A<q<<@zRX>'>腓??2f?I<=?SE?P2???M?$?F>>||>=2=n==/f= <ݛ<<a|J>>-?=?8_?PK?[?X?G?,? +>>>-=­=c=;=L`q>d+>??1?H?TKe?Q?A?'?Ԡ>> >Y=<(==6d= D<0<>5>ו???4Y???=xQ?/?RW>g>8>c>u=u=l=$g T9>a@>>?,? ? l????=> +>Q>9e=뷔=j=U"=2(<_< +1?>]Z>>>dO>%>u>7> e>>lw4>=W=#=D= N<<R<>C[>xOQ>7#>9>U>>>,>4Nq=L=_=w`!=:=k<;>.)>M>j>w=>Y>,,==/=DB=TV0=<_<!.> )><=$`==`=X=*K=%<<< <3<6<.j<"X<mU;b;T \ No newline at end of file From c9bd88fce2e6cdd11f1bbfbea56061616fce24ab Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 11 Aug 2026 22:39:13 -0700 Subject: [PATCH 4/5] Fix ability to read scanlines from FITS volume files Signed-off-by: Larry Gritz --- src/fits.imageio/fitsinput.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/fits.imageio/fitsinput.cpp b/src/fits.imageio/fitsinput.cpp index 3a470180ed..c60c003eb6 100644 --- a/src/fits.imageio/fitsinput.cpp +++ b/src/fits.imageio/fitsinput.cpp @@ -90,7 +90,7 @@ FitsInput::open(const std::string& name, ImageSpec& spec) bool -FitsInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, +FitsInput::read_native_scanline(int subimage, int miplevel, int y, int z, void* data) { lock_guard lock(*this); @@ -104,7 +104,8 @@ FitsInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, std::vector data_tmp(m_spec.scanline_bytes()); if (!planar_channels()) { - long scanline_off = (m_spec.height - y) * m_spec.scanline_bytes(); + size_t scanline_off = (size_t(z) * m_spec.height + (m_spec.height - y)) + * size_t(m_spec.scanline_bytes()); fseek(m_fd, scanline_off, SEEK_CUR); size_t n = fread(&data_tmp[0], 1, m_spec.scanline_bytes(), m_fd); if (n != m_spec.scanline_bytes()) { @@ -124,11 +125,12 @@ FitsInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, size_t row_bytes = size_t(m_spec.width) * comp_size; size_t plane_bytes = row_bytes * size_t(m_spec.height) * size_t(m_spec.depth); - long row_off = (m_spec.height - y) * long(row_bytes); + size_t row_off = (size_t(z) * m_spec.height + (m_spec.height - y)) + * row_bytes; std::vector chan_row(row_bytes); for (int c = 0; c < m_spec.nchannels; ++c) { fsetpos(m_fd, &m_filepos); - fseek(m_fd, long(c * plane_bytes) + row_off, SEEK_CUR); + fseek(m_fd, size_t(c * plane_bytes) + row_off, SEEK_CUR); size_t n = fread(&chan_row[0], 1, row_bytes, m_fd); if (n != row_bytes) { if (feof(m_fd)) From 4bf4bc4b007d4d0d926ea1cb1fc162ea176cfb17 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 12 Aug 2026 00:28:51 -0700 Subject: [PATCH 5/5] Replace fseek with 64-bit safe Filesystem::fseek Signed-off-by: Larry Gritz --- src/fits.imageio/fits_pvt.h | 5 +++-- src/fits.imageio/fitsinput.cpp | 33 ++++++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/fits.imageio/fits_pvt.h b/src/fits.imageio/fits_pvt.h index 5dcfc30e0f..c379379903 100644 --- a/src/fits.imageio/fits_pvt.h +++ b/src/fits.imageio/fits_pvt.h @@ -103,8 +103,9 @@ class FitsInput final : public ImageInput { // search for subimages: in FITS subimage is a header with SIMPLE keyword // or with XTENSION keyword with value 'IMAGE '. Information about found - // subimages are stored in m_subimages - void subimage_search(); + // subimages are stored in m_subimages. Return true for ok, false for + // error. + bool subimage_search(); // set basic info (width, height) of subimage // add attributes to ImageSpec diff --git a/src/fits.imageio/fitsinput.cpp b/src/fits.imageio/fitsinput.cpp index c60c003eb6..8ab0304fc3 100644 --- a/src/fits.imageio/fitsinput.cpp +++ b/src/fits.imageio/fitsinput.cpp @@ -9,6 +9,7 @@ #include "fits_pvt.h" +#include #include #include @@ -76,9 +77,13 @@ FitsInput::open(const std::string& name, ImageSpec& spec) return false; } // moving back to the start of the file - fseek(m_fd, 0, SEEK_SET); + if (Filesystem::fseek(m_fd, 0, SEEK_SET)) { + errorfmt("Seek error"); + return false; + } - subimage_search(); + if (!subimage_search()) + return false; if (!set_spec_info()) return false; @@ -106,7 +111,10 @@ FitsInput::read_native_scanline(int subimage, int miplevel, int y, int z, if (!planar_channels()) { size_t scanline_off = (size_t(z) * m_spec.height + (m_spec.height - y)) * size_t(m_spec.scanline_bytes()); - fseek(m_fd, scanline_off, SEEK_CUR); + if (Filesystem::fseek(m_fd, scanline_off, SEEK_CUR)) { + errorfmt("Seek error"); + return false; + } size_t n = fread(&data_tmp[0], 1, m_spec.scanline_bytes(), m_fd); if (n != m_spec.scanline_bytes()) { if (feof(m_fd)) @@ -130,7 +138,11 @@ FitsInput::read_native_scanline(int subimage, int miplevel, int y, int z, std::vector chan_row(row_bytes); for (int c = 0; c < m_spec.nchannels; ++c) { fsetpos(m_fd, &m_filepos); - fseek(m_fd, size_t(c * plane_bytes) + row_off, SEEK_CUR); + if (Filesystem::fseek(m_fd, size_t(c * plane_bytes) + row_off, + SEEK_CUR)) { + errorfmt("Seek error"); + return false; + } size_t n = fread(&chan_row[0], 1, row_bytes, m_fd); if (n != row_bytes) { if (feof(m_fd)) @@ -188,7 +200,10 @@ FitsInput::seek_subimage(int subimage, int miplevel) // setting file pointer to the beginning of IMAGE extension m_cur_subimage = subimage; - fseek(m_fd, m_subimages[m_cur_subimage].offset, SEEK_SET); + if (Filesystem::fseek(m_fd, m_subimages[m_cur_subimage].offset, SEEK_SET)) { + errorfmt("Seek error"); + return false; + } if (!set_spec_info()) return false; @@ -541,7 +556,7 @@ FitsInput::assign_channel_names() -void +bool FitsInput::subimage_search() { // saving position of the file, just for safe) @@ -549,7 +564,10 @@ FitsInput::subimage_search() fgetpos(m_fd, &fpos); // starting reading headers from the beginning of the file - fseek(m_fd, 0, SEEK_SET); + if (Filesystem::fseek(m_fd, 0, SEEK_SET)) { + errorfmt("Seek error"); + return false; + } // we search for subimages by reading whole header and checking if it // starts by "SIMPLE" keyword (primary header is always image header) @@ -567,6 +585,7 @@ FitsInput::subimage_search() offset += HEADER_SIZE; } fsetpos(m_fd, &fpos); + return true; }