Isis 3 Programmer Reference
ProcessImportFits.cpp
1
6/* SPDX-License-Identifier: CC0-1.0 */
7#include "ProcessImportFits.h"
8
9#include <QDebug>
10#include <QString>
11
12#include <iostream>
13#include <math.h>
14#include <sstream>
15
16#include "IException.h"
17#include "IString.h"
18#include "LineManager.h"
19#include "Preference.h"
20#include "Pvl.h"
21#include "PvlGroup.h"
22#include "PixelType.h"
23#include "SpecialPixel.h"
24#include "UserInterface.h"
25
26namespace Isis {
27
37
38
43 delete m_fitsImageLabels;
44 delete m_extraFitsLabels;
45 delete m_headerSizes;
46 delete m_dataStarts;
47 m_file.close();
48 }
49
50
57
58 m_fitsImageLabels = new QList< PvlGroup * >;
59 m_extraFitsLabels = new QList< PvlGroup * >;
60 m_headerSizes = new QList < int >;
61 m_dataStarts = new QList < int >;
62
63 // Process each FITS label area, storing each in its own PvlGroup.
64 char readBuf[81];
65 IString line = "";
66 unsigned int place;
67
68 // The main FITS label starts at the beginning of the file.
69 // FITS extension labels start after the previous data and on a 2880 byte boundry
70 // Each FITS keyword in all lables is stored in 80 bytes (space padded to 80 if
71 // necessary).
72
73 // Start at the beginning of the file for the main FITS label
74 m_file.seekg(0, std::ios_base::beg);
75
76 // Read the first label line (80 chars)
77 // We are assuming the file pointer is set to the beginning of the first/next label
78 while (m_file.read(readBuf, 80) && m_file.gcount() == 80) {
79
80 PvlGroup *fitsLabel = new PvlGroup("FitsLabels");
81
82 readBuf[80] = '\0';
83 line = readBuf;
84 place = 80;
85
86 // Process each fits label record (80 bytes) and place keyword, value pairs into PvlKeywords
87 // with any associated comments.
88 while (line.substr(0, 3) != "END") {
89
90 // Check for blank lines
91 if (line.substr(0, 1) != " " && line.substr(0, 1) != "/") {
92 // Name of keyword
93 PvlKeyword label(line.Token(" =").ToQt()); // Stop on spaces OR equal sign
94 if (QString::compare(label.name(), "OBJECT", Qt::CaseInsensitive) == 0) {
95 label.setName("TARGET");
96 label.addComment("NOTE: This keyword name was changed from 'OBJECT' in the original "
97 "fit header file.");
98 }
99 // Remove up to beginning of data
100 line.TrimHead(" =");
101 line.TrimTail(" ");
102 if (label.name() == "COMMENT" || label.name() == "HISTORY") {
103 label += line.ToQt();
104 }
105 else {
106 // Check for a quoted value
107 if (line.substr(0,1) == "'") {
108 line.TrimHead("'");
109 label += line.Token("'").TrimHead(" ").TrimTail(" ").ToQt();
110 line.TrimHead(" '");
111 }
112 else {
113 // Access any remaining data without the trailing comment if there is one
114 IString value = line.Token("/");
115 // Clear to end of data
116 value.TrimTail(" ");
117 label += value.ToQt();
118 line.TrimHead(" ");
119 }
120 // If the line still has anything in it, treat it is as a comment.
121 if (line.size() > 0) {
122 line.TrimHead(" /");
123 label.addComment(line.ToQt());
124 // A possible format for units, other possiblites exist.
125 if (line != line.Token("[")) {
126 label.setUnits(line.Token("[").Token("]").ToQt());
127 }
128 }
129 }
130 fitsLabel->addKeyword(label);
131 }
132
133 // Read the next label line
134 m_file.read(readBuf, 80);
135 readBuf[80] = '\0';
136 line = readBuf;
137 place += 80;
138 }
139
140 // Save off the PvlGroup and the number of records read from this label
141 m_fitsImageLabels->append(fitsLabel);
142 m_headerSizes->append((int)ceil(place / 2880.0));
143
144 // The file pointer should be pointing at the end of the record that contained "END"
145 // Move the file pointer past the padding after the "END" (i.e., points to start of data)
146 std::streamoff jump;
147 jump = m_headerSizes->last() * 2880 - place;
148 m_file.seekg(jump, std::ios_base::cur);
149
150 m_dataStarts->append(m_file.tellg());
151
152 // Does this look like a label for a FITS image? Stop after the first label that does not
153 // because we don't know how to move the file pointer past a non-image data extension.
154 if (fitsLabel->hasKeyword("BITPIX") && fitsLabel->hasKeyword("NAXIS")) {
155
156 // This section can only handle image data
157 // (i.e., keywords BITPIX & NAXIS & NAXISx must exist).
158 if((int)fitsLabel->findKeyword("NAXIS") > 0) {
159 int bytesPerPixel = 0;
160 bytesPerPixel = (int)((*fitsLabel)["BITPIX"]);
161 bytesPerPixel = std::abs(bytesPerPixel);
162 bytesPerPixel /= 8;
163
164 unsigned int axis1 = 1;
165 axis1 = toInt((*fitsLabel)["NAXIS1"]);
166
167 unsigned int axis2 = 1;
168 if (fitsLabel->hasKeyword("NAXIS2")) {
169 axis2 = toInt((*fitsLabel)["NAXIS2"]);
170 }
171
172 unsigned int axis3 = 1;
173 if (fitsLabel->hasKeyword("NAXIS3")) {
174 axis3 = toInt((*fitsLabel)["NAXIS3"]);
175 }
176
177 jump = (int)(ceil(bytesPerPixel * axis1 * axis2 * axis3 / 2880.0) * 2880.0);
178 m_file.seekg(jump, std::ios_base::cur);
179 }
180 else {
181 // Note: this will allow us to read extra label sections that have 0 axes,
182 // but has image-related info (so BITPIX and NAXIS keywords exist). This
183 // includes informational labels, as seen at the beginning of hayabusa2
184 // images in this case, there is NO DATA, so no jump should be needed to
185 // get to the next section.
186 PvlGroup *extraLabelGroup = m_fitsImageLabels->last();
187 extraLabelGroup->setName("FitsExtras");
188 m_extraFitsLabels->append(extraLabelGroup);
189
190 m_fitsImageLabels->removeLast();
191 m_headerSizes->removeLast();
192 m_dataStarts->removeLast();
193 }
194 }
195 // Do we have at least on header that looks like it has image data? If so, we can continue,
196 // but ignore the rest of the file because we don't know how to skip over a non-image data.
197 else if (m_fitsImageLabels->size() > 1) {
198 m_fitsImageLabels->removeLast();
199 m_headerSizes->removeLast();
200 m_dataStarts->removeLast();
201 break;
202 }
203
204 else {
205 QString msg = QObject::tr("The FITS file does not contain a section header that appears "
206 "to describe an image [%1].").arg(m_name.toString());
207 throw IException(IException::User, msg, _FILEINFO_);
208 }
209 }
210 }
211
212
225 if (labelNumber >= m_extraFitsLabels->size()) {
226 QString msg = QObject::tr("The requested label number [%1], from file [%2] is "
227 "past the last extra group found in this FITS file. "
228 "Extra label count is [%3]").arg(labelNumber).
229 arg(m_name.expanded()).arg(m_extraFitsLabels->size()-1);
230 throw IException(IException::User, msg, _FILEINFO_);
231 }
232
233 if (!m_extraFitsLabels) {
234 QString msg = QObject::tr("The FITS label has not been initialized, "
235 "call setFitsFile() first.");
236 throw IException(IException::Programmer, msg, _FILEINFO_);
237 }
238 else if (m_extraFitsLabels->size() < labelNumber) {
239 QString msg = QObject::tr("The requested FITS label number "
240 "was not found in file [%1].").arg(m_name.toString());
241 throw IException(IException::Programmer, msg, _FILEINFO_);
242 }
243
244 return *(*m_extraFitsLabels)[labelNumber];
245 }
246
247
260
261 if (labelNumber >= m_fitsImageLabels->size()) {
262 QString msg = QObject::tr("The requested label number [%1], from file [%2] is "
263 "past the last image group found in this FITS file. "
264 "Image label count is [%3]").arg(labelNumber).
265 arg(m_name.expanded()).arg(m_fitsImageLabels->size()-1);
266 throw IException(IException::User, msg, _FILEINFO_);
267 }
268
269 if (!m_fitsImageLabels) {
270 QString msg = QObject::tr("The FITS label has not been initialized, "
271 "call setFitsFile first.");
272 throw IException(IException::Programmer, msg, _FILEINFO_);
273 }
274 else if (m_fitsImageLabels->size() < labelNumber) {
275 QString msg = QObject::tr("The requested FITS label number "
276 "was not found in file [%1].").arg(m_name.toString());
277 throw IException(IException::Programmer, msg, _FILEINFO_);
278 }
279
280 return *(*m_fitsImageLabels)[labelNumber];
281 }
282
283
296
297 // NOTE: This needs to be changed over to use translation files
298
299 // Attempt to extract the standard instrument group keywords
300 PvlGroup inst("Instrument");
301 if (fitsLabel.hasKeyword("DATE-OBS")) {
302 inst += PvlKeyword("StartTime", fitsLabel["DATE-OBS"][0]);
303 }
304 if (fitsLabel.hasKeyword("TARGET")) {
305 inst += PvlKeyword("Target", fitsLabel["TARGET"][0]);
306 }
307 if (fitsLabel.hasKeyword("INSTRUME")) {
308 inst += PvlKeyword("InstrumentId", fitsLabel["INSTRUME"][0]);
309 }
310 if (fitsLabel.hasKeyword("OBSERVER")) {
311 inst += PvlKeyword("SpacecraftName", fitsLabel["OBSERVER"][0]);
312 }
313
314 return inst;
315 }
316
317
325 m_name = fitsFile;
326
327 SetInputFile(fitsFile.toString()); // Make sure the file exists
328
329 m_file.open(fitsFile.expanded().toLocal8Bit().constData(), std::ios::in | std::ios::binary);
330
331 if (!m_file.is_open()) {
332 QString msg = QObject::tr("Unable to open FITS formatted file [%1].")
333 .arg(fitsFile.toString());
334 throw IException(IException::User, msg, _FILEINFO_);
335 }
336
337 // Get the FITS labels internalized
339
340 // Check to make sure it is a FITS file we can handle
341 PvlGroup label = fitsImageLabel(0);
342 if (label.hasKeyword("SIMPLE") && label["SIMPLE"][0] == "F") {
343 QString msg = QObject::tr("The file [%1] cannot be processed. "
344 "It is an unsupported format.").
345 arg(fitsFile.toString());
346 throw IException(IException::User, msg, _FILEINFO_);
347 }
348
349 m_file.close();
350 }
351
352
364
365 if (labelNumber >= m_fitsImageLabels->size()) {
366 QString msg = QObject::tr("The requested label number [%1], from file [%2] is "
367 "past the last image in this FITS file [%3].").arg(labelNumber).
368 arg(InputFile()).arg(m_fitsImageLabels->size()-1);
369 throw IException(IException::User, msg, _FILEINFO_);
370 }
371
372 PvlGroup label = *(*m_fitsImageLabels)[labelNumber];
373
374 // Set the ProcessImport to skip over all the previous images and their labels and the label for
375 // this image. Don't save this info (think memory)
376 SetFileHeaderBytes((*m_dataStarts)[labelNumber]);
377
378 // Find pixel type. NOTE: There are several unsupported possiblites
379 Isis::PixelType type;
380 QString msg = "";
381 switch (toInt(label["BITPIX"][0])) {
382 case 8:
383 type = Isis::UnsignedByte;
384 break;
385 case 16:
386 type = Isis::SignedWord;
387 break;
388 case 32:
389 type = Isis::SignedInteger;
390 break;
391 case -32:
392 type = Isis::Real;
393 break;
394 case 64:
395 msg = "Signed 64-bit integer (long) pixel type is not supported for FITS imports.";
396 throw IException(IException::User, msg, _FILEINFO_);
397 break;
398 case -64:
399 type = Isis::Double;
400 break;
401 default:
402 msg = "Unknown pixel type [" + label["BITPIX"][0] + "] is not supported for FITS imports.";
403 throw IException(IException::User, msg, _FILEINFO_);
404 break;
405 }
406
407 SetPixelType(type);
408
409 // It is possible to have a NAXIS value of 0 meaning no data, the file could include
410 // xtensions with data, however, those aren't supported because we need the code to know
411 // how to skip over them.
412 // NOTE: FITS files, at least the ones seen till now, do not specify a line prefix or suffix
413 // data byte count. Some FITS files do have them and ISIS needs to remove them so it is not
414 // considered part of the DNs. So, use the parent class' prefix/suffix byte count to reduce
415 // the number of samples.
416 if (Organization() == BSQ) {
417 if (toInt(label["NAXIS"][0]) == 2) {
418 SetDimensions(toInt(label["NAXIS1"][0])
420 toInt(label["NAXIS2"][0]), 1);
421 }
422 else if (toInt(label["NAXIS"][0]) == 3) {
423 SetDimensions(toInt(label["NAXIS1"][0])
425 toInt(label["NAXIS2"][0]), toInt(label["NAXIS3"][0]));
426 }
427 else {
428 QString msg = "NAXIS count of ["
429 + label["NAXIS"][0]
430 + "] is not supported for FITS imports.";
431 throw IException(IException::User, msg, _FILEINFO_);
432 }
433 }
434 else if (Organization() == BIL) {
435 if (toInt(label["NAXIS"][0]) == 2) {
436 SetDimensions(toInt(label["NAXIS1"][0])
438 1, toInt(label["NAXIS2"][0]));
439 }
440 else if (toInt(label["NAXIS"][0]) == 3) {
441 SetDimensions(toInt(label["NAXIS1"][0])
443 toInt(label["NAXIS3"][0]), toInt(label["NAXIS2"][0]));
444 }
445 else {
446 QString msg = "NAXIS count of ["
447 + label["NAXIS"][0]
448 + "] is not supported for FITS imports.";
449 throw IException(IException::User, msg, _FILEINFO_);
450 }
451 }
452 else if (Organization() == BIP) {
453 QString msg = "BIP (Band Interleaved by Pixel) "
454 "organization is not supported for FITS imports.";
455 throw IException(IException::User, msg, _FILEINFO_);
456 }
457 else {
458 QString msg = "Unknown organization is not supported for FITS imports.";
459 throw IException(IException::User, msg, _FILEINFO_);
460 }
461
462 // Base and multiplier
463 if (label.hasKeyword("BZERO")) {
464 SetBase(toDouble(label["BZERO"][0]));
465 }
466 else {
467 SetBase(0.0);
468 }
469 if (label.hasKeyword("BSCALE")) {
470 SetMultiplier(toDouble(label["BSCALE"][0]));
471 }
472 else {
473 SetMultiplier(1.0);
474 }
475
476 // Byte order
477 SetByteOrder(Isis::Msb);
478
479 }
480
481} // end namespace Isis
File name manipulation and expansion.
Definition FileName.h:100
QString expanded() const
Returns a QString of the full file name including the file path, excluding the attributes.
Definition FileName.cpp:196
QString toString() const
Returns a QString of the full file name including the file path, excluding the attributes with any Is...
Definition FileName.cpp:515
Isis exception class.
Definition IException.h:91
@ User
A type of error that could only have occurred due to a mistake on the user's part (e....
Definition IException.h:126
@ Programmer
This error is for when a programmer made an API call that was illegal.
Definition IException.h:146
Adds specific functionality to C++ strings.
Definition IString.h:165
IString TrimTail(const std::string &chars)
Trims the input characters from the end of the object IString.
Definition IString.cpp:587
IString Token(const IString &separator)
Returns the first token in the IString.
Definition IString.cpp:897
QString ToQt() const
Retuns the object string as a QString.
Definition IString.cpp:869
IString TrimHead(const std::string &chars)
Trims The input characters from the beginning of the object IString.
Definition IString.cpp:558
FileName m_name
The name of the input FITS file.
PvlGroup fitsImageLabel(int labelNumber) const
Supplies the FITS image label corresponding to the given index.
void extractFitsLabels()
Extract all the FITS labels from the file.
void setFitsFile(FileName fitsFile)
Opens a FITS image file with header and reads the FITS labels.
QList< PvlGroup * > * m_extraFitsLabels
Holds the PvlGroups with the converted extra FITS labels from the main and all extensions.
ProcessImportFits()
Constructor for ProcessImportFits.
std::ifstream m_file
The stream used to read the FITS file.
PvlGroup standardInstrumentGroup(PvlGroup fitsLabel) const
Return a PVL instrument group populated with expected default values.
PvlGroup extraFitsLabel(int labelNumber) const
Supplies the extra FITS label corresponding to the given index.
QList< PvlGroup * > * m_fitsImageLabels
Holds the PvlGroups with the converted FITS image labels from the main and all extensions.
void setProcessFileStructure(int labelNumber)
Sets the Process file structure parameters based on the given image label index.
virtual ~ProcessImportFits()
Destructor for ProcessImportFits.
QList< int > * m_dataStarts
The starting byte of the data for each image.
QList< int > * m_headerSizes
The number, or count, of 2880 byte header records for each image header section.
void SetPixelType(const Isis::PixelType type)
Sets the pixel type of the input file.
void SetFileHeaderBytes(const int bytes)
This method sets the number of bytes in the header of a file.
void SetInputFile(const QString &file)
Sets the name of the input file to be read in the import StartProcess method and verifies its existan...
void SetMultiplier(const double mult)
Sets the core multiplier of the input cube.
int DataPrefixBytes() const
This method returns the number of data prefix bytes.
Interleave Organization() const
Gets the organization of the input cube.
QString InputFile()
Sets the name of the input file to be read in the import StartProcess method and verifies its existan...
int DataSuffixBytes() const
This method returns the number of data duffix bytes.
void SetByteOrder(const Isis::ByteOrder order)
Sets the byte order of the input file.
@ BIL
Band Interleaved By Line Format (i.e.
@ BIP
Band Interleaved By Pixel Format (i.e.
@ BSQ
Band Sequential Format (i.e.
void SetBase(const double base)
Sets the core base of the input cube.
void SetDimensions(const int ns, const int nl, const int nb)
Sets the physical size of the input cube.
bool hasKeyword(const QString &name) const
Check to see if a keyword exists.
Contains multiple PvlContainers.
Definition PvlGroup.h:41
A single keyword-value pair.
Definition PvlKeyword.h:87
void setName(QString name)
Sets the keyword name.
QString name() const
Returns the keyword name.
Definition PvlKeyword.h:103
void setUnits(QString units)
Sets the unit of measure for all current values if any exist.
void addComment(QString comment)
Add a comment to the PvlKeyword.
This is free and unencumbered software released into the public domain.
Definition Apollo.h:16
int SizeOf(Isis::PixelType pixelType)
Returns the number of bytes of the specified PixelType.
Definition PixelType.h:46
int toInt(const QString &string)
Global function to convert from a string to an integer.
Definition IString.cpp:93
double toDouble(const QString &string)
Global function to convert from a string to a double.
Definition IString.cpp:149
PixelType
Enumerations for Isis Pixel Types.
Definition PixelType.h:27