Write combine(output_file=) from a NumPy copy; map dtype to the array namespace - #999
Conversation
…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 Report✅ All modified and coverable lines are covered by tests. 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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
| 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 |
There was a problem hiding this comment.
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.
| return arr.astype(arr.dtype.type, copy=False) | ||
|
|
||
|
|
||
| def _to_numpy(arr): |
There was a problem hiding this comment.
Wasn't something like this added earlier?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
…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
Closes out the last three ccdproc-side items in section 1 of #971.
combine(output_file=...)handed the namespace result straight toCCDData.write(combiner.py:1273), whichastropy.io.fitscannot take (AttributeError: 'Array' object has no attribute 'astype'on array-api-strict). The writer now gets aCCDDatabuilt from NumPy copies ofdata,maskanduncertainty.array(same uncertainty class,unit/meta/wcscarried over) and the returned result stays in its namespace;test_combine_overwrite_outputnow 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 isNoneas on JAX), because array-api-strict refuses to export from its non-default devices. This is the same body as thenumpy_copytest helper from #998, which now just delegates to it so the device logic lives in one place. Two consequences worth knowing:numpy_copyare now attributed tocore.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 toxp.asarray/xp.astype(combiner.py:191,:1104); a builtinintor a string such as"float32"is a valid NumPy dtype but array-api-strict rejects it.core._namespace_dtype(dtype, xp)resolves the name withnumpy.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. Newtest_combiner_dtype_mapped_to_namespacecoversint,float,"float32",np.float32; thedtypedocstrings are updated.test_combiner_result_dtypethen failed in its own body — comparing the integer result with a float reference, which the standard does not promote — and now compares with a Pythonint.subtract_overscanusedlen(oscan)for the model fit (core.py:698); nowoscan.shape[0]. The test stays xfailed on #933 (astropy.modeling), as expected.Escape baseline (
ccdproc/tests/array_escape_baseline.txt): regenerated on dask withCCDPROC_WRITE_ESCAPE_BASELINE=1._to_numpyis the one new site, taggedBOUNDARY. The rewrite also dropped theaverage_combine/median_combine/sum_combineentries, which have not fired since #992 replaced themask=constructor calls with_mask, and updated the header comment (the writer's current text). The dask enforce run passes.Results
test_combine_overwrite_outputandtest_combiner_result_dtypeclear. All 13 that remain are upstream-blocked: 11 × Array API: Combiner.sigma_clipping and the default sigma_func densify via astropy.stats #929, Array API: units/Quantity handling with non-numpy arrays #936, Consider marray as a uniform masked-array representation across array backends #983 — section 1 of Tracking: remaining work for the array-API migration #971 is done.test_combiner.py+test_ccdproc.py177 passed each. dask + escape ratchet: 554 passed, no new escapes.🤖 Generated with Claude Code
https://claude.ai/code/session_01S36ZzAAVXVm32vuTdtCQME