📦 Latest build: v1.0.0 on the Releases page — download
MidnightSpectRb.exeand run it. No Python, no cloning this repo.
A desktop app for fluorescence-spectroscopy of a rubidium-87 cloud. Drop a set of camera frames onto the window, draw the crop region once, set the frequency axis, and it builds the intensity-vs-frequency spectrum and fits the line (Gaussian / Lorentzian) with FWHM, uncertainties and goodness of fit.
It is the original Rubidium87_Spectroscopy_Plotter notebook turned into a
proper application: instead of editing a crop box and a frequency list in
code, you drag the images in and select the region with the mouse.
You put a camera on the cell and step the laser frequency, saving one frame per step. Every frame has the same framing and composition — only the brightness of the fluorescing cloud changes with frequency. This app:
- takes all those frames at once (drag-and-drop),
- orders them, opens the central one and lets you crop the cloud region,
- averages the brightness inside that crop for every frame → one intensity per frequency,
- subtracts the background and fits the resulting spectrum, reporting the peak position and linewidth (FWHM) with error bars and R²/RMSE.
Windows: download MidnightSpectRb.exe from the
latest release
— one self-contained file, no side folders or DLLs. Copy it anywhere and
double-click.
macOS / Linux (from source):
pip install -r requirements.txt
python main.py # or: python -m spectrbBuilding the exe yourself (only needed if you changed the code):
pip install pyinstaller
build_windows.batproduces dist\MidnightSpectRb.exe.
- Add frames. Drag image files (or a whole folder) onto the window, or use Добавить изображения…. They appear in a table in analysis order.
- Check the order. Frames are ordered by a natural sort of the file name (see below). Fix the order by selecting a row and using ↑ Выше / ↓ Ниже if needed.
- Set the frequency axis. Enter Старт and Шаг (frequency of the first frame and the step between frames) and, if your setup uses a double-pass AOM, leave ×2 ticked — then Пересчитать частоты. You can also edit any single frequency directly in the table cell (a mix of both is fine).
- Crop the cloud. On the central frame, drag a rectangle over the cloud. Drag inside it to move, drag an edge/corner to resize, or type the exact pixel box in Область кропа. The mouse wheel zooms. The same crop is used for every frame, which is why the frames must share composition.
- Analyse. Hit Анализировать. Three tabs appear — the raw spectrum, a Gauss-vs-Lorentz comparison, and the final fit — plus a text summary.
- Export. Экспорт результатов… writes the three figures (PNG), a
spectrum.csv(per-frame frequency / raw / corrected / normalised intensity) and aresults.txt.
Only the order of the frames matters, not the names. Names are sorted the way a person reads a numbered list — digit runs compare as numbers, text compares case-insensitively:
| Naming | Result |
|---|---|
1, 2, 10 |
1, 2, 10 ✔ |
rb1, rb2, rb10 |
rb1, rb2, rb10 ✔ |
a12412412, b124…, cudhf2 |
by leading letter a, b, c ✔ |
1g, 2q, c7 (mixed digit/letter lead) |
an order, but maybe not the acquisition one ✘ |
The last case is why the app always shows the resulting order and lets you fix it by hand.
- Per-frame signal. Each frame is converted to grayscale and the mean pixel brightness inside the crop box is taken as the fluorescence intensity at that frequency.
- Frequency axis.
f_i = start + i·step, doubled for a double-pass AOM (the beam passes the modulator twice, so the frequency shift on the light is twice the drive frequency). Any point can be overridden in the table. - Background. The baseline
Cis estimated three ways — from a Gaussian fit, from a Lorentzian fit, and from the average of the spectrum's outermost points — and their mean is subtracted, then the data are normalised to their peak. (This fixes a units inconsistency in the original notebook, which estimatedCin normalised units but subtracted it from raw intensities; here all background handling is in raw intensity units.) - Fits. A Gaussian
A·exp(−(f−μ)²/2σ²)+Cand a LorentzianA·γ²/((f−μ)²+γ²)+Care fit by least squares. The peak positionμand its error come from the covariance matrix; the linewidth isFWHM = 2.355·σ(Gauss) or2·γ(Lorentz). Both fits are compared by R² and RMSE — for a homogeneously broadened line the Lorentzian is usually the better description.
spectrb/
sorting.py natural (alphanumeric) file ordering
analysis.py crop → intensity, line-shape fits, background, metrics (no GUI)
plots.py the three matplotlib figures (headless-usable)
cropview.py interactive image view with a resizable crop rectangle
app.py the PySide6 application
main.py entry point
MidnightSpectRb.spec PyInstaller one-file build
build_windows.bat one-click Windows build
Requires PySide6, numpy, scipy, matplotlib, Pillow.