Skip to content
aliperdehanPublic

About

Infrared spectra for chemistry students: OMNIC .SPA and JCAMP-DX in, tidy CSVs, peak lists and report-ready figures out.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

irspec

Infrared spectra for chemistry students. Put the files from the instrument, from NIST WebBook or from SDBS in a folder, run one command, and get tidy CSVs, peak lists and report-quality figures (PDF, PNG, TIFF, SVG) back.

An IR spectrum of phenacetin, drawn by irspec

Above: the author's own phenacetin spectrum, from irspec session with --formats png.

irspec session my_lab_folder/ --formats png,svg

Contents: Status · What it does · Install · Quick start · Inputs · Names · CSV files · Manifest · Editing · Pictures · Without TeX · PDF build · All commands · Trust · Versioning · Roadmap · Development · Licence

Status

Alpha, version 0.1.0. The command line and the folder layout below work and are tested, but before 1.0 a new minor version (0.2, 0.3, ...) may change them: pin the minor version if something depends on irspec. The Python code (import irspec) is not a stable interface before 1.0: use the command line. See Versioning and CHANGELOG.md.

What it does, and what it does not

It does:

  • read the Thermo OMNIC .SPA/.SPG files of a Nicolet iS5 FTIR (the instrument it was written for; other OMNIC files are read the same way, but only this one is tested), NIST WebBook .jdx (JCAMP-DX) files, and spectra that exist only as a picture (SDBS plots) by reading the trace off the image;
  • write each spectrum as two CSVs (see CSV files), pick its peaks and draw it with the labels kept apart;
  • put every spectrum on the same axis, %Transmittance: an Absorbance spectrum is converted on purpose, never mixed in silently;
  • make a PDF with one spectrum per landscape page, and PNG/TIFF/SVG copies of each page named after the spectrum;
  • or draw the same figures without TeX, with matplotlib or with R (ggplot2), if you would rather have those (below).

It does not interpret spectra, assign bands or identify compounds. The peak labels are wavenumbers; deciding what a band is stays your job. Peak picking is a convenience, and the manifest lets you correct it (below).

Install

Needs Python 3.9 or newer.

pip install irspec       # or: pipx install irspec
irspec --version         # python -m irspec --version does the same

From a checkout of the repository instead: pip install . (or pip install -e . while developing).

This pulls in numpy, scipy and Pillow. The rest are programs irspec calls, and only the ones for the output you ask for are needed:

You want Needs macOS Debian/Ubuntu
a PDF lualatex with pgfplots (TeX Live or MacTeX) brew install --cask mactex-no-gui apt install texlive-full
PNG, TIFF, SVG poppler (pdfinfo, pdftoppm, pdftocairo) brew install poppler apt install poppler-utils
SVG with editable text MuPDF (mutool) brew install mupdf-tools apt install mupdf-tools
matplotlib figures (irspec plot) the matplotlib package: pip install ".[matplotlib]"
R figures (irspec plot --backend r) R with ggplot2, jsonlite; ragg, svglite recommended brew install --cask r apt install r-base
digitize --omnic tesseract brew install tesseract apt install tesseract-ocr

The figures use the font STIX Two when it is installed. When it is not, they use the copies bundled with irspec (see Licence), so a figure looks the same on a machine that does not have the font. If a program is missing, irspec says which one and what to install instead of failing with a traceback.

On macOS the Homebrew python3 refuses a bare pip install ("externally managed environment"); use a virtual environment (python3 -m venv ~/irspec-venv && ~/irspec-venv/bin/pip install .) or pipx.

Quick start

  1. Make a folder and drop the files in it. Loose files are sorted for you: .SPA/.SPG into SPA/, .jdx into jdx/, anything else into other/. Pictures are the exception: put an SDBS plot (.gif, .jpg, .png, .bmp) in img/ yourself, because a screenshot of the instrument and a published plot are both "an image" and irspec cannot tell which you mean.

  2. Run it:

    irspec session my_lab_folder/
    
  3. Look at the PDF it wrote, then fix whatever you disagree with in manifest.json (a title, a peak) and run the same command again. A re-run keeps your edits.

After a run on one .SPA file the folder looks like this:

