Skip to content

Write combine(output_file=) from a NumPy copy; map dtype to the array namespace - #999

Merged
mwcraig merged 4 commits into
astropy:mainfrom
mwcraig:fix-combine-output-file-dtype
Aug 26, 2026
Merged

Write combine(output_file=) from a NumPy copy; map dtype to the array namespace#999
mwcraig merged 4 commits into
astropy:mainfrom
mwcraig:fix-combine-output-file-dtype

Conversation

@mwcraig

@mwcraig mwcraig commented Aug 25, 2026

Copy link
Copy Markdown
Member

Closes out the last three ccdproc-side items in section 1 of #971.

combine(output_file=...) handed the namespace result straight to CCDData.write (combiner.py:1273), which astropy.io.fits cannot take (AttributeError: 'Array' object has no attribute 'astype' on array-api-strict). The writer now gets a CCDData built from NumPy copies of data, mask and uncertainty.array (same uncertainty class, unit/meta/wcs carried over) and the returned result stays in its namespace; test_combine_overwrite_output now asserts both.

The copy is a new ccdproc.core._to_numpy(arr): the deliberate host-side conversion for NumPy-only consumers. It moves the array to the namespace's default device first (asked for through the standard __array_namespace_info__().default_device(), skipped when that is None as on JAX), because array-api-strict refuses to export from its non-default devices. This is the same body as the numpy_copy test helper from #998, which now just delegates to it so the device logic lives in one place. Two consequences worth knowing:

  • it is the first library-side host-copy helper, so it is the natural building block for whatever Array API: decide a policy for inherently CPU-only operations #935 decides for the CPU-only operations;
  • in the escape log, test-suite calls through numpy_copy are now attributed to core.py _to_numpy (the innermost non-test ccdproc frame), so that site's count is inflated by the test helpers. It is one baseline entry either way.

Combiner(dtype=) / combine(dtype=) passed the user's dtype straight to xp.asarray/xp.astype (combiner.py:191, :1104); a builtin int or a string such as "float32" is a valid NumPy dtype but array-api-strict rejects it. core._namespace_dtype(dtype, xp) resolves the name with numpy.dtype(...) and looks it up on the namespace; anything NumPy cannot interpret (the namespace's own dtype objects, e.g. array_api_strict.float32) passes through untouched. Verified for builtins, strings, NumPy scalar types/dtypes and namespace dtypes on numpy, jax, dask and strict. New test_combiner_dtype_mapped_to_namespace covers int, float, "float32", np.float32; the dtype docstrings are updated. test_combiner_result_dtype then failed in its own body — comparing the integer result with a float reference, which the standard does not promote — and now compares with a Python int.

subtract_overscan used len(oscan) for the model fit (core.py:698); now oscan.shape[0]. The test stays xfailed on #933 (astropy.modeling), as expected.

Escape baseline (ccdproc/tests/array_escape_baseline.txt): regenerated on dask with CCDPROC_WRITE_ESCAPE_BASELINE=1. _to_numpy is the one new site, tagged BOUNDARY. The rewrite also dropped the average_combine/median_combine/sum_combine entries, which have not fired since #992 replaced the mask= constructor calls with _mask, and updated the header comment (the writer's current text). The dask enforce run passes.

Results

🤖 Generated with Claude Code

https://claude.ai/code/session_01S36ZzAAVXVm32vuTdtCQME

mwcraig and others added 2 commits August 25, 2026 16:37
…pace

Three small array-API fixes recorded in astropy#971:

- combine(output_file=...) handed the namespace result straight to
  CCDData.write, which astropy.io.fits cannot take. Build a NumPy copy
  of data, mask and uncertainty for the writer and return the result
  unchanged in its namespace. The conversion is a new core._to_numpy
  helper: the deliberate host-side copy, moving the array to the
  namespace's default device first (array-api-strict refuses to export
  from its non-default devices). The numpy_copy test helper now
  delegates to it so the device logic lives in one place.
- Combiner(dtype=) and combine(dtype=) passed the user's dtype straight
  to xp.asarray/xp.astype; a builtin int or a string is a valid NumPy
  dtype but array-api-strict rejects it. core._namespace_dtype resolves
  the name through numpy.dtype and looks it up on the namespace, leaving
  the namespace's own dtype objects untouched.
