Echelle Project: Overview & PlaybookΒΆ
1. The problem β why your current workflow hurtsΒΆ
Typical anti-patterns (βBSβ) we keep seeingΒΆ
# β BS
sen10 = S(cb_ylist[0]); sen11 = S(cb_ylist[1]); ... sen23 = S(cb_ylist[13])
# β
Do
sen_by_order = [S(wl) for wl in cb_ylist]
# β BS
coef_10 = sen10/(sphere[10]-back[10]); coef_11 = ...
# β
Do
coef_by_order = [sen/(s-b) for sen, s, b in zip(sen_by_order, sphere_list, back_list)]
- Calibration logic shoved into a notebook (polyfit, borders, stitching) β impossible to reuse/test.
- Calibration data hardcoded in a notebook (e.g., wave/sen arrays) instead of a file with metadata:
# data/calibration/sphere_2025-01-23.csv
# instrument: Fujii Echelle A
# sphere: Labsphere XYZ, SN12345
# date: 2025-01-23
# location: Room 301 dark lab
# author: K. Student
wavelength_nm,sensitivity_W_m2_sr_nm_per_count
300,1.85e4
310,2.8e4
...
Then read with:
arr = np.loadtxt(path, delimiter=",", comments="#", skiprows=0)
2. The sane project shape β mix notebooks + packageΒΆ
Use notebooks for exploration, package for logic.
echelle-project/
pyproject.toml
src/echelle_pkg/ # β all reusable code lives here
__init__.py
calibration.py # sensitivity, borders, stitching
io.py # loaders
profiles.py # per-instrument settings
data/
calibration/ # CSVs with comment headers
examples/ # shareable demo notebooks
01_quickstart.ipynb
02_absolute_calibration.ipynb
local/ # ignored scratch area (in .gitignore)
2025-09-poster/ # your dayβs work, ad hoc plots
docs/ # add after code steadies
.gitignore # includes /local/
Install the package in editable modeΒΆ
Choose venv or poetry; venv is fine.
python -m venv ~/.venvs/echelle
source ~/.venvs/echelle/bin/activate # (Windows: Scripts/Activate.ps1)
python -m pip install -U pip wheel
pip install -e ".[dev]" # uses pyproject.toml
Poetry (if a student prefers):
poetry install
poetry shell
Because itβs editable, you can work anywhere (e.g., ~/workbench/2025-09-poster/) and still:
from echelle_pkg.calibration import fit_sensitivity_poly, stitch_orders
3. Daily usage pattern β what students should doΒΆ
- Prototype in
local/or any external folder β import the installed package. - Keep notebooks thin: plotting, quick checks, and calls like:
from echelle_pkg.profiles import load_profile
from echelle_pkg.calibration import run_absolute_calibration
prof = load_profile("data/calibration/profiles/fujii-2025.json")
factors = run_absolute_calibration(images, profile=prof)
- New spectrometer / camera / processing?
- Prototype in
local/ - Wrap into a function/class in
src/echelle_pkg/ - Add/adjust a profile JSON (orders, pixel geometry, poly degree, file paths)
- Add a minimal test in
tests/ - Add a usage notebook in
examples/
4. Git hygiene β small, readable historyΒΆ
- Branches per task:
git switch -c feat/abs-calibration-profiles - Commits are small and named for the change:
feat(calib): add profile loaderfix(stitch): skip orders without Ξ»-calibrationdocs(calib): note sphere CSV header fields - Tags for stable points:
git tag -a v0.3.0 -m "absolute calibration rework" - You donβt have to push every commit. Push at the end of the day or when a unit of work is done.
5. Docs when it stabilizes β mkdocs, minimalΒΆ
Add docs after the dust settles:
docs/with short pages:
- Quickstart (install, one example)
- Calibration data spec (CSV header, units, example)
- Profiles (how orders/geometry are encoded)
- API (one page, function signatures)
mkdocs.ymlnav that links examples notebooks (rendered or linked).
6. Side-by-side β BS vs possibleΒΆ
Manual notebook soup (BS)ΒΆ
# fit poly, hand-pick orders, repeat slices, concatenate, sort...
sen_10 = S(cb_ylist[0]); sen_11 = S(cb_ylist[1]); ...
coef_10 = sen_10/(sphere[10]-back[10]); ...
x = np.concatenate((raman[0][171:1749], raman[1][188:1431], ...))
y = np.concatenate((cb10[171:1749], cb11[188:1431], ...))
Package-first (possible)ΒΆ
# src/echelle_pkg/calibration.py
def sensitivity_from_csv(path, deg=6):
arr = np.loadtxt(path, delimiter=",", comments="#")
return np.poly1d(np.polyfit(arr[:,0], arr[:,1], deg))
def coefficients(image, background, S, orders=None):
# auto-borders + skip NaN-Ξ» orders inside
x, s = stitch_orders(image, orders=orders)
_, b = stitch_orders(background, orders=orders)
y = s - b
return x, S(x) / np.clip(y, 1e-12, None)
# examples/02_absolute_calibration.ipynb
S = sensitivity_from_csv("data/calibration/sphere_2025-01-23.csv", deg=6)
x_nm, coef = coefficients(sphere_img, back_img, S, orders=[10,11,12,13,14,15,16,17,18,19,20,21,22])
7. MessageΒΆ
- Notebooks are for exploration and visualization.
All reusable logic must live in
src/echelle_pkg/. - Calibration data is a file with a header, not literals in a cell.
If it isnβt in
data/calibration/β¦csvwith a comment header, it doesnβt exist. - Instrument differences go into a profile JSON, not into copied code.
- Commit small, often; tag stable points; push when a unit of work is complete.
- If you wrote the same line twice, you need a loop or a function.
8. Minimal pyproject.toml you can reuseΒΆ
[build-system]
requires = ["setuptools>=69", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "echelle-pkg"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["numpy", "scipy", "matplotlib", "tifffile", "pillow"]
[project.optional-dependencies]
dev = ["pytest", "ruff", "black"]
[project.scripts]
echelle-cli = "echelle_pkg.cli:main"
[tool.setuptools.packages.find]
where = ["src"]
include = ["echelle_pkg*"]
EncouragementΒΆ
Ψ΄ΩΩΩΩΩΩΨ© Ψ΄ΩΩΩΩΩΩΨ© ΨͺΨ¨ΩΩ Ψ¬Ψ¨Ω
Shuyyat shuyyat tabni jabal β Little by little, you build a mountain.