my_lab_folder/
  SPA/PHENACETIN_CRUDE_LR1-2.SPA            the file, moved here
  csv/PHENACETIN_CRUDE_LR1-2.csv            archival CSV
  plain/PHENACETIN_CRUDE_LR1-2_plain.csv    the CSV the figure reads
  manifest.json                             titles, peaks, per-spectrum settings
  figures.json                              the figures as the drawing backends read them
  ir_my_lab_folder.md                       the figure list, for nulabreport's pdfmd
  ir_my_lab_folder.tex                      the same for plain lualatex  (--build latex)
  ir_my_lab_folder.pdf                      the figure PDF, one spectrum per page
  figures/PHENACETIN_CRUDE_LR1-2.png        with --formats (from the PDF)
  figures/matplotlib/..., figures/r/...     with irspec plot

The base name ir_... is ir_<dd-mm> when the folder's name contains a date (own-29.09 gives ir_29-09), else ir_ and the folder's name. --md-name NAME chooses it yourself.

Options of irspec session

Option Meaning
--dry-run print the file moves and stop
--preview --dry-run, and then every spectrum drawn as text with its peaks and the names it will get; writes nothing, see Preview a whole folder (takes --width, --height, --ascii, --json as irspec preview does)
--build auto|pdfmd|latex|none how the PDF is made (--no-pdf is none); the run says which build it used, see How the PDF is made
--pdfmd PATH the pdfmd to use (otherwise $PDFMD, else pdfmd on the PATH)
--sort time|name order of new files (modification time by default)
--title STEM=TITLE set a caption from the command line (repeatable)
--md-name NAME base name of the .md/.tex/.pdf
--formats LIST, --dpi N also write PNG/TIFF/SVG, see Pictures

The three kinds of input

Source Goes in What happens
The lab's own instrument file (.SPA, .SPG) SPA/ decoded directly (checked against a Spectragryph export to 0.0000 %T); peaks picked
NIST WebBook .jdx jdx/ Transmittance is rescaled 0–1 → 0–100; Absorbance is converted to %T; QUANT-IR gas-phase files (absorptivity, not an absorbance) need peak_a or cl in the manifest, see below
A spectrum published only as a picture (SDBS) img/ the trace is read off the image and drawn back over it as a check; a failed calibration or redraw check stops the run and names the log in check/

.jdx files that use JCAMP's compressed number formats (SQZ/DIF/DUP) are refused with a message rather than decoded wrongly.

QUANT-IR gas-phase files

These carry an absorptivity (absorbance per µmol/mol per metre), not an absorbance, and the file does not say what concentration or path length it was measured at. irspec will not invent one. Say what to scale the strongest band to, and say so in your caption:

{"file": "jdx/1634-04-4-IR.jdx", "name": "MTBE_GAS", "peak_a": 1}

"cl": <c×L in µmol/mol·m> is the alternative when you know the product.

Names

An instrument spectrum keeps its file's stem: SPA/RUN3.SPA gives csv/RUN3.csv and plain/RUN3_plain.csv. A reference spectrum (jdx/, img/) gets a name in capitals with underscores and no spaces, csv/NAME.csv and plain/NAME.csv (no _plain; the difference is deliberate). The name comes from, in this order:

  1. "name" in its manifest entry;
  2. cas_names.json in the session folder, looked up by the CAS number the file name starts with: {"67-56-1": "METHANOL_GAS", "103-90-2": "ACETAMINOPHEN"};
  3. the file's own stem (a WebBook file is called after its CAS number, so this is rarely what you want).

CSV files

Two files per spectrum, because the two jobs want different things:

archival csv/ plain plain/
separator semicolon comma
decimal mark comma point
order descending wavenumber ascending
range everything the file has 650 cm⁻¹ and up
y as decoded, never converted %T, the figures' axis
use keeping the data as it came plotting, peak picking, correlation, other programs

The plain CSV has a header line and two columns, wavenumber and intensity; a spreadsheet, R, matplotlib or pgfplots reads it as it is. The archival form is the shape Spectragryph exports.

The manifest

manifest.json is the one place a session is configured. It is a list with one entry per spectrum, in figure order, written by the first run and kept on every re-run: your edits stay, new files are appended, vanished files are dropped.