- subtract_overscan sized the model fit with len(oscan); use shape[0].

test_combiner_result_dtype compared an integer result with a float
reference, which the standard does not promote; compare with a Python
int instead. Escape baseline: _to_numpy is the new (BOUNDARY) site; the
average/median/sum_combine entries no longer fire since astropy#992 and are
dropped as the file's own instructions ask.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S36ZzAAVXVm32vuTdtCQME
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S36ZzAAVXVm32vuTdtCQME
@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.78%. Comparing base (2d34b6e) to head (57fd985).
⚠️ Report is 9 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #999      +/-   ##
==========================================
+ Coverage   97.76%   97.78%   +0.02%     
==========================================
  Files           9        9              
  Lines        1788     1808      +20     
==========================================
+ Hits         1748     1768      +20     
  Misses         40       40              
Flag Coverage Δ
dask 96.94% <100.00%> (+0.03%) ⬆️
jax 97.11% <95.65%> (-0.03%) ⬇️
numpy 97.67% <100.00%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Codecov flagged core.py:196-197, the `except TypeError: return dtype`
branch, as the only uncovered lines of astropy#999. Only array-api-strict's own
dtype objects reach it, and the strict job does not upload coverage, so
add a direct test with an object NumPy cannot interpret; it runs on
every backend.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S36ZzAAVXVm32vuTdtCQME
Comment thread ccdproc/combiner.py Outdated
Allows user to set dtype. See `numpy.array` ``dtype`` parameter
description. If ``None`` it uses ``np.float64``.
dtype : dtype-like or None, optional
The dtype for the stacked data and the results: a dtype object of

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shorten this

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Proposed, 4 lines instead of 7 and no longer spelling out every accepted form (the _namespace_dtype docstring does that):

    dtype : dtype-like or None, optional
        The dtype of the stacked data and the results; NumPy-style dtypes
        (e.g. ``int``, ``"float32"``) are mapped to the namespace's dtype
        of the same name. Default is ``None``, i.e. ``float64``.

The combine() one already just points here, so it can stay as is. Can push when you say so.

Written by Claude at @mwcraig's direction.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 57fd985, as proposed above.

Written by Claude at @mwcraig's direction.

Comment thread ccdproc/core.py
return arr.astype(arr.dtype.type, copy=False)


def _to_numpy(arr):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't something like this added earlier?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — numpy_copy in ccdproc/tests/pytest_fixtures.py, added in #998 (54f321d, made device-generic in 2d34b6e). Its body is exactly this function.

This PR does not duplicate it, it moves it: the body now lives here as core._to_numpy because the library needs it for the output_file writer, and numpy_copy is reduced to a one-line delegate (return _to_numpy(array)) so the device logic exists in one place. That is the pytest_fixtures.py hunk in the diff. There is no other library-side host copy to reuse — _native_numpy (#995, core.py:102) is the different job of converting a NumPy array to native byte order, and a grep of the package for to_device/default_device/np.asarray finds nothing else outside the test suite and the escape logger.

If you would rather not keep the test-suite alias at all, the two test callers (numpy_copy itself and numpy_ccddata) can import _to_numpy from ccdproc.core directly, the way test_combiner.py already imports _native_numpy; that removes numpy_copy and its docstring entirely. Happy to do either.

Written by Claude at @mwcraig's direction.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 57fd985numpy_copy is gone; numpy_ccddata and test_combiner.py import _to_numpy from ccdproc.core directly. Full suite unchanged on numpy (562 passed) and strict (the same 13, all upstream); jax and the dask escape ratchet pass.

Written by Claude at @mwcraig's direction.

…py alias

- Shorten the Combiner ``dtype`` docstring; the accepted forms are spelled
  out in _namespace_dtype's docstring.
- Remove the numpy_copy test helper, which had become a one-line delegate
  to core._to_numpy; numpy_ccddata and test_combiner.py import _to_numpy
  directly, as test_combiner.py already does for _native_numpy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S36ZzAAVXVm32vuTdtCQME
@mwcraig
mwcraig merged commit be80055 into astropy:main Aug 26, 2026
19 checks passed
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.

1 participant