subtract_dark(scale=True) promotes float32 inputs to float64; scale=False does not
- Dominant language
- Python
- Stars
- 93
- Forks
- 92
- Avg merge
- 14h 44m
- Merged PRs (30d)
- 30
Description
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
```python
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:
```python
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.
Contributor guide
Research direction
Read ccdproc/core.py around lines 722 and 725, then run the reproducer with float32 light and dark CCDData. The fix is complete when scale=True preserves the float32 result like scale=False, removes the unnecessary copy, and avoids the float64 intermediate and its extra memory use.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- numpy, python
- Domain
- data, performance
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 90/100