Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Providing a way to create and validate Solarnet-net compatible FITS headers #7271

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
10 changes: 10 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,16 @@
target = target.strip()
nitpick_ignore.append((dtype, target))

# -- Generate CSV Files for Docs ---------------------------------------------
if not os.path.exists("generated"):
os.mkdir("generated") # generate the directory before putting things in it

from sunpy.io.meta import fits_meta

solarnet_schema = fits_meta.read_schema_file(fits_meta.solarnet_schema_file)
# remove allowed_values column which contains list which cannot be written to csv
solarnet_schema.remove_column('allowed_values')
solarnet_schema.write("./generated/fits_schema.csv", overwrite=True)

# -- Options for intersphinx extension -----------------------------------------
# Example configuration for intersphinx: refer to the Python standard library.
Expand Down
60 changes: 60 additions & 0 deletions docs/topic_guide/fits_keywords.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.. _sunpy-topic-guide-fits-keywords:

************************************
Creating and validating FITS Headers
************************************

Sometimes it is necessary to inspect, validate, or create metadata for a FITS file.
There are a number of standards and requirements associated with the keywords used in FITS files.
When opening reading a FITS file, sunpy makes use of these standards to properly interpret the FITS metadata.

The SOLARNET Metadata Recommendations for Solar Observations (Version 2.0, 14. August 2023) `https://arxiv.org/pdf/2011.12139.pdf <https://arxiv.org/pdf/2011.12139.pdf>`_ provides descriptions and recommendations for many keywords.
A listing of FITS keywords and their descriptions are provided below.
The `required` column describes whether the keyword is required by SOLARNET.
The `data_type` column provides a description of the expected type of the keyword value.
Note that deprecated keywords (such as DATE-OBS or EXPTIME) are explicitely not included in this list.


.. csv-table:: Table 1-1: FITS keywords
:file: ../generated/fits_schema.csv
:widths: 30, 70, 30, 30, 30
:header-rows: 1


In order to support creating FITS headers with the appropriate keywords, the
`sunpy.io.meta.fits_meta` module provides the `~sunpy.io.meta.fits_meta.SolarnetHeader` class.
It is a subclass of `~astropy.io.fits.Header`.
It provides additional functionality like the ability to validate against the solarnet schema as well as support an additional custom schema.

To create a blank header with only the required keywords

.. code-block:: python

from sunpy.io.meta import fits_meta
header = fits_meta.SolarnetHeader()
header['OBSRVTRY'] = 'myawesomemission'

You can then inspect the keywords and fill in the values as needed.
After you've filled all of your values, you can validate the header with

.. code-block:: python

header.validate()

This will provide warnings for each issue that it finds.
It checks for a number of potential issues such as whether the required keywords
have non-blank values, that deprecrated keywords are not used, and that the values
can be interpreted as the correct types (e.g. dates, quantities, int, floats).
For a complete listing of the issues checked see `~sunpy.io.meta.fits_meta.SolarnetHeader.validate()`.

You can define your own schema by creating a yaml file with the following structure for each keywords.

.. code-block:: yaml

SOLARNET:
description: Fully SOLARNET-compliant=1.0, partially=0.5
human_readable: Solarnet compatibility
required: true
data_type: float

