version 3.10-dev
rasterimagereader.hh
Go to the documentation of this file.
1// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2// vi: set et ts=4 sw=4 sts=4:
3//
4// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5// SPDX-License-Identifier: GPL-3.0-or-later
6//
12#ifndef DUMUX_RASTER_IMAGE_READER_HH
13#define DUMUX_RASTER_IMAGE_READER_HH
14
15#include <cassert>
16#include <cstdint>
17#include <string>
18#include <vector>
19#include <fstream>
20#include <sstream>
21#include <algorithm>
22#include <map>
23#include <iterator>
24#include <iostream>
25
26#include <dune/common/exceptions.hh>
29
30namespace Dumux {
31
38{
39 template<typename T>
41
44
45public:
46
52 static Format getFormat(const std::vector<std::string_view>& firstLineTokes)
53 {
54 static const auto format = []{
55 std::map<std::string, Format> format;
56 format["P1"] = Format{"P1", "Portable BitMap", "ASCII"};
57 format["P2"] = Format{"P2", "Portable GrayMap", "ASCII"};
58 format["P3"] = Format{"P3", "Portable PixMap", "ASCII"};
59 format["P4"] = Format{"P4", "Portable BitMap", "binary"};
60 format["P5"] = Format{"P5", "Portable GrayMap", "binary"};
61 format["P6"] = Format{"P6", "Portable PixMap", "binary"};
62 return format;
63 }();
64
65 const std::string& magicNumber = std::string(firstLineTokes[0]);
66
67 if (!format.count(magicNumber))
68 DUNE_THROW(Dune::IOError, magicNumber << " is not a valid magic number for the Netpbm format");
69
70 return format.at(magicNumber);
71 }
72
81 static Result<bool> readPBM(const std::string& fileName, const bool useDuneGridOrdering = true)
82 {
83 std::ifstream infile(fileName, std::ios::binary);
84
85 if (!infile)
86 DUNE_THROW(Dune::IOError, "Raster data file not found or corrupt");
87
88 HeaderData headerData = readHeader(infile);
89 std::vector<bool> values;
90
91 if (headerData.format.magicNumber == "P1")
92 values = readPBMDataASCII_(infile, headerData);
93 else if (headerData.format.magicNumber == "P4")
94 values = readPBMDataBinary_(infile, headerData);
95 else
96 DUNE_THROW(Dune::IOError, headerData.format.magicNumber << " not supported. Use readPBM for P1 and P4 or readPGM for P2 and P5");
97
98 Result<bool> result(std::move(values), std::move(headerData));
99 printInfo(result);
100
101 if (useDuneGridOrdering)
102 applyDuneGridOrdering(result);
103
104 return result;
105 }
106
119 template<class ValueType = std::uint8_t>
120 static Result<ValueType> readPGM(const std::string& fileName, const bool useDuneGridOrdering = true)
121 {
122 std::ifstream infile(fileName, std::ios::binary);
123
124 if (!infile)
125 DUNE_THROW(Dune::IOError, "Raster data file not found or corrupt");
126
127 HeaderData headerData = readHeader(infile);
128 std::vector<ValueType> values;
129
130 if (headerData.format.magicNumber == "P2")
131 values = NetPBMReader::template readPGMDataASCII_<ValueType>(infile, headerData);
132 else if (headerData.format.magicNumber == "P5")
133 values = NetPBMReader::template readPGMDataBinary_<ValueType>(infile, headerData);
134 else
135 DUNE_THROW(Dune::IOError, headerData.format.magicNumber << " not supported. Use readPBM for P1 and P4 or readPGM for P2 and P5");
136
137 Result<ValueType> result(std::move(values), std::move(headerData));
138 printInfo(result);
139
140 if (useDuneGridOrdering)
141 applyDuneGridOrdering(result);
142
143 return result;
144 }
145
151 static HeaderData readHeader(std::ifstream& infile)
152 {
153 HeaderData headerData;
154 std::string inputLine;
155 std::size_t lineNumber = 0;
156
157 // First line : get format.
158 std::getline(infile, inputLine);
159 ++lineNumber;
160
161 const auto firstLineTokens = tokenize(inputLine, " ");
162 headerData.format = getFormat(firstLineTokens);
163 const auto magicNumber = headerData.format.magicNumber;
164
165 // dimensions could be given right after magic number (in same line)
166 if (firstLineTokens.size() > 2)
167 {
168 if (isBlackAndWhite_(magicNumber) && firstLineTokens.size() != 3)
169 DUNE_THROW(Dune::IOError, "Could not read first line for B/W image");
170
171 headerData.nCols = std::stoi(std::string(firstLineTokens[1]));
172 headerData.nRows = std::stoi(std::string(firstLineTokens[2]));
173
174 if (isGrayScale_(magicNumber))
175 {
176 if (firstLineTokens.size() == 4)
177 headerData.maxValue = std::stoi(std::string(firstLineTokens[3]));
178 if (firstLineTokens.size() > 4)
179 DUNE_THROW(Dune::IOError, "Could not read first line for grayscale image");
180 }
181 }
182 else
183 {
184 // Read dimensions and maximum value (for non-b/w images).
185 while (!infile.eof())
186 {
187 std::getline(infile, inputLine);
188 ++lineNumber;
189
190 // Skip comments.
191 if (isComment_(inputLine))
192 continue;
193
194 const auto tokens = tokenize(inputLine, " ");
195
196 // The first line after the comments contains the dimensions.
197 if (tokens.size() != 2)
198 DUNE_THROW(Dune::IOError, "Expecting " << [](auto size){ return size < 2 ? "both" : "only"; }(tokens.size()) << " dimensions (2 numbers) in line " << lineNumber);
199
200 headerData.nCols = std::stoi(std::string(tokens[0]));
201 headerData.nRows = std::stoi(std::string(tokens[1]));
202
203 // Grayscale images additionally contain a maximum value in the header.
204 if (isGrayScale_(magicNumber))
205 {
206 std::getline(infile, inputLine);
207 ++lineNumber;
208
209 const auto token = tokenize(inputLine, " ");
210 if (token.size() != 1)
211 DUNE_THROW(Dune::IOError, "Expecting" << [](auto size){ return size == 0 ? "" : " only"; }(token.size()) << " intensity (one number) in line " << lineNumber);
212
213 headerData.maxValue = std::stoi(std::string(token[0]));
214 }
215 break;
216 }
217 }
218
219 return headerData;
220 }
221
228 template<class T>
229 static void applyDuneGridOrdering(Result<T>& result)
230 {
231 auto tmp = result;
232 for (std::size_t i = 0; i < result.size(); i += result.header().nCols)
233 std::swap_ranges((result.begin() + i), (result.begin() + i + result.header().nCols), (tmp.end() - i - result.header().nCols));
234 }
235
241 template<class T>
242 static void printInfo(const Result<T>& result)
243 {
244 const Format& format = result.header().format;
245 std::cout << "Reading " << format.type << " file (" << format.encoding << ")" << std::endl;
246 std::cout << "Dimensions : " << result.header().nCols << " " << result.header().nRows << std::endl;
247 std::cout << "Maximum value : " << result.header().maxValue << std::endl;
248 }
249
257 template<class Image, class T>
258 static void fillImage(Image& image, const Result<T>& result)
259 {
260 const auto nCols = result.header().nCols;
261 const auto nRows = result.header().nRows;
262 using RowType = std::decay_t<decltype(image[0])>;
263 image.resize(nRows, RowType(nCols));
264
265 std::size_t rowIdx = 0;
266 std::size_t colIdx = 0;
267 for (const auto val : result)
268 {
269 image[rowIdx][colIdx] = val;
270
271 // start a new line after nCols entries
272 if (++colIdx == nCols)
273 {
274 colIdx = 0;
275 ++rowIdx;
276 }
277 }
278 }
279
285 template<class Image>
286 static auto flattenImageToVector(const Image& image)
287 {
288 // deducing the type from the access operator fails for std::vector<bool>
289 // for some implementation since the access operator might return a proxy
290 // so we expect a STL container interface here with value_type defined
291 std::vector<std::decay_t<typename Image::value_type::value_type>> data;
292 data.reserve(image.size()*image[0].size());
293 for (const auto& row : image)
294 data.insert(data.end(), row.begin(), row.end());
295
296 return data;
297 }
298
299private:
300
301 static bool isBlackAndWhite_(const std::string& magicNumber)
302 {
303 return magicNumber == "P1" || magicNumber == "P4";
304 }
305
306 static bool isGrayScale_(const std::string& magicNumber)
307 {
308 return magicNumber == "P2" || magicNumber == "P5";
309 }
310
319 static std::vector<bool> readPBMDataASCII_(std::ifstream& infile,
320 const HeaderData& headerData)
321 {
322 std::string inputLine;
323 std::vector<bool> data;
324 data.reserve(numPixel_(headerData));
325
326 while (!infile.eof())
327 {
328 std::getline(infile, inputLine);
329 if (!isComment_(inputLine))
330 {
331 inputLine.erase(std::remove_if(inputLine.begin(), inputLine.end(), [](unsigned char c){ return std::isspace(c); }), inputLine.end());
332 if (!inputLine.empty())
333 {
334 for (const auto& value : inputLine)
335 {
336 assert(value == '0' || value == '1');
337 data.push_back(value - '0'); // convert char to int
338 }
339 }
340 }
341 }
342
343 return data;
344 }
345
354 static std::vector<bool> readPBMDataBinary_(std::ifstream& infile,
355 const HeaderData& headerData)
356 {
357 std::vector<bool> data(numPixel_(headerData));
358
359 // Skip potentially remaining comments in header section
360 // before reading binary content. We detect a comment by
361 // reading a line with std::getline and checking the resulting string.
362 // We continue reading new lines until no more comments are found. Then we
363 // need to set infile's current position to one line before the actual binary
364 // content, otherwise the following steps will fail.
365 std::string inputLine;
366 while (!infile.eof())
367 {
368 // store position before calling std::getline
369 const auto lastPos = infile.tellg();
370 std::getline(infile, inputLine);
371
372 // stop the loop if no more comment is found and go back one line
373 if (!isComment_(inputLine))
374 {
375 infile.seekg(lastPos);
376 break;
377 }
378 }
379
380 // read actual binary content
381 std::size_t nBytes = 0;
382 std::size_t bitIndex = 0;
383 using Bit = std::uint8_t;
384 Bit b = 0;
385 for (std::size_t j = 0; j < headerData.nRows; j++)
386 {
387 for (std::size_t i = 0; i < headerData.nCols; i++)
388 {
389 if (i%8 == 0)
390 {
391 char tmp;
392 infile.read(&tmp, 1);
393 b = static_cast<Bit>(tmp);
394 if (infile.eof())
395 DUNE_THROW(Dune::IOError, "Failed reading byte " << nBytes);
396
397 ++nBytes;
398 }
399
400 const Bit k = 7 - (i % 8);
401 data[bitIndex++] = static_cast<bool>((b >> k) & 1);
402 }
403 }
404
405 return data;
406 }
407
420 template<class ValueType = std::uint8_t>
421 static std::vector<ValueType> readPGMDataASCII_(std::ifstream& infile,
422 const HeaderData& headerData)
423 {
424 std::string inputLine;
425
426 std::vector<ValueType> data;
427 data.reserve(numPixel_(headerData));
428
429 while (!infile.eof())
430 {
431 std::getline(infile, inputLine);
432 if (inputLine.empty())
433 continue;
434
435 // if the line contains multiple comma-separated values, store them individually in a vector
436 if (inputLine.find(" ") != std::string::npos)
437 {
438 std::istringstream iss(inputLine);
439 std::vector<std::string> tokens;
440 std::copy(std::istream_iterator<std::string>(iss),
441 std::istream_iterator<std::string>(),
442 std::back_inserter(tokens));
443
444 for (const auto& t : tokens)
445 data.push_back(std::stoi(t)); // convert string to integer type
446 }
447 else
448 data.push_back(std::stoi(inputLine));
449 }
450
451 return data;
452 }
453
466 template<class ValueType = std::uint8_t>
467 static std::vector<ValueType> readPGMDataBinary_(std::ifstream& infile,
468 const HeaderData& headerData)
469 {
470 // check the size of the binary part of the file
471 const auto curPos = infile.tellg();
472 infile.seekg(0, std::ios::end);
473 const auto endPos = infile.tellg();
474 const auto size = endPos - curPos;
475 if (size != numPixel_(headerData))
476 DUNE_THROW(Dune::IOError, "Binary file size does not match with raster image size");
477
478 // reset to the current position
479 infile.seekg(curPos, std::ios::beg);
480
481 // extract the binary data
482 std::vector<std::uint8_t> data(size);
483 infile.read(reinterpret_cast<char*>(&data[0]), size);
484
485 // convert std::uint8_t to ValueType
486 return std::vector<ValueType>(data.begin(), data.end());
487 }
488
494 static std::size_t numPixel_(const HeaderData& headerData)
495 {
496 return headerData.nRows*headerData.nCols;
497 }
498
502 static bool isComment_(const std::string_view line)
503 {
504 return line[0] == '#';
505 }
506};
507
508} // namespace Dumux
509
510#endif
The return type of the reading functions. Holds the actual pixel values and the header data.
Definition: rasterimagedata.hh:48
const HeaderData & header() const
Returns the header data.
Definition: rasterimagedata.hh:70
A simple reader class for the Netpbm format (https://en.wikipedia.org/wiki/Netpbm_format)....
Definition: rasterimagereader.hh:38
static HeaderData readHeader(std::ifstream &infile)
Returns the header data of the image file.
Definition: rasterimagereader.hh:151
static void fillImage(Image &image, const Result< T > &result)
Fill a pre-defined 2D image object, e.g. std::vector<std::vector<bool>>, with the pixel values stored...
Definition: rasterimagereader.hh:258
static Format getFormat(const std::vector< std::string_view > &firstLineTokes)
A helper function to retrieve the format from tokens of the file's first line.
Definition: rasterimagereader.hh:52
static Result< bool > readPBM(const std::string &fileName, const bool useDuneGridOrdering=true)
Reads a *pbm (black and white) in ASCII or binary encoding. Returns a struct that contains both the p...
Definition: rasterimagereader.hh:81
static auto flattenImageToVector(const Image &image)
Flattens a 2D image object to a 1D container.
Definition: rasterimagereader.hh:286
static void applyDuneGridOrdering(Result< T > &result)
Change the ordering of the pixels according to Dune's convention, shifting the origin from upper left...
Definition: rasterimagereader.hh:229
static Result< ValueType > readPGM(const std::string &fileName, const bool useDuneGridOrdering=true)
Reads a *.pgm (grayscale) in ASCII or binary encoding. Returns a struct that contains both the pixel ...
Definition: rasterimagereader.hh:120
static void printInfo(const Result< T > &result)
Print the data contained in the header.
Definition: rasterimagereader.hh:242
constexpr Line line
Definition: couplingmanager1d3d_line.hh:31
Definition: adapt.hh:17
std::vector< std::string_view > tokenize(std::string_view str, std::string_view delim)
Definition: stringutilities.hh:38
A data class for raster image information.
Helpers for working with strings.
A struct that holds all information of the image format.
Definition: rasterimagedata.hh:25
std::string magicNumber
Definition: rasterimagedata.hh:26
std::string type
Definition: rasterimagedata.hh:27
std::string encoding
Definition: rasterimagedata.hh:28
A struct that contains all header data of the image.
Definition: rasterimagedata.hh:35
std::size_t maxValue
Definition: rasterimagedata.hh:39
std::size_t nRows
Definition: rasterimagedata.hh:38
Format format
Definition: rasterimagedata.hh:36
std::size_t nCols
Definition: rasterimagedata.hh:37