File: ReadWriteTiff.cpp

package info (click to toggle)
bornagain 23.0-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 103,936 kB
  • sloc: cpp: 423,131; python: 40,997; javascript: 11,167; awk: 630; sh: 318; ruby: 173; xml: 130; makefile: 51; ansic: 24
file content (233 lines) | stat: -rw-r--r-- 8,648 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/IO/ReadWriteTiff.cpp
//! @brief     Implements functions readTiff, writeTiff.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#ifdef BA_TIFF_SUPPORT

#include "Device/IO/ReadWriteTiff.h"
#include "Base/Axis/Frame.h"
#include "Base/Axis/MakeScale.h"
#include "Base/Axis/Scale.h"
#include "Base/Util/Assert.h"
#include "Base/Util/SysUtil.h"
#include "Device/Data/Datafield.h"
#include <cstring> // memcpy
#include <memory>
#include <sstream>
#include <tiffio.h>
#include <tiffio.hxx>

namespace {
const std::string ref_to_doc =
    "\n\nThe TIFF format requirements can be found in the web documentation.";
} // namespace

Datafield Util::RW::readTiff(std::istream& input_stream)
{
    // Update webdoc page about tiff requirements if they are changed.

    TIFF* tiffstream = TIFFStreamOpen("MemTIFF", &input_stream);
    if (!tiffstream)
        throw std::runtime_error("Cannot open the TIFF file" + ref_to_doc);

    //... Read header.

    std::ostringstream message;
    message << "Cannot read TIFF file:" << std::endl;

    uint32_t w, h;
    if (!TIFFGetField(tiffstream, TIFFTAG_IMAGEWIDTH, &w)
        || !TIFFGetField(tiffstream, TIFFTAG_IMAGELENGTH, &h)) {
        message << "missing width/height in header" << ref_to_doc << std::endl;
        throw std::runtime_error(message.str());
    }

    // BitsPerSample defaults to 1 according to the TIFF spec.
    uint16_t bitsPerSample;
    if (!TIFFGetField(tiffstream, TIFFTAG_BITSPERSAMPLE, &bitsPerSample))
        bitsPerSample = 1;

    if (8 != bitsPerSample && 16 != bitsPerSample && 32 != bitsPerSample) {
        message << "    TIFFTAG_BITSPERSAMPLE: " << bitsPerSample << std::endl
                << "Only 8, 16 or 32 bits per sample are allowed." << ref_to_doc << std::endl;
        throw std::runtime_error(message.str());
    }


    // they may be e.g. grayscale with 2 samples per pixel
    uint16_t samplesPerPixel;
    if (!TIFFGetField(tiffstream, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel))
        samplesPerPixel = 1;

    if (samplesPerPixel != 1) {
        message << "    TIFFTAG_SAMPLESPERPIXEL: " << samplesPerPixel << std::endl
                << "Only 1 sample per pixel (channel) is allowed." << ref_to_doc << std::endl;
        throw std::runtime_error(message.str());
    }


    uint16_t sampleFormat;
    if (!TIFFGetField(tiffstream, TIFFTAG_SAMPLEFORMAT, &sampleFormat))
        sampleFormat = 1;

    switch (sampleFormat) {
    case 1: // unsigned int
    case 2: // signed int
        break;
    case 3: // IEEE float
        if (32 != bitsPerSample) {
            message
                << "    TIFFTAG_BITSPERSAMPLE: " << bitsPerSample << std::endl
                << "    TIFFTAG_SAMPLEFORMAT: " << sampleFormat << std::endl
                << "Only 32 bits per sample are allowed for IEEE float format (sample format = 3)."
                << ref_to_doc << std::endl;
            throw std::runtime_error(message.str());
        }
        break;
    default:
        message << "    TIFFTAG_SAMPLEFORMAT: " << sampleFormat << std::endl
                << "Only value 1, 2 or 3 is allowed." << ref_to_doc << std::endl;
        throw std::runtime_error(message.str());
    }

    //... Read data.

    ASSERT(0 == bitsPerSample % 8);
    uint16_t bytesPerSample = bitsPerSample / 8;
    tmsize_t buf_size = TIFFScanlineSize(tiffstream);
    tmsize_t expected_size = bytesPerSample * w;
    if (buf_size != expected_size)
        throw std::runtime_error("Cannot read TIFF file: wrong scanline size" + ref_to_doc);

    tdata_t const buf = _TIFFmalloc(buf_size); // tdata_t is void*
    if (!buf)
        throw std::runtime_error("Cannot read TIFF file: failed allocating buffer" + ref_to_doc);

    Datafield data(std::vector<const Scale*>{newEquiDivision("u (bin)", w, 0.0, double(w)),
                                             newEquiDivision("v (bin)", h, 0.0, double(h))});

    std::vector<int8_t> line_buf;
    line_buf.resize(buf_size, 0);

    for (uint32_t row = 0; row < h; row++) {
        if (TIFFReadScanline(tiffstream, buf, row) < 0)
            throw std::runtime_error("Cannot read TIFF file: error in scanline." + ref_to_doc);

        memcpy(line_buf.data(), buf, buf_size);

        for (unsigned col = 0; col < w; ++col) {
            void* incoming = &line_buf[col * bytesPerSample];
            double sample = 0;

            switch (sampleFormat) {
            case 1: // unsigned int
                switch (bitsPerSample) {
                case 8:
                    sample = *reinterpret_cast<uint8_t*>(incoming);
                    break;
                case 16:
                    sample = *reinterpret_cast<uint16_t*>(incoming);
                    break;
                case 32:
                    sample = *reinterpret_cast<uint32_t*>(incoming);
                    break;
                default:
                    throw std::runtime_error("Corrupted TIFF file");
                }
                break;
            case 2: // signed int
                switch (bitsPerSample) {
                case 8:
                    sample = *reinterpret_cast<int8_t*>(incoming);
                    break;
                case 16:
                    sample = *reinterpret_cast<int16_t*>(incoming);
                    break;
                case 32:
                    sample = *reinterpret_cast<int32_t*>(incoming);
                    break;
                default:
                    throw std::runtime_error("Corrupted TIFF file");
                }
                break;
            case 3: // IEEE float
                sample = double(*reinterpret_cast<float*>(incoming));
                break;
            default:
                throw std::runtime_error("Cannot read TIFF file: unexpected sample format"
                                         + ref_to_doc);
            }

            data[(h - 1 - row) * w + col] = sample;
        }
    }
    _TIFFfree(buf);
    TIFFClose(tiffstream);

    return data;
}

