Skip to main content

Python bindings for the protobuf zfits library

Project description

protozfits-python

Low-level reading and writing of zfits files using google protocol buffer objects.

To analyze data, you might be more interested in using a ctapipe plugin to load your data into ctapipe. There are currently several plugins using this library as a dependency for several CTA(O) prototypes:

Note: before version 2.4, the protozfits python library was part of the adh-apis Repository.

To improve maintenance, the two repositories were decoupled and this repository now only hosts the python bindings (protozfits). The needed C++ libZFitsIO is build from a git submodule of the adh-apis.

Table of Contents

Installation

Users

This package is published to PyPI and conda-forge. PyPI packages include pre-compiled manylinux wheels (no macOS wheels though) and conda packages are built for Linux and macOS.

When using conda, it's recommended to use the miniforge conda distribution, as it is fully open source and comes with the faster mamba package manager.

So install using:

pip install protozfits

or

mamba install protozfits

For development

This project is build using scikit-build-core, which supports editable installs recompiling the project on import by setting a couple of config-options for pip. See https://scikit-build-core.readthedocs.io/en/latest/configuration.html#editable-installs.

To setup a development environment, create a venv, install the build requirements and then run the pip install command with the options given below:

$ python3 -m venv venv
$ source venv/bin/activate
$ pip install 'scikit-build-core[pyproject]' pybind11 'setuptools_scm[toml]'
$ pip install -e '.[all]' --no-build-isolation

You can now e.g. run the tests:

$ pytest src

scikit-build-core will automatically recompile the project when importing the library. Some caveats remain though, see the scikit-build-core documentation linked above.

Usage

If you are just starting with proto-z-fits files and would like to explore the file contents, try this:

Open a file

>>> from protozfits import File
>>> example_path = 'protozfits/tests/resources/example_9evts_NectarCAM.fits.fz'
>>> file = File(example_path)
>>> file
File({
    'RunHeader': Table(1xDataModel.CameraRunHeader),
    'Events': Table(9xDataModel.CameraEvent)
})

From this we learn, the file contains two Table named RunHeader and Events which contains 9 rows of type CameraEvent. There might be more tables with other types of rows in other files. For instance LST has its RunHeader called CameraConfig.

Getting an event

Usually people just iterate over a whole Table like this:

for event in file.Events:
    # do something with the event
    pass

But if you happen to know exactly which event you want, you can also directly get an event, like this:

event_17 = file.Events[17]

You can also get a range of events, like this:

for event in file.Events[100:200]:
    # do something events 100 until 200
    pass

It is not yet possible to specify negative indices, like file.Events[:-10] does not work.

If you happen to have a list or any iterable or a generator with event ids you are interested in you can get the events in question like this:

interesting_event_ids = range(100, 200, 3)
for event in file.Events[interesting_event_ids]:
    # do something with intesting events
    pass

RunHeader

Even though there is usually only one run header per file, technically this single run header is stored in a Table. This table could contain multiple "rows" and to me it is not clear what this would mean... but technically it is possible.

At the moment I would recommend getting the run header out of the file we opened above like this (replace RunHeader with CameraConfig for LST data):

assert len(file.RunHeader) == 1
header = file.RunHeader[0]

For now, I will just get the next event

event = file.Events[0]
type(event)
<class 'protozfits.CameraEvent'>
event._fields
('telescopeID', 'dateMJD', 'eventType', 'eventNumber', 'arrayEvtNum', 'hiGain', 'loGain', 'trig', 'head', 'muon', 'geometry', 'hilo_offset', 'hilo_scale', 'cameraCounters', 'moduleStatus', 'pixelPresence', 'acquisitionMode', 'uctsDataPresence', 'uctsData', 'tibDataPresence', 'tibData', 'swatDataPresence', 'swatData', 'chipsFlags', 'firstCapacitorIds', 'drsTagsHiGain', 'drsTagsLoGain', 'local_time_nanosec', 'local_time_sec', 'pixels_flags', 'trigger_map', 'event_type', 'trigger_input_traces', 'trigger_output_patch7', 'trigger_output_patch19', 'trigger_output_muon', 'gps_status', 'time_utc', 'time_ns', 'time_s', 'flags', 'ssc', 'pkt_len', 'muon_tag', 'trpdm', 'pdmdt', 'pdmt', 'daqtime', 'ptm', 'trpxlid', 'pdmdac', 'pdmpc', 'pdmhi', 'pdmlo', 'daqmode', 'varsamp', 'pdmsum', 'pdmsumsq', 'pulser', 'ftimeoffset', 'ftimestamp', 'num_gains')
event.hiGain.waveforms.samples
array([241, 245, 248, ..., 218, 214, 215], dtype=int16)

An LST event will look something like so:

>>> event
CameraEvent(
    configuration_id=1
    event_id=1
    tel_event_id=1
    trigger_time_s=0
    trigger_time_qns=0
    trigger_type=0
    waveform=array([  0,   0, ..., 288, 263], dtype=uint16)
    pixel_status=array([ 0,  0,  0,  0,  0,  0,  0, 12, 12, 12, 12, 12, 12, 12], dtype=uint8)
    ped_id=0
    nectarcam=NectarCamEvent(
        module_status=array([], dtype=float64)
        extdevices_presence=0
        tib_data=array([], dtype=float64)
        cdts_data=array([], dtype=float64)
        swat_data=array([], dtype=float64)
        counters=array([], dtype=float64))
    lstcam=LstCamEvent(
        module_status=array([0, 1], dtype=uint8)
        extdevices_presence=0
        tib_data=array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=uint8)
        cdts_data=array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
               0, 0, 0, 0, 0, 0, 0, 0], dtype=uint8)
        swat_data=array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
               0, 0, 0, 0], dtype=uint8)
        counters=array([  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
                 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
                 0,   0,   1,   0,   0,   0,  31,   0,   0,   0, 243, 170, 204,
                 0,   0,   0,   0,   0], dtype=uint8)
        chips_flags=array([    0,     0,     0,     0,     0,     0,     0,     0, 61440,
                 245, 61440,   250, 61440,   253, 61440,   249], dtype=uint16)
        first_capacitor_id=array([    0,     0,     0,     0,     0,     0,     0,     0, 61440,
                 251, 61440,   251, 61440,   241, 61440,   245], dtype=uint16)
        drs_tag_status=array([ 0, 12], dtype=uint8)
        drs_tag=array([   0,    0, ..., 2021, 2360], dtype=uint16))
    digicam=DigiCamEvent(
        ))
>>> event.waveform
array([  0,   0,   0, ..., 292, 288, 263], dtype=uint16)

event supports tab-completion, which I regard as very important while exploring. It is implemented using collections.namedtuple. I tried to create a useful string representation, it is very long, yes ... but I hope you can still enjoy it:

>>> event
CameraEvent(
    telescopeID=1
    dateMJD=0.0
    eventType=<eventType.NONE: 0>
    eventNumber=97750287
    arrayEvtNum=0
    hiGain=PixelsChannel(
        waveforms=WaveFormData(
            samples=array([241, 245, ..., 214, 215], dtype=int16)
            pixelsIndices=array([425, 461, ..., 727, 728], dtype=uint16)
            firstSplIdx=array([], dtype=float64)
            num_samples=0
            baselines=array([232, 245, ..., 279, 220], dtype=int16)
            peak_time_pos=array([], dtype=float64)
            time_over_threshold=array([], dtype=float64))
        integrals=IntegralData(
            gains=array([], dtype=float64)
            maximumTimes=array([], dtype=float64)
            tailTimes=array([], dtype=float64)
            raiseTimes=array([], dtype=float64)
            pixelsIndices=array([], dtype=float64)
            firstSplIdx=array([], dtype=float64)))
# [...]

Table header

fits.fz files are still normal FITS files and each Table in the file corresponds to a so called "BINTABLE" extension, which has a header. You can access this header like this:

>>> file.Events
Table(100xDataModel.CameraEvent)
>>> file.Events.header
# this is just a sulection of all the contents of the header
XTENSION= 'BINTABLE'           / binary table extension
BITPIX  =                    8 / 8-bit bytes
NAXIS   =                    2 / 2-dimensional binary table
NAXIS1  =                  192 / width of table in bytes
NAXIS2  =                    1 / number of rows in table
TFIELDS =                   12 / number of fields in each row
EXTNAME = 'Events'             / name of extension table
CHECKSUM= 'BnaGDmS9BmYGBmY9'   / Checksum for the whole HDU
DATASUM = '1046602664'         / Checksum for the data block
DATE    = '2017-10-31T02:04:55' / File creation date
ORIGIN  = 'CTA'                / Institution that wrote the file
WORKPKG = 'ACTL'               / Workpackage that wrote the file
DATEEND = '1970-01-01T00:00:00' / File closing date
PBFHEAD = 'DataModel.CameraEvent' / Written message name
CREATOR = 'N4ACTL2IO14ProtobufZOFitsE' / Class that wrote this file
COMPILED= 'Oct 26 2017 16:02:50' / Compile time
TIMESYS = 'UTC'                / Time system
>>> file.Events.header['DATE']
'2017-10-31T02:04:55'
>>> type(file.Events.header)
<class 'astropy.io.fits.header.Header'>

The header is provided by astropy.

pure protobuf mode

The library by default converts the protobuf objects into namedtuples and converts the AnyArray data type to numpy arrays. This has some runtime overhead. In case you for example know exactly what you want from the file, then you can get a speed-up by passing the pure_protob=True option:

>>> from protozfits import File
>>> file = File(example_path, pure_protobuf=True)
>>> event = next(file.Events)
>>> type(event)
<class 'ProtoDataModel_pb2.CameraEvent'>

Now iterating over the file is faster than before. But you have no tab-completion and some contents are less useful for you:

>>> event.eventNumber
97750288   # <--- just fine
>>> event.hiGain.waveforms.samples

type: S16
data: "\362\000\355\000 ... "   # <---- goes on "forever" .. raw bytes of the array data
>>> type(event.hiGain.waveforms.samples)
<class 'CoreMessages_pb2.AnyArray'>

You can convert these AnyArrays into numpy arrays like this:

>>> from protozfits import any_array_to_numpy
>>> any_array_to_numpy(event.hiGain.waveforms.samples)
array([242, 237, 234, ..., 218, 225, 229], dtype=int16)

Command-Line Tools

This module comes with a command-line tool that can re-compress zfits files using different options for the default and specific column compressions. This can also be used to extract the first N events from a large file, e.g. to produce smaller files for unit tests.

Usage:

$ python -m protozfits.recompress_zfits --help

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

protozfits-2.12.0.tar.gz (13.8 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

protozfits-2.12.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (13.9 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

protozfits-2.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (12.4 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

protozfits-2.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (12.4 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

protozfits-2.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (12.4 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

protozfits-2.12.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (12.4 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64

protozfits-2.12.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (12.4 MB view details)

Uploaded CPython 3.9manylinux: glibc 2.17+ x86-64

File details

Details for the file protozfits-2.12.0.tar.gz.

File metadata

  • Download URL: protozfits-2.12.0.tar.gz
  • Upload date:
  • Size: 13.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for protozfits-2.12.0.tar.gz
Algorithm Hash digest
SHA256 211e421f96ed2538d43604fa34f8ae6256919166375d2836a24614ec86674836
MD5 395aa683905d028e9942d1cd8b1f9837
BLAKE2b-256 b7b39715e193de2bbc018c74cb0ad2989579264ff8a708195167d70286aba7cf

See more details on using hashes here.

File details

Details for the file protozfits-2.12.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for protozfits-2.12.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 837883dc72111e1f770e2bcd8f5831540e9360d87a7147d684f83b133b9f47ab
MD5 4f80392ab3104aa4fa7307e03ca7dc40
BLAKE2b-256 4a7ce06140ccde6e57b6e789f19f94462b30de35bcc07473cac22a89e7d3d90e

See more details on using hashes here.

File details

Details for the file protozfits-2.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for protozfits-2.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 51555e64bab35db4b75f56318b83ef8b19630e7190a16a37ed9d837780c54eb4
MD5 31a1b2ab351b3a232227849c06b9c449
BLAKE2b-256 fdcfa1e4e80d81bddf0666acf47a8f5fd079c9ec70713656cd2a29107427f5dc

See more details on using hashes here.

File details

Details for the file protozfits-2.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for protozfits-2.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 20f73455aa5be2a24c5829517fb3fbdd87f4b1e9c2fbcf871e2efeee8a1b7526
MD5 9ed174dc4f6f7bd48f4d537fc927807c
BLAKE2b-256 ea3df61b5d4fcaf58a2d0d8a2df7849cb21ecfcd3d5103a0bc5f41df95e0171e

See more details on using hashes here.

File details

Details for the file protozfits-2.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for protozfits-2.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 7aec30cfb5526a37839f6338c474e5d3f19bbc071be27ff503b4cef308abefde
MD5 d8f4c93f688adb6b86d4831415df1539
BLAKE2b-256 71cc66635fc066857755afee408c2713d02e6e352e5bfc41d80cfc384e089fb0

See more details on using hashes here.

File details

Details for the file protozfits-2.12.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for protozfits-2.12.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 ba2761fee7bb14632849c1b4ddbfb50214d882f52d01053ee58da4765c29fa59
MD5 fbffa23f81ab46e953ec0d1b1d959761
BLAKE2b-256 c4ef17a2d8fd2e9adb605b771ce33079f6c48e9d45fa9d3d9494fa3065845b35

See more details on using hashes here.

File details

Details for the file protozfits-2.12.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for protozfits-2.12.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 3a4a8a4068d5dc0219b168866f13c63b0e8ecad0126691db352f947bb6e80b7d
MD5 92088927d3e26be1a052e46e1461fef4
BLAKE2b-256 93c488e1224e12e51220e78012736f165c1bdc7dc5ddca9e35d2690549f40317

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page