EleFits
4.0.0
A modern C++ API on top of CFitsIO
|
In this tutorial, we will show how to read and write multi-extension Fits (MEF) files. This means understanding the usage of the following service classes:
MefFile
at file level,ImageHdu
and BintableHdu
at HDU level,ImageRaster
and BintableColumns
at data unit level;as well as Record
, Raster
and Column
data classes.
We strongly recommend reading first What's a Fits file?, even if you're familiar with the Fits format, because it introduces EleFits
-specific wording.
At the end of the tutorial, you will be able to create a MEF file from scratch with unlimited number of various extensions, and to read the metadata and data back!
The tutorial is built together with an example program: EleFitsTutorial.cpp. We've embedded the calls to the logger in the code snippets below, so that you can easily map the execution log to the following explanations. You can already run the program and watch the resulting file:
We'll first discover the data classes, then use them to create a MEF file, and finally read the file and values back.
First things first, we have to declare the dependency to EleFits and to use the right headers and namespaces. For the first part, head to the install_guide. For the headers and namespace, here's the only thing you'll have to do:
Data classes are the classes which hold the pieces of data read from and written to a Fits file. There are three main classes:
Record
for header units,Raster
for data units of image HDUs,Column
for data units of binary table HDUs.A keyword record as defined in the Fits standard is a triplet of keyword, value and optional comment rendered in the file as:
A unit can be included in the comment as follows:
The value can be a Boolean, an integer, real or complex number, or a string.
Record
is a simple template class which merely stores those fields. For the purpose of the tutorial, we define a structure to hold our records:
Here's a how they can be built:
Images in Fits are n-dimensional arrays. Raster
is a class which represents this kind of data and provides constant time pixel accessors. Template arguments are the pixel type and number of axes. There are two kinds of rasters:
VecRaster
owns the data as an std::vector
;PtrRaster
points to the data owned by another class, as a raw pointer (be careful not to destroy the data while the raster is alive).The rasters of this tutorial are stored in a dedicated structure:
Again, let's show an example of how to create rasters:
A binary table is made of columns which can be either scalar (each cell contains a value) or vector (each cell contains several values). In the latter case, the number of values per cell is named repeat count.
For string columns, the repeat count must be greater than the longest string value.
A Column
is a simple structure which holds the column name, repeat count (=1 for scalar columns, >1 for vector columns), unit, and owns or references some data, respectively as a VecColumn
or as a PtrColumn
.
The following structure stores the tutorial columns:
Here's a set of examples on how create them:
Now that we have learned about the data classes, we can move to the service classes, or handlers. The first thing we can do is to create a new file.
The MefFile
class represents multi-extension Fits (MEF) files. It extends the FitsFile
class, which is also the base class of SifFile
for single-image Fits (SIF) files.
Creating (or opening) a file is simply done with the constructor of MefFile
(or SifFile
for SIF files!):
A newly created Fits file consists in an empty Primary, which can then be accessed and modified, but is never created by hand.
The mode parameter controls the access rights on the file. For example, an existing file can be opened with read-only mode:
The file is closed when the destructor of MefFile
is called (although a FitsFile::close()
method is provided for convenience).
MefFile
provides services to create and access extensions. Conceptually, MefFile
is a FitsFile
with a vector of HDUs (in contrast, SifFile
is a FitsFile
with a single HDU).
HDUs are represented by two classes:
ImageHdu
to access image HDUs,BintableHdu
to access binary table HDUs.Both derive from parent class Hdu
.
To sum up, a MefFile
is a vector of Hdu
s, which can be a mix of ImageHdu
s and BintableHdu
s.
There are two kinds of services in MefFile
for creating extensions: They can be either initialized with header only or assigned directly with data. Here's an example of creating image HDUs:
And here's one of creating binary table HDUs:
Records are read and written through a Header
object, which is accessible from an ImageHdu
or a BintableHdu
as header()
. They can be written and updated one-by-one or by sequences.
An optional template parameter controls the write policy, e.g. what to do if the given keyword already exists. By default, a record is created if the keyword doesn't exists, or updated if the keyword already exists.
Record writing functions have different overloards to allow calling them in different ways. Here are a few examples:
Remember that we have left IMAGE2
extension without data? Filling it is done through the ImageRaster
class, accessed as ImageHdu::raster()
. For example, here is how to write all the pixels at once:
Many more options are available to write data region-wise. They are described in details in Image data unit handlers.
Analogously to ImageRaster
for image HDUs, BintableColumns
provides read/write services for the data unit of the binary table extension.
As the name implies, data is stored, read and written column-wise, as opposed to the row-major ordering of the Fits file. This is to avoid working with very complex structures like tuples with holes, and rather rely on Column
, std::vector
, arrays... Yet, this also means that I/Os could be very inefficient! To workaround this, several columns can be written (and read) at a time. In this case, I/Os are internally performed chunk-wise, using some buffer, and performances drammatically improve.
Like for image HDUs, it is possible to write the data unit only partially, e.g. to append rows, using richer methods of BintableColumns
.
HDUs can be accessed with a set of methods, templated with the type of HDU: BintableHdu
, ImageHdu
, or Hdu
. For metadata work, we don't need to know the type of HDU: whether this is an image or binary table HDU has no impact, and a Hdu
will be returned by default.
HDUs are accessed either by their name (first HDU whose name matches argument is returned) or by their index, and a shortcut is provided for the primary HDU (which has index 0):
You've probably noticed that we use references to constant HDU handlers. Indeed, HDU handlers are not modified by reading and writing services, only the MefFile
is. Actually, HDU-level and data unit-level handlers are very light classes which aim at providing a clear interface.
Record
s are parsed using Header
services. Like for writing, you can parse several records at once:
Like for writing, reading images is ensured by ImageRaster
. A VecRaster
is generally instantiated, and pixel values can be accessed with the subscript operator []
:
Again, regions can be read.
Column reading is provided by BintableColumns
. Columns are generally read as VecColumn
s, values of which are accessed with the call operator ()
:
A picture is worth a thousand words; below is the class diagram of what we've just learned (i.e. the main contents of the Euclid::Fits
namespace).
To go further or out of curiosity, head to the module pages or related pages. Specifically, data classes and service classes are described in more details in Data classes and File and HDU handlers. To practice or test your own code, you can also browse the Euclid::Fits::Test
namespace, which provides ready-to-use functions and classes, such as the random records, rasters and columns we've seen above.