[
  {
    "file": "SPA/PHENACETIN_CRUDE_LR1-2.SPA",
    "title": "Phenacetin (crude)",
    "peaks": [[740.5, 52.3], [1241.9, 47.3], [3253.3, 75.3]],
    "ymin": -5
  }
]
Key Meaning
file path of the spectrum, relative to the folder (SPA/…, jdx/…, img/…)
title the caption under the figure, free text ("Alcohol (stock)" is fine). A new entry starts with the file's stem (an instrument spectrum), or with its compound name ("METHYL_CINNAMATE_GAS" gives "Methyl cinnamate gas"), or with the title inside the .jdx; change it here
peaks [wavenumber, label_y] pairs, as picked; edit, add or delete here (or with irspec edit … peak). A bare number is snapped to the nearest local band. [] means no peaks. Delete the key to pick again
axis y-axis label (default Transmittance %)
ymin bottom of the y axis; the tool's suggestion is kept as it is, and may be negative for a strong band
threshold, prominence, distance peak-picking sensitivity overrides, for a band at the plot edge or noise taken for bands
name the reference name above (written back by the first run)
peak_a, cl QUANT-IR files only, see above
check pictures only: the peak table printed under the SDBS plot, typed as "3490:84,2977:6,…", for the digitiser's own cross-check
args pictures only: extra flags for irspec digitize convert, as a list. For a book figure: ["--palleros", "--x-ticks", "3500,3000,2500,2000,1500,1000", "--y-ends", "72.7:24.5"], with check set to the band positions printed under the troughs ("3027.1,2948.8,..."); those printed positions then become the peak labels, not the peak finder's

Editing the manifest: irspec edit

irspec edit changes manifest.json from the command line, so a script (or an AI agent) can do what you do in a text editor. It touches the manifest only; run irspec session on the folder afterwards to redraw (a re-run keeps what you set).

irspec edit my_lab_folder/ PHENACETIN_CRUDE_LR1-2 set title="Phenacetin (crude)" ymin=-5
irspec edit my_lab_folder/ SPA/PHENACETIN_CRUDE_LR1-2.SPA unset ymin
irspec edit my_lab_folder/ PHENACETIN_CRUDE_LR1-2 peak add 1720 3000    # snapped to the nearest band
irspec edit my_lab_folder/ PHENACETIN_CRUDE_LR1-2 peak remove 3253
irspec edit my_lab_folder/ PHENACETIN_CRUDE_LR1-2 peak clear            # no peaks at all
irspec edit my_lab_folder/ PHENACETIN_CRUDE_LR1-2 peak auto             # pick again, drop hand edits
irspec edit my_lab_folder/ PHENACETIN_CRUDE_LR1-2 move --to 1           # first figure in the report
irspec edit my_lab_folder/ PHENACETIN_CRUDE_LR1-2 move --to last
irspec edit my_lab_folder/ 62-44-2-IR remove                            # out of the manifest, file to removed/
irspec edit my_lab_folder/ show                 # every entry, as text
irspec edit my_lab_folder/ show PHENACETIN_CRUDE_LR1-2 --json
  • Which entry. FILE is the entry's file value (SPA/X.SPA), or just its file name or stem when only one entry has it; an ambiguous or unknown name is an error that lists the candidates.
  • Which keys. set and unset take title, axis, name (text) and ymin, ymax, xmin, xmax, threshold, prominence, distance (numbers, stored as JSON numbers; a value that is not a number is refused). Any other key is an error naming the allowed ones. xmin, xmax and ymax are stored but the drawing code does not read them yet.
  • Peaks. peak add X ... snaps each wavenumber to the nearest local band of that spectrum (within 15 cm⁻¹) and adds it with the label height session gives a band named by its wavenumber alone. peak remove X ... takes out the stored peak nearest X, if one is within 15 cm⁻¹. peak clear leaves the figure with no peaks ("peaks": []). peak auto picks again with the entry's threshold, prominence and distance, replaces everything stored, hand edits included, and lists what it dropped. Each writes an explicit peaks list, which irspec session keeps, so the redraw shows exactly these peaks. They start from what the figure shows now (an entry with no peaks yet starts from the automatic list), a peak already there or none near an X to remove changes nothing (a note goes to stderr, exit 0), and the changes print as peak: old -> new (--json: {"key": "peak", "old": null, "new": [1732.7, 93.6]} for an added peak, the other way round for a removed one). The spectrum is read as session reads it (an own .SPA from the file, a reference spectrum from plain/NAME.csv); with no spectrum to read, exit 2.
  • Order and removal. move --to POSITION puts the entry at that place in manifest.json (1 is first, last is allowed, a number past the end means last, below 1 is exit 2) and keeps the order of all the others; the figures in the report follow the manifest order. An entry already there is left alone (--json: {"file": ..., "changes": [{"key": "position", "old": 3, "new": 1}]}, with changes empty when nothing moved). remove takes the entry out of manifest.json and moves its spectrum file to removed/ (same name; the folder is created). Nothing is deleted: put the file back to restore it. The move is needed because a file left in SPA/, jdx/ or img/ is listed again by the next irspec session, while removed/ is not looked at, nor filed into other/. The csv/ and plain/ files made for it stay where they are. If removed/NAME already exists, nothing is changed and the exit status is 2 (a file is never overwritten). The entry's own peaks, title and other keys are kept nowhere else; --json prints the whole entry ({"file", "removed": {...}, "moved_to": "removed/NAME"}) so you can paste it back. Run irspec session afterwards to redraw.
  • What it writes. The file is replaced atomically (a temporary file in the same folder, then a rename). Entries and keys keep their order, a new key goes last, and the file keeps its layout (the one session writes, else its own indentation, line endings and final newline). A command that changes nothing does not touch the file, so running it twice leaves identical bytes; unset of a key that is not there is not an error.
  • What it prints. Each change as key: old -> new. With --json, exactly one object on stdout, {"file": "SPA/X.SPA", "changes": [{"key": "ymin", "old": -5, "new": -7}]} (old is null for a key that was not there, new is null after unset; unchanged keys are not listed). show --json prints the entry as an object, or all entries as a list.
  • Exit status. 0 done; 2 the command cannot be carried out (unknown key, not a number, no such entry); 1 manifest.json is missing or unreadable. Errors go to stderr. It never asks anything.

