Skip to content

Migrate codebase to Python 3 and fix related issues - #158

Open
CuriousAvenger wants to merge 56 commits into
LCOGT:masterfrom
CuriousAvenger:python3
Open

Migrate codebase to Python 3 and fix related issues#158
CuriousAvenger wants to merge 56 commits into
LCOGT:masterfrom
CuriousAvenger:python3

Conversation

@CuriousAvenger

Copy link
Copy Markdown

No description provided.

svalenti and others added 30 commits September 6, 2025 19:32
- Replace Python 2 print statements with print() calls
- Replace except (ExcType, e): with except ExcType as e: syntax (mysqldef.py, sqlcl.py)
- Replace map(None, X, Y) with zip(X, Y) in lscastrodef.py linreg()
- Replace urllib/urllib2 with urllib.request/urllib.parse (externaldata.py, sqlcl.py)
- Replace file() built-in with open() (util.py, externaldata.py)
- Replace xrange() with range() and remove string module usage (cosmics.py, sqlcl.py)
- Replace np.cast[] with np.asarray(..., dtype=) (cosmics.py)
- Replace np.Inf with np.inf (cosmics.py)
- Replace np.alen() with len() (cosmics.py)
- Replace ndimage.morphology.*/ndimage.measurements.*/ndimage.filters.* with top-level ndimage.*
- Replace pkg_resources with importlib.resources (util.py)
- Remove Python 2/3 conditional in userinput() (util.py)
- Replace raw string in re.sub() call (lscpsfdef.py, myloopdef.py)
- Use integer floor division in rebin() (cosmics.py)
- Add odrpack as fallback import for scipy.odr in lscabsphotdef.py
- Update setup.py: distutils -> setuptools, add odrpack to requires
- Update .gitignore: add build artifacts, __pycache__, .pytest_cache,
  editor/OS files, pyraf cache, and data file patterns
- cosmics.py (growmask): fix variable name typo 'dismask' -> 'dilmask' in
  the else-branch of growmask(); the function returned the correct 'dilmask'
  name but the assignment used the wrong name, causing NameError at runtime
  when size is neither 3 nor 5.

- util.py (readkey3): fix incorrect string escape sequence '\#' -> '\#';
  the single-backslash form is not a recognised Python escape sequence and
  was silently treating '#' without stripping the backslash, producing
  wrong header keyword values.

- myloopdef.py (checkfilevsdatabase): fix missing index on
  lista['filename'] inside a for-loop; 'lista["filename"].replace(...)'
  operated on the whole column Series instead of the i-th element,
  causing incorrect .sn2.fits path construction and potential AttributeError.
Python 2 runs often avoided this failure because this fallback path was less frequently triggered and numeric values more often matched built-in float identity.\n\nIn Python 3 migration runs, FITS header values can be valid numeric scalar types that are not identical to float under exact type checks.\n\nThe previous logic used strict type identity, which is not robust across numeric representations.\n\nThis patch uses explicit float coercion with the same exception path for truly invalid values.
In Python 2-era environments, mixed string/numeric arrays passed through this code path with more permissive coercion behavior in the surrounding scientific stack.\n\nIn Python 3 migration runs with modern NumPy, np.where over string values (for example 'INDEF') and numeric fallback values (for example 9999) now triggers explicit dtype promotion checks. Under NumPy 2 this yields DTypePromotionError when no common non-object dtype exists.\n\nThis patch removes mixed-type np.where array construction and normalizes each photometry vector through explicit float coercion with a controlled sentinel fallback.\n\nResult: the SN2 FITS table columns are consistently float-typed, preserving prior semantics while remaining compatible with current Python 3/NumPy behavior.
MySQL/MariaDB clients on ARM Linux default to requiring TLS (--ssl-mode=REQUIRED),
causing 'ERROR 2026: SSL is required' when connecting to a local MariaDB container
that has no TLS certificates configured. This regression does not occur on x86 because
the default client SSL policy differs between architectures and client versions.

Fix: detect whether the installed mysql client supports --ssl-mode (MySQL 5.7.11+) or
only --ssl=0 (older MariaDB clients), then pass the appropriate flag to suppress TLS
for loopback container-to-container connections where encryption is unnecessary.
…backend