void Util::RW::writeTiff(const Datafield& data, std::ostream& output_stream)
{
    if (data.rank() != 2)
        throw std::runtime_error("Cannot write TIFF file: unsupported data rank");
    TIFF* tiffstream = TIFFStreamOpen("MemTIFF", &output_stream);
    ASSERT(tiffstream);
    const size_t m_width = data.axis(0).size();
    const size_t m_height = data.axis(1).size(); // this does not exist for 1d data

    //... Write header.

    TIFFSetField(tiffstream, TIFFTAG_ARTIST, "BornAgain.IOFactory");
    TIFFSetField(tiffstream, TIFFTAG_DATETIME, Base::System::getCurrentDateAndTime().c_str());
    TIFFSetField(tiffstream, TIFFTAG_IMAGEDESCRIPTION,
                 "Image converted from BornAgain intensity file.");
    TIFFSetField(tiffstream, TIFFTAG_SOFTWARE, "BornAgain");

    const auto width = static_cast<uint32_t>(m_width);
    const auto height = static_cast<uint32_t>(m_height);
    TIFFSetField(tiffstream, TIFFTAG_IMAGEWIDTH, width);
    TIFFSetField(tiffstream, TIFFTAG_IMAGELENGTH, height);

    // output format, hardcoded here
    const uint16_t bitPerSample = 32;
    const uint16_t samplesPerPixel = 1;
    TIFFSetField(tiffstream, TIFFTAG_BITSPERSAMPLE, bitPerSample);
    TIFFSetField(tiffstream, TIFFTAG_SAMPLESPERPIXEL, samplesPerPixel);

    TIFFSetField(tiffstream, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);

    //... Write data.

    using sample_t = int;
    const tmsize_t buf_size = sizeof(sample_t) * m_width;
    const tdata_t buf = _TIFFmalloc(buf_size);
    if (!buf)
        throw std::runtime_error("Cannot write TIFF file: failed allocating buffer");

    std::vector<sample_t> line_buf;
    line_buf.resize(m_width, 0);
    for (unsigned row = 0; row < (uint32_t)m_height; row++) {
        for (unsigned col = 0; col < line_buf.size(); ++col)
            line_buf[col] = static_cast<sample_t>(data[(m_height - 1 - row) * m_width + col]);
        memcpy(buf, line_buf.data(), buf_size);

        if (TIFFWriteScanline(tiffstream, buf, row) < 0)
            throw std::runtime_error("Cannot write TIFF file: error in TIFFWriteScanline");
    }
    _TIFFfree(buf);
    TIFFFlush(tiffstream);
    TIFFClose(tiffstream);
}

#endif // BA_TIFF_SUPPORT