You can add an additional optional field `allowed_values: [1.0, 0.5, 0]` if you want to limit the possible options.
1 change: 1 addition & 0 deletions docs/topic_guide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ Although there are code snippets in various parts of each topic guide, these are
new_map_class
timeseries_metadata
rsun
fits_keywords.rst
307 changes: 307 additions & 0 deletions sunpy/data/fits_solarnet_schema.yaml
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this comment in the meeting discussion today, but I wonder whether we want to store this schema in sunpy itself. As this convention was developed elsewhere, I don't think we want to be the ones that own the spec/become de facto in charge of maintaining/updating the spec. My suggestion would be to either pull this schema from its official source or if that doesn't exist in a convenient format, then to put this in a separate, versioned repository that we can pull from such that the schema is maintained separately from sunpy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the right approach. Maybe @wafels could host an "official" solarnet repository given that their funding has ended? I'd like the same to be case for the CDF ISTP schema as well to be hosted by the SPDF.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do this, although at the moment SDAC does not require compliance with the SOLARNET metadata standard. The heliophysics data policy does ask for FITS files for solar data, and the HEASARC hosts FITS verification tools. At the moment, testing against the schema would be purely optional for an instrument team.

Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
# the following is based on SOLARNET Metadata Recommendations for Solar Observations
# Version 2.0 – 14. August 2023
# Stein Vidar Hagfors Haugan and Terje Fredvik
# https://arxiv.org/pdf/2011.12139.pdf
AUTHOR:
description: Who designed the observation
human_readable: Author
required: true
data_type: str
BLANK:
description: Value marking undefined pixels before the application of BSCALE, BZERO
human_readable: Missing data indicator
required: true
data_type: str
BTYPE:
description: Description of what the data array represents
human_readable: Data label
required: true
data_type: str
BUNIT:
description: Units of data array
human_readable: Units
required: true
data_type: str
CADENCE:
description: "[s] Planned/commanded cadence (frame to frame spacing)"
human_readable: Planned measurement cadence
required: true
data_type: quantity
CADAVG:
description: "[s] Average (actual) measurement cadence (frame to frame spacing)"
human_readable: Average actual measurement cadence
required: true
data_type: quantity
CADMAX:
description: "[s] Maximum frame-to-frame spacing"
human_readable: Maximum measurement cadence
required: true
data_type: quantity
CADMIN:
description: "[s] Minimum frame-to-frame spacing"
human_readable: Minimum measurement cadence
required: true
data_type: quantity
CAMPAIGN:
description: Coordinated campaign name(s)
human_readable: Observation campaign
required: false
data_type: str
CREATOR:
description: Name of software pipeline that produced the FITS file
human_readable: File Creator
required: true
data_type: str
DATATAGS:
description: Additional information
human_readable: Data Tags
required: false
data_type: str
DATE:
description: FITS file creation date in UTC
human_readable: File Creation Date
required: true
data_type: date
DATE-BEG:
description: Start time of the data aquisition
human_readable: Aquisition start time
required: true
data_type: date
DATE-END:
description: End time of the data aquisition
human_readable: Aquisition end time
required: false
data_type: date
DATE-AVG:
description: Average time of the data aquisition
human_readable: Aquisition average time
required: false
data_type: date
DETECTOR:
description: Name of detector
human_readable: detector
required: true
data_type: str
DSUN_OBS:
description: Distance to Sun
human_readable: Distance to Sun
required: true
data_type: quantity
EXTNAME:
description: Extension name
human_readable: Extension name
required: true
data_type: str
FILENAME:
description: FITS filename
human_readable: Filename
required: true
data_type: str
GEOX_OBS:
description: "[m] Observer's non-fixed geographic X coordinate"
human_readable: Observer X Position
required: false
data_type: quantity
GEOY_OBS:
description: "[m] Observer's non-fixed geographic Y coordinate"
human_readable: Observer Y Position
required: false
data_type: quantity
GEOZ_OBS:
description: "[m] Observer's non-fixed geographic Z coordinate"
human_readable: Observer Z Position
required: false
data_type: quantity
HASH_SW:
description: Commit hash of software applied
human_readable: Software Hash
required: true
data_type: str
INFO_URL:
description: Human-readable web page describing the data set
human_readable: Info url
required: true
data_type: url
INSTRUME:
description: Instrument name
human_readable: Instrument
required: true
data_type: str
LEVEL:
description: Data level of fits file
human_readable: Data level
required: true
data_type: float
MISSION:
description: Mission name
human_readable: Mission
required: true
data_type: str
NSUMEXP:
description: The total number of exposures
human_readable: Number of exposures
required: false
data_type: int
OBS_DESC:
description: Description of observation
human_readable: Observation description
required: false
data_type: str
OBS_HDU:
description: Description of observation
human_readable: Observation description
required: true
data_type: str
OBS_LOG:
description: URL of observation
human_readable: Observation description
required: false
data_type: url
OBS_MODE:
description: Name of predefined settings used during observation
human_readable: Observation mode
required: false
data_type: str
OBSERVER:
description: Who acquired the data
human_readable: Observer
required: false
data_type: str
OBSRVTRY:
description: Satellite name
human_readable: Observatory
required: true
data_type: str
ORIGIN:
description: Location where FITS file has been created
human_readable: File originator
required: true
data_type: str
PLANNER:
description: Observation planner
human_readable: Planner
required: false
data_type: str
PROJECT:
description: Project name
human_readable: Project
required: true
data_type: str
PRSTEP1:
description: First processing step
human_readable: Processing step
required: true
data_type: str
RELEASE:
description: Public release date of data
human_readable: Release date
required: true
data_type: date
RELEASEC:
description: Email address of data release administrator
human_readable: Release administrator
required: true
data_type: email
RESPAPPL:
description: Response function applied
human_readable: Applid response function
required: false
data_type: str
RESOLVPW:
description: Resolving power for spectrometric data
human_readable: Resolving power
required: false
data_type: float
SETTINGS:
description: Additional instrument/acquisition settings
human_readable: Settings
required: false
data_type: str
SLIT_WID:
description: "[arcsec] Slit width"
human_readable: Slit width
required: false
data_type: quantity
SOLARNET:
description: Fully SOLARNET-compliant=1.0, partially=0.5
human_readable: Solarnet compatibility
required: true
data_type: float
allowed_values: [1.0, 0.5, 0]
TELCONFG:
description: Telescope configuration
human_readable: Telescope configuration
required: false
data_type: str
TELESCOP:
description: Telescope/Sensor name
human_readable: Telescope
required: true
data_type: str
TEXPOSURE:
description: The integration time for a single exposure
human_readable: Single exposure time
required: false
data_type: float
TIMESYS:
description: Time scale of the time-related keywords.
human_readable: Time system
required: true
data_type: str
allowed_values: ["UTC"]
VERS_CAL:
description: Version of calibration pack applied
human_readable: Calibration version
required: true
data_type: str
VERS_SW:
description: Version of software applied
human_readable: Software version
required: true
data_type: str
VERSION:
description: FITS file processing generation/versio
human_readable: Processor version
required: true
data_type: str
WAVEBAND:
description: Human-readable description of the waveband
human_readable: Waveband description
required: false
data_type: str
WAVECOV:
description: The total wavelength coverage if multiple filters (<WAVEMIN1>-<WAVEMAX1>, etc.)
human_readable: Wavelength coverage
required: false
data_type: str
WAVELTH:
description: The characteristic wavelength of the observation
human_readable: Characteristic wavelength
required: false
data_type: float
WAVEMAX:
description: "[Angstrom] Maximum wavelength covered by filter"
human_readable: Maximum wavelength
required: true
data_type: quantity
WAVEMIN:
description: "[Angstrom] Minimum wavelength covered by filter"
human_readable: Minimum wavelength
required: true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems odd to me that this is required. This is a less-well defined quantity for some types of data, e.g. imaging instruments which have a bandpass or a magnetograph.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recommendations are specific to the kinds of observatories so you are right that this is technically only required for "Spectrographs and filter instruments
(Sections 3.2 and 5.4)" see page 59. I wasn't sure how to implement this kind of level of requirement system.

data_type: quantity
WAVEREF:
description: Wavelength related kwds in vacuum
human_readable: Wavelength reference
required: true
data_type: str
allowed_values: ["air", "vacuum"]
WAVEUNIT:
description: "Wavelength related kwds have unit: 10^(WAVEUNIT) m"
human_readable: Wavelength unit
required: true
data_type: str
XPOSURE:
description: "[s] Total effective exposure time"
human_readable: Exposure time
required: true
data_type: quantity
Empty file added sunpy/io/meta/__init__.py
Empty file.