Looking at a spectrum in the terminal: irspec preview

irspec preview FILE prints one spectrum as plain text, so you can see what the pipeline is going to do with it before anything is filed or built. FILE is a .SPA, a .jdx or a CSV. It reads the file and writes nothing.

irspec preview own-29.09/SPA/PHENACETIN_CRUDE_LR1-2.SPA
irspec preview X.jdx --width 100 --height 24
irspec preview X.jdx --ascii            # . : * | instead of Braille dots
irspec preview X.jdx --json             # the numbers only, one object
PHENACETIN_CRUDE_LR1-2.SPA  --  Transmittance % against wavenumber (cm-1)

    |*********:........:***::**:::::::**|:   .:  . ..
 90 |          ** *****            Q    ||..|||:||||||.*
    |           V  UTSR                 |N||**||*|**|||
 50 |                                   P *KIG*BA*75||1
    |                                   O MJHFD 986 43
 20 |                                     L  EC      2
    |
-10 |
    ++------+-------+------+-------+------+-------+-----
     4,000        3,000  2,500   2,000  1,500        650

axis:    Transmittance %
points:  3734 (3474 between 650 and 4000 cm-1)
peaks:   31 (picked automatically); wavenumber/label height
  1 740.5/52.3   2 784.9/47.3   3 825.4/39.4
  ...
  • The plot. The wavenumber axis runs 4000 to 650 from left to right, as in the figures, with intensity up; the trace is the part of the spectrum the figure shows, and the vertical extent is the figure's (the same ymin, so the room the peak labels need is there too). Each picked peak has a symbol (1-9, then A-Z, then a-z) under its band, or above it for an absorbance spectrum, and the list under the plot gives its wavenumber and label height. A peak with no free place near the curve is listed with - and is not marked.
  • Characters. Braille dots (2 x 4 per character) when stdout's encoding is UTF-8, else, or with --ascii, . (low in its cell), : (middle), * (high) and | (a steep stretch). Everything around the trace is ASCII either way.
  • Width and height. The terminal's width when stdout is a terminal, 80 columns when it is a pipe or a log (the text is otherwise the same); --width N and --height N (rows of plot, default 20) override both.
  • What is shown is what a session would do. The peaks, the label heights and the ymin come from the code irspec session runs, and a .jdx is converted as the session converts it (fractional %T times 100, an absorbance to %T, which the text says). If the file is listed in a manifest.json in its folder or the folder above, that entry is used (its peaks, threshold, prominence, distance, axis, ymin, and peak_a or cl for a QUANT-IR file), so irspec edit followed by irspec preview shows the edit. Otherwise the peaks are the automatic ones.
  • --json prints exactly one object and no plot: file, kind (spa, jdx, csv), axis, converted_from ("Absorbance" or null), points, range (x and y, of the figure's window for y), ymin, peaks ([wavenumber, label_y] pairs) and peaks_from ("automatic" or "manifest.json").
  • Exit status. 0 done; 2 when the file cannot be read or drawn (a missing file, a file that is not what its name says, a QUANT-IR file with no peak_a or cl to scale it, a width below 30 or a height below 3); the reason is on stderr. It never asks anything.
  • The limit. The preview shows the shape, the ranges, the peak positions and which peaks are marked. It does not show fonts, the exact place of the labels in the figure, or its tick spacing. Several spectra at once (--stack) is not there yet.

Preview a whole folder before filing

irspec session DIR --preview [--width N] [--height N] [--ascii] [--json] [--title STEM=TITLE]

A dry run that shows more than --dry-run: it prints the planned file moves exactly as --dry-run does, then, for every spectrum in DIR (loose, or already in SPA/ and jdx/), the same text irspec preview prints, headed by the place and the names the file will get (title, csv/..., plain/..., as irspec session would give them). It writes nothing: no folder, no manifest.json, figures.json, .md, .tex or .pdf, and no file is moved. A loose file is read from where it lies.

  • An existing manifest.json is respected as irspec preview does (its peaks and titles win), and --title STEM=TITLE is honoured in the header. --dry-run together with --preview means the same as --preview; --build, --formats and --dpi are ignored.
  • Pictures (img/) are listed with the names they will get and a note that irspec session digitizes them; they are not drawn here.
  • --json prints exactly one object and nothing else on stdout: {"moves": [{"from", "to"}, ...], "spectra": [...], "images": [...]}. Each spectrum is what irspec preview --json prints for it (with file relative to DIR, where the file is now) plus will_be_filed_as (e.g. "SPA/RUN3.SPA"), title, csv and plain; a spectrum that could not be read has file, those four keys and error.
  • Exit status. 0 done; 1 when one or more spectra could not be read (each is listed with its error and the others are still shown); 2 when DIR is not a folder, has no spectra, its manifest.json cannot be used, or --width/--height are too small. The reason is on stderr.

Pictures: PNG, TIFF, SVG

--formats takes a comma-separated list. The pictures are made from the figure PDF, one file per spectrum into figures/, so they all look like the PDF.

Format File Made by Notes
png NAME.png poppler --dpi (default 300)
tiff NAME.tiff poppler LZW-compressed, the same pixels as the PNG
svg NAME.svg poppler (pdftocairo) looks exactly like the PDF anywhere; text is drawn as outlines, so it cannot be edited as text
svg-text NAME_text.svg MuPDF (mutool) labels stay real <text> for Inkscape or Illustrator; the fonts' names are kept, so STIX Two must be installed where it is opened; TeX's word gaps are not space characters, so a label reads "Transmittance%" in an editor while it draws correctly

A PDF you already have can be converted without a session: irspec export fig.pdf --formats png,tiff --dpi 600 --out pics/ --names A,B,C (--names gives the file stems in page order; without it the pages are called page-1, page-2, …; the default folder is figures/ beside the PDF).

Drawing without TeX: matplotlib and R

irspec session also writes figures.json: the figures (the plain CSV, title, y-axis label, peaks, ymin) in a form that knows nothing about OMNIC, JCAMP or TeX. Two more drawing programs read it, and neither needs TeX:

irspec plot my_lab_folder/                                  # matplotlib: PNG
irspec plot my_lab_folder/ --formats png,svg-text,pdf       # more formats
irspec plot my_lab_folder/ --backend r --formats png,svg    # R and ggplot2
Rscript irspec.R my_lab_folder/ --formats png               # R, without Python

(irspec plot --print-r-script prints where irspec.R is; the script stands alone, so a class that works in R needs only that file and a figures.json.) Pictures go to figures/matplotlib/ or figures/r/, so they never overwrite the ones made from the TeX PDF. Options: --formats, --out, --size WIDTHxHEIGHT in inches (default 10x6.6), --dpi (300), --no-caption.

matplotlib R
needs matplotlib R with ggplot2, jsonlite (ragg for PNG/TIFF and svglite for SVG are used when installed)
formats png, tiff, pdf, svg (text as outlines), svg-text (editable text) the same five; pdf and svg need an R with working cairo (on macOS, CRAN's R has it only with XQuartz: brew install --cask xquartz), the others do not
font STIX Two Text if installed, else the bundled copy; the one used is printed the same (the R script looks for the bundled copy in the fonts/ folder beside it, so keep that folder with it)

All three drawings (TeX, matplotlib, R) share one set of numbers (src/irspec/layout.py), measured on the TeX figure: the axis box, the x axis from 4000 to 650 cm⁻¹, the trace (0.5 pt, #B03A2E), the peak labels (8 pt, rotated, hanging below their label_y on a short grey leader tick) and where the y axis starts and stops. They differ in the y tick spacing (pgfplots' rule is its own), in antialiasing, and in where the caption sits, so the same figure, not the same pixels. The TeX version remains the reference.

How the PDF is made

--build auto (the default) uses:

  • pdfmd + nulabreport, if both are installed: the .md is built by the author's own report pipeline (pdfmd is public, pip install pdfmd-cli; nulabreport is the author's LaTeX style for Nazarbayev University lab reports and is not public), so the figures come out in the house style of those reports. Nobody else needs this;
  • otherwise plain lualatex on the generated .tex. That document loads the real nulabreport.sty when TeX can find it and else the bundled excerpt irspec-ir.sty, which irspec copies beside the .tex.

The excerpt is cut mechanically out of nulabreport.sty (its header names the version) and draws the same page: on the author's phenacetin spectrum the two differ in 0 pixels, and nulabreport's smoke test checks that the committed excerpt is what its markers generate. It is a fallback, not a second implementation, with two limits: it understands only the captions setting of \labsetup (furniture is accepted and ignored), and it can lag behind the full package. Where they disagree, the full package is right.

The other commands

session is what most people want; the others are its steps, for when one step is all you need. Each has its own --help.

Command Does Example
irspec session DIR the whole thing above irspec session own-29.09/ --formats png
irspec export PDF PNG/TIFF/SVG from a PDF irspec export fig.pdf --formats svg
irspec edit DIR set or unset a manifest key, edit peaks, reorder or remove an entry, show the entries (above) irspec edit own-29.09/ X set ymin=-5
irspec preview FILE one spectrum as text in the terminal, with its peaks (above) irspec preview own-29.09/SPA/X.SPA --ascii
irspec plot DIR the figures drawn by matplotlib or R, no TeX irspec plot own-29.09/ --backend r --formats png
irspec spa .SPA → CSVs and peaks; batch for several, plot for a legacy matplotlib PNG/SVG irspec spa convert run.SPA
irspec jdx .jdx → CSVs, one file or a folder irspec jdx convert X.jdx --name X --out csv/X.csv --plain-out plain/X.csv
irspec peaks peak-pick a .SPA, .jdx or CSV irspec peaks plain/X.csv
irspec digitize a picture → CSVs, with a redraw check irspec digitize convert img/X.gif --name X --out csv/X.csv --plain-out plain/X.csv
irspec a2t Absorbance → %T (--to absorbance for the other way) irspec a2t plain/X.csv --out plain/X_T.csv
irspec absorptivity QUANT-IR absorptivity → Absorbance irspec absorptivity X.jdx --name X --out csv/X.csv --plain-out plain/X.csv --peak-a 1
irspec corr Pearson r between spectra (the first is the reference) irspec corr plain/REF.csv plain/UNKNOWN.csv

digitize has two more layouts than SDBS: --palleros for book figures and --omnic for OMNIC screenshots (this one reads the tick labels with tesseract). The module docstrings are the long documentation of each tool: what was checked, and how.

Can I trust the output?

The tools began as the IR scripts of the author's lab-report package and were moved here without changing what they compute. The tests (PYTHONPATH=src python3 -m unittest discover -s tests) hold that in place:

  • the CSV pair for the author's own phenacetin spectrum is compared byte for byte with the ones the original scripts made;
  • sixteen commands are run by the original scripts and by the package on the same inputs, and the exit code, the output and every written file are compared;
  • the Markdown a session writes is compared with what the original organizer wrote, and the plain-lualatex page with the full-package page, pixel for pixel;
  • every exported file is compared with pdftoppm of its own PDF page;
  • the y limits of the drawing code are held to what the TeX figure really draws (frame and tick positions read from the PDF on six y ranges); the matplotlib figure's line data, limits, label texts and positions are compared with the CSV and the peak list; the R script's limits and ticks are compared with the Python ones.

Three more checks run on the author's own coursework folders (the environment variable IRSPEC_LR6_IR points at them) and are skipped everywhere else; they are how the WebBook and SDBS paths were shown to give byte-identical CSVs.

The one sample spectrum in tests/data/ is the author's own. No other student's data and no instructor's material is in this repository.

Versioning

0.MINOR.PATCH until 1.0, then Semantic Versioning.

  • Minor (0.1.0 → 0.2.0): a new feature, or anything that could break a folder or a script someone already has: a command-line flag renamed or removed, a manifest key or the folder layout changed, the CSV or figure format changed.
  • Patch (0.1.0 → 0.1.1): a fix, documentation, tests, a regenerated excerpt that draws the same page.
  • SPEC_VERSION (in irspec/figure.py, now 1) is a separate integer, written into figure descriptions. It changes only when the manifest/figure format breaks, and it is frozen at 1.0.
  • 0.1.0 is the first release: the TeX pipeline, the exports and the matplotlib and R backends; 1.0.0 is planned for when the formats have settled.
  • The version lives in one place, irspec.__version__. A release is a git tag vX.Y.Z plus an entry in CHANGELOG.md. The bundled excerpt records which nulabreport version it was cut from.

Anything that depends on irspec before 1.0 should pin the minor version (irspec>=0.1,<0.2).

Roadmap

The first release is out (PyPI, public repository). Planned, in this order; of this, only irspec edit set, unset, show, peak, move and remove, irspec preview FILE and irspec session --preview, exist so far:

  • Editing from the command line (irspec edit set/unset/show/peak/move/remove are in; rename is set name=; still to come: re-running the session for the figure), --json output and a written description of the manifest, so scripts and AI agents can drive everything a person can;
  • A preview in the terminal (irspec preview FILE and irspec session --preview are in; still to come: --stack): the spectrum drawn in text to the width of the terminal, with what the run is going to do;
  • \irspectrum reading .SPA and .jdx files directly in LuaTeX, with automatic peaks and the x and y ranges chosen in the call, so a report needs no CSV;
  • optional smoothing, baseline correction and band areas, off by default, the raw data never changed;
  • 1.0 when the command line, the manifest and the figure format have settled;
  • perhaps a small Mac app over the same commands, built from this repository (app/).

The reasons and the design are in docs/roadmap.md.

Out of scope: MS, NMR, GC and TLC tools, identifying compounds, and anything that extracts figures from copyrighted books.

Development

PYTHONPATH=src python3 -m unittest discover -s tests

The original scripts the code came from are in the git history (tag legacy-oracle, folder legacy/; see PROVENANCE.md); the tests compare against what they produced, recorded in tests/data/. irspec-ir.sty is generated and never edited: change nulabreport.sty and regenerate it.

Releasing

The version bump and the CHANGELOG.md entry go in one commit on main; then the tag: git tag -a vX.Y.Z -m "irspec X.Y.Z" && git push origin vX.Y.Z. The tag starts .github/workflows/publish.yml, which refuses a tag that is not irspec.__version__, runs the tests, builds, and uploads to PyPI by trusted publishing (no token is stored; the PyPI project lists this repository, publish.yml and the environment pypi).

Licence

irspec is MIT, see LICENSE. It bundles three unmodified fonts from the STIX Fonts Project (STIX Two Text Regular and Italic, STIX Two Math; about 1.9 MB), which are under the SIL Open Font License 1.1, not MIT: the notice and the license text are in src/irspec/data/fonts/ (OFL.txt, README.txt) and travel with every copy, as the license asks. They are used only when STIX Two is not installed.

About

Infrared spectra for chemistry students: OMNIC .SPA and JCAMP-DX in, tidy CSVs, peak lists and report-ready figures out.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages