% \iffalse meta-comment % % ┌─────────────────────────────────────────────────────────────┐ % │ │ % │ The “fits-astro” package │ % │ Retrieving FITS file header information in LuaTeX │ % │ │ % │ Copyright (C) 2026 Valentin Dao │ % │ │ % │ This file may be distributed and/or modified under the │ % │ conditions of the LaTeX Project Public License, either │ % │ version 1.3c of this license or (at your option) any │ % │ later version. │ % │ │ % │ The latest version of this license is in: │ % │ │ % │ > http://www.latex-project.org/lppl.txt │ % │ │ % │ This work has the LPPL maintenance status 'maintained' │ % │ and the current maintainer is Valentin Dao │ % │ (vdao.texdev@gmail.com). │ % │ │ % │ This work consists of the files listed in the │ % │ 'MANIFEST.md' file. │ % │ │ % │ The repository of this work can be found here: │ % │ │ % │ > https://github.com/TeXackers/fits-astro │ % │ │ % └─────────────────────────────────────────────────────────────┘ % %<*driver> % \fi % \iffalse \documentclass[11pt, a4paper]{l3doc} \catcode`\@=11 \usepackage{fontspec} \IfFontExistsTF{ChronicleTextG3} { \setmainfont{ChronicleTextG3}[ Extension = .otf, UprightFont = *-Roman-Pro, UprightFeatures = { Kerning = On }, ItalicFont = *-Italic-Pro, BoldFont = *-Bold-Pro, BoldItalicFont = *-BoldIta-Pro, ] }{} \IfFontExistsTF{Whitney} { \setsansfont{Whitney}[ Extension = .otf, UprightFont = *-Medium, ItalicFont = *-MediumItalic, BoldFont = *-Bold, BoldItalicFont = *-BoldItalic, UprightFeatures = { StylisticSet = 1, StylisticSet = 10, StylisticSet = 11, }, BoldFeatures = { StylisticSet = 1, StylisticSet = 10, StylisticSet = 11, }, ItalicFeatures = { StylisticSet = 1, StylisticSet = 10, StylisticSet = 11, StylisticSet = 8, StylisticSet = 9 } ] }{} \IfFontExistsTF{MonospaceNeon} { \setmonofont{MonaspaceNeon}[ Extension = .otf, Scale = 0.8, UprightFont = *-Regular, UprightFeatures = { Kerning = On }, ItalicFont = *-Italic, BoldFont = *-Bold, BoldItalicFont = *-BoldItalic, ] }{} \usepackage{microtype} \ExplSyntaxOn \debug_on:n { all } \usepackage{fits-astro} \debug_off:n { all } \ExplSyntaxOff \usepackage[ a4paper, marginparwidth = 32mm, top = 3cm, bottom = 2cm, left = 44mm, right = 30mm, marginparsep = 2mm, ]{geometry} \usepackage{fancyhdr} \fancyhf{} \fancyfoot[C]{\small\sffamily\itshape---\space\thepage\space\kern0.4mm\/---} \renewcommand{\headrulewidth}{0pt} \pagestyle{fancy} \usepackage[silence]{codedescribe} \usepackage{hologo} \usepackage[svgnames]{xcolor} \definecolor{RoyalBlue}{RGB}{0, 35, 102} \definecolor{RoyalRed}{RGB}{157, 16, 45} \definecolor{RoyalGreen}{HTML}{48845F} \definecolor{urlcolour}{HTML}{B42F5E} \definecolor{packagecolour}{HTML}{B86B00} \definecolor{keycolour}{HTML}{004766} \definecolor{macrocolour}{HTML}{047658} \definecolor{macro3colour}{HTML}{27262E} \definecolor{optioncolour}{HTML}{630476} \defobjectfmt{macro}{code}{color=macrocolour} \defobjectfmt{macro3}{code}{color=macro3colour} \defobjectfmt{variable3}{code}{color=SlateGrey} \defobjectfmt{pkg}{pkg}{font=\ttfamily} \defobjectfmt{option}{option}{color=optioncolour, shape preadj=thin, shape posadj=thin, noshape} \defgroupfmt{keys}{color=keycolour, noshape} \newcodekey{fits}{ ruleht = 0, texcsstyle = \bfseries\color{RoyalBlue}, texcs = [2]{ loadheader, showheader, headerkey, setheaderkey, gsetheaderkey, HST, ra, dec, exptime }, texcsstyle = [2]{\color{macrocolour}}, codeprefix = {}, resultprefix = {} } \setcodelabels{ new = \textsf{new}, update = \textsf{update}, note = \textsf{N.B}, remark = \textsf{remark}, and = \textsf{and}, or = \textsf{or}, } \lstset{ gobble=2, } \usepackage{titlesec} \titleformat*{\section}{\sffamily\LARGE\bfseries} \titleformat*{\subsection}{\sffamily\Large\bfseries} \usepackage{changelog} \usepackage{enumitem} \renewenvironment{changelogitemize} {\begin{itemize}[label=\raisebox{0.5ex}{$\scriptscriptstyle\blacktriangleright$}]} {\end{itemize}} \setlength{\parindent}{0pt} \usepackage{polyglossia} \setmainlanguage{english} \usepackage[perpage]{footmisc} \usepackage{hyperref} \hypersetup{ pdfauthor = {Valentin Dao}, pdftitle = {The fits-astro package}, pdfcreator = {LuaLaTeX with hyperref package}, colorlinks = true, linkcolor = RoyalBlue, urlcolor = urlcolour, } \title{The \href{https://ctan.org/pkg/fits-astro}{\textsf{\fitsastro@module}} package\medbreak{\Large\fitsastro@version}} \author{Valentin Dao\footnote{E-mail: \href{mailto:vdao.texdev@gmail.com}{\ttfamily vdao.texdev@gmail.com}}} \date{\today} \listfiles \EnableCrossrefs \EnableDocumentation \EnableImplementation \begin{document} \DocInput{fits-astro-en.dtx} \end{document} % % \fi % % \begin{documentation} % % \maketitle % The \tsobj[pkg]{\fitsastro@module} package provides a simple interface to read \href{https://www.stsci.edu/hst/wfpc2/Wfpc2_dhb/intro_ch23.html}{Flexible Image Transport System} (FITS) files and access their header to retrieve information. It relies on a custom Lua script and therefore requires the Lua\hologo{LaTeXTeX} engine. Unlike more complex libraries, such as \texttt{astropy.io.fits} in Python, this package is designed solely for directly recovering key-value pairs from headers. It is \emph{not} intended for modifying them or fixing standardisation issues, let alone for reading the data part. Also note that as of now, the package \emph{does not} support files with multiple Header Data Units (HDUs) and will only read the primary header; i.e, all extensions will be ignored. % % \section{Loading the package} % % The package may be loaded in either of the following ways: % \begin{itemize} % \item \tsobj[code]{\usepackage{fits-astro}} if compiling with Lua\LaTeX. % \item \tsobj[code]{\input{fits-astro.tex}} if compiling with Lua\TeX. % \end{itemize} % % \section{Package macros} % % \begin{codedescribe}[macro]{\loadheader} % \begin{codesyntax} % \tsobj[code]{\loadheader}\tsargs[marg]{FITS identification}\tsargs[marg]{FITS file path} % \end{codesyntax} % This macro reads the \texttt{FITS} file and allocates the header information to the specified FITS identification, which can either be a string or a control sequence. In the latter case, \tsobj[macro]{\csstring} is used to strip off the leading escape character for internal storage. Therefore, if a control sequence is specified, it does not actually contain the header in any way. As a result, the two inputs are interchangeable. The macro furthermore uses the \texttt{Kpathsea} library, so the files shouldn't necessarily be in the current directory. This documentation will use a \href{https://fits.gsfc.nasa.gov/fits_samples.html}{public sample image} to demonstrate the package's macros (the image used is the first one in the table). % \end{codedescribe} % % \begin{codestore}[loadfits] % \loadheader\HST{WFPC2u5780205r_c0fx.fits} % \loadheader{HST}{WFPC2u5780205r_c0fx.fits} % Alternative way to load the file % \end{codestore} % % \tscode*[fits]{loadfits} % \loadheader\HST{../../WFPC2u5780205r_c0fx.fits} % % \begin{codedescribe}[macro]{\showheader} % \begin{codesyntax} % \tsobj[code]{\showheader}\tsargs[marg]{FITS identification} % \end{codesyntax} % This macro displays the header information for the specified FITS identification in the log file. % \end{codedescribe} % % \begin{codestore}[showfits] % \showheader\HST % \showheader{HST} % Would produce the same result % \end{codestore} % % \tscode*[fits]{showfits} % % \begin{codedescribe}[macro]{\headerkey} % \begin{codesyntax} % \tsobj[code]{\headerkey}\tsargs[marg]{FITS identification}\tsargs[marg]{key} % \end{codesyntax} % This macro retrieves the value associated with the specified key from the header of the given FITS identification. % \end{codedescribe} % % \begin{codestore}[keyfits] % \headerkey\HST{FITSDATE}\par % \headerkey\HST{TELESCOP} % \end{codestore} % % \tsdemo[fits]{keyfits} % % \begin{codedescribe}[macro]{\setheaderkey, \gsetheaderkey} % \begin{codesyntax} % \tsobj[code]{\setheaderkey}\tsargs[marg]{FITS identification}\tsargs[marg]{key}\tsargs[marg]{control sequence} % \end{codesyntax} % This macro retrieves the value associated with the specified key from the header of the given FITS identification and assigns it to the specified control sequence. The assignment is either local to the current group or global. % \end{codedescribe} % % \begin{codestore}[setfitskey] % \ttfamily % \begingroup % \setheaderkey\HST{RA_TARG}\ra % \setheaderkey\HST{DEC_TARG}\dec % \gsetheaderkey\HST{EXPTIME}\exptime % \meaning\ra\par % \meaning\dec\par % \meaning\exptime\par % \endgroup % \meaning\exptime\par % \meaning\ra\par % \meaning\dec % \end{codestore} % % \tsdemo[fits]{setfitskey} % % \begin{changelog}[sectioncmd=\section*] % \begin{version}[v=1.0.1, date=09-07-2026] % \added % \item French documentation. % \fixed % \item Improved Lua script and documentations. % \end{version} % \shortversion{v=1.0.0, date=06-04-2026, changes=Initial version.} % \end{changelog} % % \end{documentation} % % \begin{implementation} % % \newpage % % \section{Implementation} % % \subsection{\TeX\ package} % % \begin{macrocode} %<*package-tex> \ifdefined\loadheader\expandafter\endinput\fi \def\FITSerr#1{\errmessage{Package fits Error: #1}} \def\FITSwar#1{\message{Package fits Warning: #1}} \unless\ifdefined\directlua\FITSerr{LuaTeX required}\fi \directlua{dofile('fits-astro.lua')} \def\loadheader#1#2{ \directlua{ local file = '\csstring#1' if FITS_data[file] then tex.print('\luaescapestring{\FITSerr{FITS file '#2' already loaded}}') else FITS_data[file] = load_FITS_header('#2') end } } \def\showheader#1{ \directlua{ show_fits(FITS_data['\csstring#1']) } } \def\headerkey#1#2{ \directlua{ if FITS_data['\csstring#1']['#2'] then get_header_key(FITS_data['\csstring#1'], '#2') else tex.print('\luaescapestring{\FITSwar{Header key '#2' not found}}') end } } \def\setheaderkey#1#2#3{ \directlua{ if FITS_data['\csstring#1']['#2'] then token.set_macro('\csstring#3', FITS_data['\csstring#1']['#2']) end } } \def\gsetheaderkey#1#2#3{ \directlua{ if FITS_data['\csstring#1']['#2'] then token.set_macro('\csstring#3', FITS_data['\csstring#1']['#2'], 'global') end } } % % \end{macrocode} % \subsection{\LaTeX\ package} % \c@CodelineNo=\z@ % \begin{macrocode} %<*package-latex> \NeedsTeXFormat{LaTeX2e} \def\fitsastro@module{fits-astro} \def\fitsastro@version{v1.0.1} \def\fitsastro@date{2026-07-09} \def\fitsastro@description{Retrieving FITS file header information in LuaTeX} \ProvidesPackage{fits-astro}[\fitsastro@date\space\fitsastro@version\space\fitsastro@description] \input{fits-astro.tex} % % \end{macrocode} % \subsection{Lua script} % \c@CodelineNo=\z@ % \begin{macrocode} %<*package-lua> tex = tex or {} texio = texio or {} FITS_data = {} function load_FITS_header(file) local file_path = kpse.find_file(file) if not file_path then tex.error("FITS file not found: " .. file) else local FITS_file = io.open(file_path, "rb") local header = {} repeat local header_line = FITS_file:read(80) if not header_line or #header_line < 80 then break end -- string local key, value = header_line:match("([%u%-%d_]+)%s*=%s*'([^']*)'") if not key then -- number or boolean key, value = header_line:match("([%u%-%d_]+)%s*=%s*([^/]+)") end if key and value then value = sanitize_FITS_key(value) if value:match("^%s*$") then header[key] = nil else header[key] = parse_FITS_key(key, value) end end until header_line:sub(1, 3) == "END" FITS_file:close() return header end end function parse_FITS_key(key, value) -- converts FITS standard status to a boolean if key == "SIMPLE" then if value == "T" then value = true else value = false end return value end -- converts numbers into proper integers local num = tonumber(value) if num then value = num end return value end function sanitize_FITS_key(value) value = value:match("^%s*(.-)%s*$") or value return value end function get_header_key(header, key) local value = header[key] if value then tex.print(value) end end function show_fits(header) texio.write_nl("FITS header information:") for key, value in pairs(header) do texio.write_nl(string.format("%s = %s", key, tostring(value))) end end % % \end{macrocode} % \end{implementation} % % \newpage % % \PrintIndex