In Python 2, raw_input() was implemented in C and cooperated with the OS scheduler
in a way that allowed Tk's event loop to continue servicing Expose/paint events on
the main thread between keystrokes. In Python 3, input() delegates to
sys.stdin.readline(), which is a hard blocking call on the main thread. Because
Matplotlib's TkAgg backend drives its canvas entirely on that same thread, the call
to input() starves the Tk event queue: the window opens, receives one paint from the
initial plt.draw(), then immediately blocks. On ARM Docker + XQuartz this race is
lost and the window appears black.

Fix (myloopdef.py): run userinput() in a daemon thread and spin plt.pause(0.1) on
the main thread while waiting, keeping the Qt/Tk event loop alive throughout.

Fix (util.py): guard userinput() against non-TTY stdin (docker exec -i) and
EOFError, returning '' so the loop exits cleanly rather than crashing.

Fix (Dockerfile / docker-compose.yml / bashrc): install pyqt via conda and set
MPLBACKEND=QtAgg. TkAgg is structurally fragile under Python 3 + XQuartz + ARM
because any blocking call on the main thread stalls rendering; QtAgg uses its own
native event loop and is robust to the threading model used here.
The previous image hard-coded an x86 DS9 package, which fails on ARM64 at runtime with a missing dynamic loader (/lib64/ld-linux-x86-64.so.2). The build now resolves the DS9 package from uname -m and installs an architecture-matched binary (x86_64 or aarch64). This removes host-dependent runtime breakage and makes the container reproducible across heterogeneous Linux platforms.
iraf.display() requires a running XImtool/DS9 image display server bound to
the IRAF image device (imtool). When no server is present the IRAF task
terminates with an IrafError, aborting the PSF stage entirely. This is a
runtime environment issue, not a code defect, and is equally possible under
Python 2 and 3.

Fix: wrap the display/tvmark block in a broad Exception catch so the PSF
fitting pipeline continues uninterrupted and the user receives an informative
warning rather than an unhandled traceback.
CuriousAvenger and others added 24 commits May 17, 2026 11:57
In Python 2, Docosmic used the bundled lsc.cosmics (L.A.Cosmic) implementation
unconditionally. The Python 3 migration replaced this with astroscrappy, a
compiled C extension offering the same algorithm with better performance, but
made the dependency mandatory with an unconditional import. On environments
where astroscrappy is not installed the import raises ModuleNotFoundError,
which propagates through multiprocessing.Pool and aborts the cosmics stage
entirely — a regression relative to Python 2 behaviour.

Fix: wrap the astroscrappy import in a try/except ImportError inside the
existing Python 3 branch. When astroscrappy is absent the code falls back to
lsc.cosmics, preserving Python 2 parity. When astroscrappy is installed the
fast path is taken unchanged.
In Python 2, zip() returned a materialised list of tuples with a known length
and shape, allowing NumPy and astropy.wcs to infer dtype('float64') directly.
In Python 3, zip() returns a lazy iterator with no __len__ or element-type
information; NumPy therefore allocates a 0-d object array (dtype('O')), which
astropy's wcs.p2s refuses to cast to float64, raising TypeError at the
wcs_pix2world call site.

Fix: wrap the three affected zip() calls in list() to restore the eager
evaluation semantics that Python 2 provided implicitly.
Python 2 allowed float result from / 2 in array shape division, but Python 3 returns float and slice indices must be integers. This patch uses integer division for yctr/xctr to fix difftype 1 in lscdiff.py.
In Python 3, numpy cannot compare None with str using '<', which causes
argsort inside astropy's group_by to fail with TypeError when multiple
standards in the same filter have different zcol1/zcol2 values (one NULL
from MySQL, one a string like 'UB').

The fix replaces None values in zcol1/zcol2 columns with empty strings
before calling group_by, allowing the sort to proceed without errors.

This is a Python 2→3 regression: Python 2 allowed cross-type comparisons
(None < 'str' returned True), but Python 3 raises TypeError.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
readnoi was set to gain and epadu to ron; corrected to readnoi=ron,
epadu=gain to match lscastrodef/lscpsfdef. Fixes DAOPHOT photometric
error terms.
Seed fsolve near the analytic SNR root (Nsigma^2/gain) instead of
np.median(data). On difference images the median is ~0, which sits left of
the SNR curve's minimum where the sqrt goes imaginary; fsolve stepped into
the nan wall, returned ~0, and limmag blew up to ~104 instead of ~21. The
old seed also carried cross-build float noise (~1e-13 median differences),
making the stall flip per-file between environments. The new seed is a
fixed constant, so limmag is now stable and correct on diff images.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants