Skip to content

subtract_dark(scale=True) promotes float32 inputs to float64; scale=False does not #1013

Description

@mwcraig

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions