Migration guide
LUCI was restructured so it can be installed, tested, and modified safely. This page maps every old name to its replacement.
Nothing in this guide is urgent. Every old spelling still works — the old
module names are re-export shims and the uppercase LUCI package name is
aliased onto the new lowercase luci. Existing notebooks and scripts run
unchanged. Migrate when convenient.
The one thing that did change is numerical output, and only where a bug was fixed. See What changed in the results — read that section even if you never touch the new API.
Installation
The conda environment is gone. LUCI is a normal Python package:
# users
pip install luci-sitelle
# contributors
git clone https://github.com/crhea93/LUCI && cd LUCI
uv sync
uv run pytest
Because the package installs properly now, the ``sys.path.insert`` line at the top of every example is no longer needed and should be deleted:
# before
import sys
sys.path.insert(0, '/home/carterrhea/Documents/LUCI/')
from LuciBase import Luci
# after
from luci import SitelleCube
Luci_path is now optional
Luci_path — the absolute path to the checkout, with its mandatory trailing
slash — is how LUCI found its ML/ and Data/ directories. It is why every
example began by hardcoding a path into somebody else’s home directory.
It is now resolved automatically: from $LUCI_DATA_DIR if set, otherwise from
the installed package’s own location. Pass one only to override.
# before
Luci_path = '/home/carterrhea/Documents/LUCI/'
cube = Luci(Luci_path, cube_path, output_dir, name, redshift, resolution)
# after
cube = SitelleCube(cube_path=cube_path, output_dir=output_dir,
object_name=name, redshift=redshift, resolution=resolution)
Passing it positionally still works exactly as before, so no existing call needs to change.
Package and class names
Old |
New |
|---|---|
|
|
|
|
|
|
|
|
the 22-key dict from |
|
Note
A lowercase luci package and an uppercase LUCI package cannot coexist
on macOS or Windows, whose filesystems are case-insensitive. So LUCI is
not a package but a module (LUCI.py) that registers the alias in
sys.modules. LUCI.cube and luci.cube are therefore the same
module object, not two copies — no duplicated state.
Module layout
The flat LUCI/Luci*.py files were split by responsibility. Each old module
still exists as a re-export shim, so old imports resolve.
Old module |
New home |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Two modules are new rather than moved:
luci.instrument.filtersOne
FilterSpecper filter, holding every band-dependent number that used to be copy-pasted across fiveif filter == 'SN3'chains. Adding a filter is now one dict entry — see Adding a New Filter.luci.engine.runnerandluci.engine.selectionA single fit orchestrator and a single pixel-selection resolver, replacing four near-identical blocks in the old
Luciclass.
Configuring a fit
SpectrumFitter still takes all of its original keyword arguments. They can
now also be grouped into a FitConfig, which validates them in one place:
from luci import FitConfig, SpectrumFitter
config = FitConfig(
lines=["Halpha", "NII6583"],
model="sincgauss",
vel_rel=[1, 1],
sigma_rel=[1, 1],
)
fitter = SpectrumFitter(spectrum, axis, wavenumbers_syn, config=config)
Both spellings produce bit-identical fits; a test asserts it.
Errors instead of exit()
Library code no longer calls quit()/exit() on bad input, which used to
kill the host interpreter — including a Jupyter kernel — with no traceback.
Situation |
Now raises |
|---|---|
Unrecognised filter |
|
PCA background on an unsupported filter |
|
Invalid fit configuration |
|
Unrecognised region/mask argument |
|
Output messages
Progress and diagnostic messages go through the luci logger instead of
print(), so they can be filtered or silenced:
import logging
logging.getLogger("luci").setLevel(logging.WARNING) # quieter
from luci.log import silence
silence() # off entirely
Output is on by default at INFO, so nothing disappears unless you ask.
What changed in the results
Bug fixes changed numerical output. The full register is in
REFACTOR_BUGS.md; these are the ones that move published numbers.
Line broadening was wrong for every line except the last (B21). The
per-line σ bounds were built in a loop of lambda closures over the loop
variable, so all of them read the final value — every line was constrained by
the last line’s bounds. On the test cube, NII6583 moved 164 → 101 km/s and
SII6731 167 → 101 km/s, and the spread across velocity-tied lines dropped
from 67.5 to 2.4 km/s.
Warning
If you published broadening or velocity maps from a multi-line fit, they were affected by B21. Re-running is the only way to know by how much.
``ML_bool=False`` fabricated zero kinematics (B1). The non-ML path started
the optimiser at σ = 0, a singular point of the sinc-Gauss, and returned
velocity = broadening = 0 rather than failing. The documentation actively
recommended ML_bool=False for unsupported filters. It now seeds from the
brightest peak with a default broadening and recovers the injected physics.
``fit_region`` ignored the background (B3). It passed bkg but not
bkgType, so subtraction was silently skipped — results were as if no
background had been given.
``pixel_list=True`` fitted the whole cube (B23). The mask started as
all-True, so the pixel list narrowed nothing.
Smaller fixes — a deep image dropping its trailing rows (B7), multi-component fits overwriting each other’s output maps (B16), MDN broadening priors read without their softplus transform (B17) — are described in the register with the test that pins each one.