SourceXtractorPlusPlus 1.0.3
SourceXtractor++, the next generation SExtractor
Loading...
Searching...
No Matches
FitsFile.cpp
Go to the documentation of this file.
1
17
18/*
19 * FitsFile.cpp
20 *
21 * Created on: Jun 9, 2020
22 * Author: mschefer
23 */
24
25#include <assert.h>
26
27#include <fstream>
28#include <iomanip>
29#include <iostream>
30#include <string>
31
32#include <boost/algorithm/string/case_conv.hpp>
33#include <boost/algorithm/string/trim.hpp>
34#include <boost/filesystem/operations.hpp>
35#include <boost/filesystem/path.hpp>
36#include <boost/regex.hpp>
37
38#include "ElementsKernel/Exception.h"
39#include "ElementsKernel/Logging.h"
40
42
43namespace SourceXtractor {
44
46
54static typename MetadataEntry::value_t valueAutoCast(const std::string& value) {
55 boost::regex float_regex("^[-+]?(\\d*\\.?\\d+|\\d+\\.?\\d*)([eE][-+]?\\d+)?$");
56 boost::regex int_regex("^[-+]?\\d+$");
57
58 try {
59 if (value.empty()) {
60 return value;
61 } else if (boost::regex_match(value, int_regex)) {
62 return static_cast<int64_t>(std::stoll(value));
63 } else if (boost::regex_match(value, float_regex)) {
64 return std::stod(value);
65 } else if (value.size() == 1) {
66 return value.at(0);
67 }
68 } catch (...) {
69 }
70
71 // Single quotes are used as escape code of another single quote, and
72 // the string starts and ends with single quotes.
73 // We used to use boost::io::quoted here, but it seems that starting with 1.73 it
74 // does not work well when the escape code and the delimiter are the same
75 std::string unquoted;
76 bool escape = false;
77
78 unquoted.reserve(value.size());
79 for (auto i = value.begin(); i != value.end(); ++i) {
80 if (*i == '\'' && !escape) {
81 escape = true;
82 // skip this char
83 } else {
84 escape = false;
85 unquoted.push_back(*i);
86 }
87 }
88 return unquoted;
89}
90
91static void close_fits(fitsfile* ptr) {
92 if (ptr != nullptr) {
93 int status = 0;
94 fits_close_file(ptr, &status);
95 }
96}
97
98FitsFile::FitsFile(const boost::filesystem::path& path, bool writeable)
99 : m_path(path), m_is_writeable(writeable), m_fits_ptr(nullptr, close_fits) {
100
101 open();
102 loadInfo();
103}
104
106
108 return m_fits_ptr.get();
109}
110
112 return m_image_hdus;
113}
114
118
120 int status = 0;
121 fitsfile* ptr = nullptr;
122
123 // Open
124 fits_open_image(&ptr, m_path.native().c_str(), m_is_writeable ? READWRITE : READONLY, &status);
125 m_fits_ptr.reset(ptr);
126 if (status != 0) {
127 if (m_is_writeable) {
128 // Create file if it does not exists
129 status = 0;
130 fits_create_file(&ptr, m_path.native().c_str(), &status);
131 m_fits_ptr.reset(ptr);
132 }
133 if (status != 0) {
134 char error_message[32];
135 fits_get_errstatus(status, error_message);
136 throw Elements::Exception()
137 << "Can't open FITS file: " << m_path << " status: " << status << " = " << error_message;
138 }
139 }
140 assert(ptr->Fptr->open_count == 1);
141}
142
144
145 // After modifying the FITS file, We need to close and reopen the file before we can query
146 // infos about it again without cfitsio crashing
147
148 int status = 0;
149 fitsfile* ptr = nullptr;
150
151 m_fits_ptr.reset(nullptr);
152
153 fits_open_image(&ptr, m_path.native().c_str(), m_is_writeable ? READWRITE : READONLY, &status);
154 if (status != 0) {
155 char error_message[32];
156 fits_get_errstatus(status, error_message);
157 throw Elements::Exception()
158 << "Can't close and reopen FITS file: " << m_path << " status: " << status << " = " << error_message;
159 }
160 assert(ptr->Fptr->open_count == 1);
161 m_fits_ptr.reset(ptr);
162
163 loadInfo();
164}
165
167 int status = 0;
168
169 fitsfile* ptr = m_fits_ptr.get();
170
171 // save current HDU (if the file is opened with advanced cfitsio syntax it might be set already
172 int original_hdu = 0;
173 fits_get_hdu_num(ptr, &original_hdu);
174
175 // Number of HDU
176 m_image_hdus.clear();
177 int number_of_hdus = 0;
178 if (fits_get_num_hdus(ptr, &number_of_hdus, &status) < 0) {
179 char error_message[32];
180 fits_get_errstatus(status, error_message);
181 throw Elements::Exception() << "Can't get the number of HDUs in FITS file: " << m_path
182 << " status: " << status << " = " << error_message;
183 }
184 m_headers.clear();
185 m_headers.resize(number_of_hdus);
186
187 // loop over HDUs to determine which ones are images
188 int hdu_type = 0;
189 for (int hdu_number = 1; hdu_number <= number_of_hdus; ++hdu_number) {
190 fits_movabs_hdu(ptr, hdu_number, &hdu_type, &status);
191 if (status != 0) {
192 char error_message[32];
193 fits_get_errstatus(status, error_message);
194 throw Elements::Exception() << "Can't switch HDUs while opening: " << m_path
195 << " status: " << status << " = " << error_message;
196 }
197
198 if (hdu_type == IMAGE_HDU) {
199 int bitpix, naxis;
200 long naxes[3] = {1, 1, 1};
201
202 fits_get_img_param(ptr, 3, &bitpix, &naxis, naxes, &status);
203 if (status == 0 && (naxis == 2 || naxis == 3)) {
204 m_image_hdus.emplace_back(hdu_number);
205 }
206 }
207 }
208
209 // go back to saved HDU
210 fits_movabs_hdu(ptr, original_hdu, &hdu_type, &status);
211
212 // load all FITS headers
214
215 // load optional .head file to override headers
216 loadHeadFile();
217}
218
221 char record[81];
222 int keynum = 1, status = 0;
223
224 fits_read_record(fptr, keynum, record, &status);
225 while (status == 0 && strncmp(record, "END", 3) != 0) {
226 static boost::regex regex("([^=]{8})=([^\\/]*)(\\/(.*))?");
227 std::string record_str(record);
228
229 boost::smatch sub_matches;
230 if (boost::regex_match(record_str, sub_matches, regex)) {
231 auto keyword = boost::to_upper_copy(sub_matches[1].str());
232 auto value = sub_matches[2].str();
233 auto comment = sub_matches[4].str();
234 boost::trim(keyword);
235 boost::trim(value);
236 boost::trim(comment);
237 headers.emplace(keyword, MetadataEntry{valueAutoCast(value), {{"comment", comment}}});
238 }
239 fits_read_record(fptr, ++keynum, record, &status);
240 }
241
242 return headers;
243}
244
246 int status = 0;
247
248 // save current HDU (if the file is opened with advanced cfitsio syntax it might be set already)
249 int original_hdu = 0;
250 fits_get_hdu_num(m_fits_ptr.get(), &original_hdu);
251
252 int hdu_type = 0;
253 for (unsigned int i = 0; i < m_headers.size(); i++) {
254 fits_movabs_hdu(m_fits_ptr.get(), i + 1, &hdu_type, &status); // +1 hdus start at 1
255
257 }
258
259 // go back to saved HDU
260 fits_movabs_hdu(m_fits_ptr.get(), original_hdu, &hdu_type, &status);
261}
262
264 auto base_name = m_path.stem();
265 base_name.replace_extension(".head");
266 auto head_filename = m_path.parent_path() / base_name;
267
268 if (!boost::filesystem::exists(head_filename)) {
269 return;
270 }
271
272 auto hdu_iter = m_image_hdus.begin();
273 std::ifstream file;
274
275 // open the file and check
276 file.open(head_filename.native());
277 if (!file.good() || !file.is_open()) {
278 throw Elements::Exception() << "Cannot load ascii header file: " << head_filename;
279 }
280
281 logger.info() << "Loading .head file: " << head_filename << " for fits: " << m_path;
282
283 int headers_nb = 0;
284 int hdu_nb = 0;
285 bool is_new_hdu = true;
286 while (file.good() && hdu_iter != m_image_hdus.end()) {
287 int current_hdu = *hdu_iter;
288
289 std::string line;
290 std::getline(file, line);
291
292 static boost::regex regex_blank_line("\\s*$");
293 line = boost::regex_replace(line, regex_blank_line, std::string(""));
294 if (line.size() == 0) {
295 continue;
296 }
297
298 if (boost::to_upper_copy(line) == "END") {
299 current_hdu = *(++hdu_iter);
300 is_new_hdu = true;
301 } else {
302 static boost::regex regex("([^=]{1,8})=([^\\/]*)(\\/ (.*))?");
303 boost::smatch sub_matches;
304 if (boost::regex_match(line, sub_matches, regex) && sub_matches.size() >= 3) {
305 auto keyword = boost::to_upper_copy(sub_matches[1].str());
306 auto value = sub_matches[2].str();
307 auto comment = sub_matches[4].str();
308 boost::trim(keyword);
309 boost::trim(value);
310 boost::trim(comment);
311 m_headers.at(current_hdu - 1)[keyword] = MetadataEntry{valueAutoCast(value), {{"comment", comment}}};
312 headers_nb++;
313 if (is_new_hdu) {
314 hdu_nb++;
315 is_new_hdu = false;
316 }
317 }
318 }
319 }
320 if (headers_nb > 0) {
321 logger.info() << "Headers overriden: " << headers_nb << " in " << hdu_nb << " hdu(s)";
322 }
323}
324
326 int status = 0;
327
328 // save current HDU
329 int original_hdu = 0;
330 fits_get_hdu_num(m_fits_ptr.get(), &original_hdu);
331
332 // got to requested HDU
333 int hdu_type = 0;
334 fits_movabs_hdu(m_fits_ptr.get(), hdu, &hdu_type, &status);
335
336 // get dimensions
337 long naxes[3] = {1, 1, 1};
338 int bitpix, naxis;
339
340 fits_get_img_param(m_fits_ptr.get(), 3, &bitpix, &naxis, naxes, &status);
341 if (status != 0 || (naxis != 2 && naxis != 3)) {
342 char error_message[32];
343 fits_get_errstatus(status, error_message);
344 throw Elements::Exception()
345 << "Can't find 2D image or data cube in FITS file: " << m_path << "[" << hdu << "]"
346 << " status: " << status << " = " << error_message;
347 }
348
349 std::vector<int> dims;
350 dims.push_back(naxes[0]);
351 dims.push_back(naxes[1]);
352 if (naxis == 3) {
353 dims.push_back(naxes[2]);
354 }
355
356 // go back to saved HDU
357 fits_movabs_hdu(m_fits_ptr.get(), original_hdu, &hdu_type, &status);
358
359 return dims;
360}
361
362
363} // namespace SourceXtractor
T at(T... args)
T begin(T... args)
static Logging getLogger(const std::string &name="")
std::unique_ptr< fitsfile, void(*)(fitsfile *)> m_fits_ptr
Definition FitsFile.h:63
boost::filesystem::path m_path
Definition FitsFile.h:61
std::map< std::string, MetadataEntry > & getHDUHeaders(int hdu)
Definition FitsFile.cpp:115
std::vector< std::map< std::string, MetadataEntry > > m_headers
Definition FitsFile.h:65
fitsfile * getFitsFilePtr()
Definition FitsFile.cpp:107
std::vector< int > getDimensions(int hdu) const
Definition FitsFile.cpp:325
std::vector< int > m_image_hdus
Definition FitsFile.h:64
FitsFile(const boost::filesystem::path &path, bool writeable)
Definition FitsFile.cpp:98
const std::vector< int > & getImageHdus() const
Definition FitsFile.cpp:111
T emplace(T... args)
T empty(T... args)
T end(T... args)
T getline(T... args)
T good(T... args)
T is_open(T... args)
static Elements::Logging logger
static void close_fits(fitsfile *ptr)
Definition FitsFile.cpp:91
static std::map< std::string, MetadataEntry > loadHeadersFromFits(fitsfile *fptr)
Definition FitsFile.cpp:219
static MetadataEntry::value_t valueAutoCast(const std::string &value)
Definition FitsFile.cpp:54
T open(T... args)
T push_back(T... args)
T reserve(T... args)
T size(T... args)
T stod(T... args)
T stoll(T... args)
T strncmp(T... args)
boost::variant< bool, char, int64_t, double, std::string > value_t
Definition ImageSource.h:43