From 9141b16b0eeec71ad8f476d7e6ecdd13b0cbd3fe Mon Sep 17 00:00:00 2001 From: Karl Kosack Date: Thu, 20 Mar 2025 14:39:39 +0100 Subject: [PATCH 1/2] Started to add Observation table --- src/vodf_schema/level1/__init__.py | 3 +- src/vodf_schema/level1/eventlist.py | 16 +++- src/vodf_schema/metadata.py | 125 +++++++++++++++++----------- 3 files changed, 92 insertions(+), 52 deletions(-) diff --git a/src/vodf_schema/level1/__init__.py b/src/vodf_schema/level1/__init__.py index 2aaa929..c565c31 100644 --- a/src/vodf_schema/level1/__init__.py +++ b/src/vodf_schema/level1/__init__.py @@ -4,5 +4,6 @@ from .eventlist import EventList from .groups import IRFGroupingTable, ObservationGroupingTable +from .observation import Observation -__all__ = ["ObservationGroupingTable", "IRFGroupingTable", "EventList"] +__all__ = ["ObservationGroupingTable", "IRFGroupingTable", "EventList", "Observation"] diff --git a/src/vodf_schema/level1/eventlist.py b/src/vodf_schema/level1/eventlist.py index 6615964..2613e37 100644 --- a/src/vodf_schema/level1/eventlist.py +++ b/src/vodf_schema/level1/eventlist.py @@ -56,26 +56,34 @@ class __header__( EVENT_ID = Int64( description="ID of this event, unique within an observation", ) + TIME = Double( description="Event time of arrival, as an MET", unit=u.s, ucd="time", reference=Ref.ogip_event_lists, ) - RA = Double( + + RA_RECO = Double( description="Reconstructed Right Ascension of event point-of-origin.", unit=u.deg, ucd="pos.eq.ra;stat.fit", reference=Ref.ogip_event_lists, ) - DEC = Double( + + DEC_RECO = Double( description="Reconstructed Declination of event point-of-origin", unit=u.deg, ucd="pos.eq.dec;stat.fit", reference=Ref.ogip_event_lists, ) - ENERGY = Double( - description="Reconstructed event energy", + + # TODO: how to deal with non-energy-like energy proxies (like n_hits) + ENERGY_RECO = Double( + description=( + "Reconstructed event energy, or proxy for that value. " + "This value can be folded through the IRF to measure true energy" + ), unit=u.TeV, ucd="phys.energy;stat.fit", reference=Ref.ogip_event_lists, diff --git a/src/vodf_schema/metadata.py b/src/vodf_schema/metadata.py index 0349a56..f0a97bc 100644 --- a/src/vodf_schema/metadata.py +++ b/src/vodf_schema/metadata.py @@ -8,7 +8,7 @@ from .references import Ref from .version import __version__ as vodf_version -URL = "https://PUT_VODF_DOCUMENTATION_URL_FOR_THIS_VERSION_HERE/" +URL = "https://vodf.readthedocs.org" __all__ = [ "VODFFormatHeader", @@ -35,13 +35,6 @@ class SpatialReferenceHeader(Header): This version of VODF supports only Earth-centered locations. """ - TREFPOS = HeaderCard( - description="Code for the spatial location at which the observation time is valid", - reference=Ref.fits_v4, - type_=str, - allowed_values=["TOPOCENTER"], - ) - OBSGEO_B = HeaderCard( keyword="OBSGEO-B", description="the latitude of the observation, with North positive", @@ -68,9 +61,36 @@ class SpatialReferenceHeader(Header): ) +# TODO: unify names of ground coordinate reference and sky coordinate reference. +class CoordinateSystemHeader(Header): + """Coordinate system definition.""" + + EQUINOX = HeaderCard( + description="Coordinate epoch. Optional since implied by RADECSYS", + reference=Ref.fits_v4, + type_=float, + unit=u.yr, + allowed_values=2000.0, + required=False, + ) + RADECSYS = HeaderCard( + description="Coordinate stellar reference frame", + reference=Ref.fits_v4, + type_=str, + allowed_values={"ICRS", "FK5"}, + ) + + class TemporalReferenceHeader(Header): """Defines the reference time, to which all time columns in the HDU are relative.""" + TREFPOS = HeaderCard( + description="Code for the spatial location at which the observation time is valid", + reference=Ref.fits_v4, + type_=str, + allowed_values=["TOPOCENTER", "RELOCATABLE"], + ) + # TODO: do we really need to split MJDREF into (I/F) parts? Is that level of # precision required? MJDREFI = HeaderCard( @@ -112,18 +132,6 @@ class TemporalReferenceHeader(Header): type_=str, allowed_values=["TT", "UTC", "UT1", "TAI", "GPS", "LOCAL"], ) - TREFPOS = HeaderCard( - description="spatial location at which the observation time is valid.", - reference=Ref.fits_v4, - type_=str, - allowed_values=[ - "TOPOCENTER", - "GEOCENTER", - "BARYCENTER", - "RELOCATABLE", - "CUSTOM", - ], - ) TIMEDEL = HeaderCard( required=False, description="time resolution in the units of TIMEUNIT, useful for binned time-series.", @@ -131,15 +139,21 @@ class TemporalReferenceHeader(Header): ) +# TODO: better name to avoid class ObservationHeader(Header): """Describes an observation.""" - OBS_ID = HeaderCard(ivoa_name="obs_id") # TODO: define better + OBS_ID = HeaderCard( + type_=str, + description="unique ID of this observation, within a Data Release.", + ucd="", + ivoa_name="ObsCore.obs_id", + ) - DATE_OBS = HeaderCard( - "DATE-OBS", + DATE_BEG = HeaderCard( + "DATE-BEG", type_=str, - description="Human-readable observation start date/time, in UTC and ISO format.", + description="Human-readable observation start date/time, in the TIMESYS scale and ISO format.", examples=["2025-01-01 15:34:21"], reference=Ref.fits_v4, ) @@ -147,7 +161,7 @@ class ObservationHeader(Header): DATE_END = HeaderCard( "DATE-END", type_=str, - description="Human-readable observation stop date/time, in UTC and ISO format", + description="Human-readable observation stop date/time, in TIMESYS scale and ISO format", examples=["2025-01-01 15:44:21"], reference=Ref.fits_v4, ) @@ -159,6 +173,7 @@ class ObservationHeader(Header): reference=Ref.fits_v4, ivoa_name="ObsCore.facility_name", ) + INSTRUME = HeaderCard( type_=str, required=False, @@ -170,16 +185,49 @@ class ObservationHeader(Header): TSTART = HeaderCard( type_=float, description="Precise start time of the observation in the units and scale defined by the temporal reference headers", - reference=Ref.ogip, + reference=Ref.fits_v4, ) TSTOP = HeaderCard( type_=float, description="Precise stop time of the observation in the units and scale defined by the temporal reference headers", - reference=Ref.ogip, + reference=Ref.fits_v4, + ) + + +class TemporalCoverageHeader(Header): + pass + + +class SpectralCoverageHeader(Header): + """Spectral coverage of this observation.""" + + E_MIN = HeaderCard( + description="Estimated minimum energy of the observation, for findability purproses.", + unit="TeV", + ivoa_name="ObsCore.em_min", + ) + E_MAX = HeaderCard( + description="Estimated maximum energy of the observation, for findability purproses.", + unit="TeV", + ivoa_name="ObsCore.em_max", ) - # TODO: Should we separate these exposure headers into another Header? +class SpatialCoverageHeader(Header): + """Spatial Coverage.""" + + RA_OBS = HeaderCard( + description="Center position of the observation, or mean in time." + ) + DEC_OBS = HeaderCard( + description="Center position of the observation, or mean in time." + ) + FOV_OBS = HeaderCard() + + pass + + +class ExposureHeader(Header): ONTIME = HeaderCard( description=( "the total 'good' time (in seconds) on 'source'. If a 'Good Time Interval' (GTI) table is provided, " @@ -188,7 +236,9 @@ class ObservationHeader(Header): ), reference=Ref.heasarc_r11, ) + LIVETIME = HeaderCard( + required=False, type_=float, unit="s", description=( @@ -269,25 +319,6 @@ class FixityHeader(Header): # Copied from GADF so far, need to update: -class CoordinateSystemHeader(Header): - """Coordinate system definition.""" - - EQUINOX = HeaderCard( - description="Coordinate epoch. Optional since implied by RADECSYS", - reference=Ref.fits_v4, - type_=float, - unit=u.yr, - allowed_values=2000.0, - required=False, - ) - RADECSYS = HeaderCard( - description="Coordinate stellar reference frame", - reference=Ref.fits_v4, - type_=str, - allowed_values={"ICRS", "FK5"}, - ) - - class Object(Header): """Name and coordinates of observerd object, if any.""" From a7962052019e71cb425108f7bb67653bcd9fdd2b Mon Sep 17 00:00:00 2001 From: Karl Kosack Date: Wed, 26 Mar 2025 13:59:09 +0100 Subject: [PATCH 2/2] added starting point for obs table --- src/vodf_schema/level1/observation.py | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/vodf_schema/level1/observation.py diff --git a/src/vodf_schema/level1/observation.py b/src/vodf_schema/level1/observation.py new file mode 100644 index 0000000..f739bb3 --- /dev/null +++ b/src/vodf_schema/level1/observation.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# Licensed un a 3-clause BSD-style license - see LICENSE + +"""The minimum contents of a "Observation HDU". + +- Headers: Observation, DataRelease, and ObservationCollection + +- Bintable: a grouping table containing the links (URI) to the HDUs of that +observation to its associated EventList, Pointing, OnTime , IRF HDUs ... qThe +sub-HDUs in the group may or may not be included, and could be fetched +separately (e.g. if the URI points to a new file) +""" + +from fits_schema import BinaryTableHeader, HeaderCard + +from ..hdu import GroupingTable +from ..metadata import ( + ObservationHeader, + SpatialReferenceHeader, + TemporalReferenceHeader, + VODFFormatHeader, +) + + +class Observation(GroupingTable): + """Defines what HDUs link to a single observation.""" + + class __header__( + VODFFormatHeader, + BinaryTableHeader, + ObservationHeader, + SpatialReferenceHeader, + TemporalReferenceHeader, + ): + EXTNAME = HeaderCard(allowed_values=["OBSERVATION"])