This issue was written and opened by Claude Code at the direction of Matt Craig (@mwcraig).
Environment: ccdproc 2.4.3, astropy 7.1.0, numpy 2.3.5.
With a float32 image and a float32 master dark, subtract_dark(ccd, master, scale=True)
returns float64 data; scale=False returns float32. Cause (ccdproc/core.py): the
scale=True branch does
master_scaled = master.copy() # line 722
master_scaled = master_scaled.multiply(data_exposure / dark_exposure) # line 725
data_exposure / dark_exposure is a 0-d astropy.units.Quantity, treated as float64 rather
than a weak scalar; multiplying the float32 master by it upcasts to float64, so
ccd.subtract(master_scaled) then returns float64 even though ccd is float32. The
scale=False branch calls ccd.subtract(master) directly with no intermediate multiply, so it
stays float32. The master.copy() on line 722 is also unnecessary: multiply() already
returns a new object.
Reproducer and output:
import numpy as np
from astropy.nddata import CCDData
import astropy.units as u
import ccdproc
rng = np.random.default_rng(0)
light = CCDData(rng.random((50, 50)).astype(np.float32), unit=u.adu, meta={'exposure': 30.0})
dark = CCDData(rng.random((50, 50)).astype(np.float32) * 0.1, unit=u.adu, meta={'exposure': 10.0})
r_scaled = ccdproc.subtract_dark(light, dark, exposure_time='exposure',
exposure_unit=u.second, scale=True)
r_unscaled = ccdproc.subtract_dark(light, dark, exposure_time='exposure',
exposure_unit=u.second, scale=False)
print(r_scaled.data.dtype, r_unscaled.data.dtype)
Output: float64 float32
Suggested fix: cast the ratio to master.dtype (or use a plain Python float) before
multiplying, e.g. master_scaled.multiply(np.array(data_exposure / dark_exposure, dtype=master.dtype)), and drop the .copy() on line 722.
Cost in practice: on a 4096x4096 float32 light with a scaled dark, this promotion (extra copy
plus float64 intermediate) measured 336 MB peak vs 201 MB when the ratio is pre-cast to float32
before scaling.
This issue was written and opened by Claude Code at the direction of Matt Craig (@mwcraig).
Environment: ccdproc 2.4.3, astropy 7.1.0, numpy 2.3.5.
With a float32 image and a float32 master dark,
subtract_dark(ccd, master, scale=True)returns float64 data;
scale=Falsereturns float32. Cause (ccdproc/core.py): thescale=Truebranch doesdata_exposure / dark_exposureis a 0-dastropy.units.Quantity, treated as float64 ratherthan a weak scalar; multiplying the float32 master by it upcasts to float64, so
ccd.subtract(master_scaled)then returns float64 even thoughccdis float32. Thescale=Falsebranch callsccd.subtract(master)directly with no intermediate multiply, so itstays float32. The
master.copy()on line 722 is also unnecessary:multiply()alreadyreturns a new object.
Reproducer and output:
Output:
float64 float32Suggested fix: cast the ratio to
master.dtype(or use a plain Pythonfloat) beforemultiplying, e.g.
master_scaled.multiply(np.array(data_exposure / dark_exposure, dtype=master.dtype)), and drop the.copy()on line 722.Cost in practice: on a 4096x4096 float32 light with a scaled dark, this promotion (extra copy
plus float64 intermediate) measured 336 MB peak vs 201 MB when the ratio is pre-cast to float32
before scaling.