EuclidRust – Rust FITS reader for Euclid, 10x faster than astropy A Rust-based FITS reader for Python, euclidrust, reports reading speeds 10x faster than astropy, according to the project's own benchmarks. The package, built with PyO3 and installable via pip, was developed as a testbed for ESA Euclid mission data-processing tools and handles 2-D FITS images, including Rice and GZIP tile-compressed files, with exact 64-bit integer handling. The project's code, benchmarks and documentation were written by Anthropic's Claude Code coding agent under the author's direction, and the author describes it as experimental, urging users to run the benchmark themselves. High-performance FITS I/O for Python, written in Rust and exposed via PyO3 https://pyo3.rs/ as a normal, pip install -able Python extension module. Built as a testbed for data-processing tools for the ESA Euclid mission, but useful for any FITS-based workflow. The Python package it contains, and the name you import , is euclidrust . python import euclidrust stats = euclidrust.fits stats "tile.fits" cutout = euclidrust.extract cutout "tile.fits", ra deg=83.82, dec deg=-5.39, width=50, height=50 euclidrust.export cutout parquet "tile.fits", 83.82, -5.39, 50, 50, "cutout.parquet" About this project. The code, benchmarks and documentation here were written by Claude Code https://claude.com/claude-code Anthropic's coding agent . The author decided what to build, in what order, how to measure it and when to stop, and steered the agent throughout. Treat the project as experimental: read the code, run the benchmark yourself, and judge the numbers rather than the author. Contents: What files it works with what-kind-of-files-does-it-work-with · How Python and Rust fit together how-python-and-rust-fit-together-pyo3 · Install install · Use it use-it · Many images at once many-images-at-once · NVIDIA GPU using-an-nvidia-gpu · Tests run-the-tests-optional · Run the benchmark run-the-benchmark-on-your-own-fits-file · Troubleshooting troubleshooting · Motivation motivation · Benchmark results benchmark-results · Limitations known-limitations-read-before-relying-on-this-for-science · Roadmap roadmap Short answer: 2-D FITS images, raw or tile-compressed Rice / GZIP , whether the image sits in the primary HDU or in an extension. FITS Flexible Image Transport System is the standard file format of astronomy. A FITS file is a sequence of blocks called HDUs "header/data units" : each has a text header and a payload, which can be an image or a table. euclidrust reads images : a rectangular grid of pixels a sky mosaic, a detector frame... , plus the header keywords that say how pixels map to the sky the WCS . Where the image sits depends on the product. A Euclid MER mosaic keeps its image in the first HDU the "primary HDU" , which is what euclidrust reads by default. Calibrated frames VIS, NISP and raw frames have an empty primary HDU and one image extension per detector, with names such as DET11.SCI or 1-1.E . For those you pick the HDU with the hdu= argument an index or a name , and list hdus path shows what a file contains. Supported: | Requirement | Details | |---|---| | An image HDU | 2-D NAXIS = 2 . The primary HDU by default, or any image extension through hdu= 0-based index or EXTNAME | | Compression | None, or FITS tile compression with RICE 1 , GZIP 1 or GZIP 2 the .fits.fz format written by fpack and by astropy's CompImageHDU , including quantized floating-point images NO DITHER , SUBTRACTIVE DITHER 1 , SUBTRACTIVE DITHER 2 . A compressed image always lives in an extension, so pass hdu=1 or its name | | Pixel type BITPIX | 8 , 16 , 32 , 64 integers , -32 , -64 floating point . BZERO / BSCALE are applied, so unsigned 16-bit raw data BZERO = 32768 works. 64-bit integers, signed or unsigned FITS stores unsigned 64-bit as signed with BZERO = 2^63 , are handled exactly , in raw and in gzip-compressed images cfitsio compresses 64-bit integers with gzip only , including a whole-number BSCALE / BZERO : the exact values are in min int / max int fits stats and data int extract cutout , as Python ints, and the Parquet value column is Int64 / UInt64 ; the ordinary float fields hold the nearest float64 | | WCS, only needed for cutouts | CTYPE1/2 = RA---xxx / DEC--xxx with xxx one of TAN , SIN , ARC , STG , ZEA , ZPN the zenithal projections , TAN-SIP tangent plus the SIP polynomial distortion or TPV tangent plus the PV1 m / PV2 m polynomials used by the Euclid VIS pipeline , together with CRVAL1/2 , CRPIX1/2 and a linear matrix written as CD CD1 1 , CD1 2 , CD2 1 , CD2 2 , as CDELT1/2 with an optional PC matrix, or as CDELT1/2 with the legacy CROTA2 . LONPOLE is honoured. fits stats needs no WCS | Not supported. Anything else is rejected with a ValueError that says why, rather than being read wrongly these cases are covered by tests/test input validation.py : | Kind of file | Error you get | |---|---| | Tile compression with another algorithm HCOMPRESS 1 , PLIO 1 | HDU 1 SCI : tile-compressed image uses ZCMPTYPE = HCOMPRESS 1 which is not supported yet supported: RICE 1, GZIP 1, GZIP 2 | | An extension that is a table catalogue, spectrum... | HDU 1 is a BINTABLE table, not an image | | 3-D cubes NAXIS = 3 | NAXIS = 3 is not supported ...: only 2-D images are handled | | Not a FITS file | truncated file: no END card found | | A projection that is not implemented cylindrical or all-sky ones such as CAR , AIT , MOL , HPX ... , a mismatched CTYPE pair, or a missing CTYPE | Cutouts raise unsupported WCS projection: found CTYPE1/CTYPE2 = ...; supported are RA---TAN/DEC--TAN also with -SIP distortion , RA---TPV/DEC--TPV and the zenithal projections SIN, ARC, STG, ZEA and ZPN ... fits stats still works | | Distortion stored in lookup tables HST CPDIS / D2IMDIS keywords | Cutouts raise this header declares distortion corrections stored in lookup tables ... which euclidrust does not apply fits stats still works | | No WCS at all no CRVAL / CRPIX / matrix keywords | Cutouts raise this file has no WCS header CRVAL, CRPIX and either a CD matrix or CDELT/PC fits stats still works | Null pixels. NaN pixels of floating-point images are left out of min , max and mean and counted in n nan infinities are values and stay in . The BLANK null value of integer images is not honoured: those pixels are counted as ordinary values. If you forget hdu= on a file whose primary HDU is empty, the error lists the image HDUs it found and tells you what to pass, for example 1 DET11.SCI 2040x2040, BITPIX -32 ... Pass hdu=