You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is the canonical checklist for finishing the array-API (array-library-agnostic) migration. It supersedes #909 as the place to track status; #909 and its children (#910–#913) remain as the CuPy-specific findings.
State as of 2026-08-25 (post-#989/#990): the August batch (#972–#980, plus #985 CI coverage), #987 (NaN-aware sum/mean/std/median fallbacks, closing #986), #988 (combiner internals: xp.any/xp.count_nonzero, namespace bool mask dtypes, nbytes-free memory sizing), #989 (array-API median fallback for subtract_overscan, wrapping nanmedian with numpy's NaN-propagating semantics) and #990 (test-only hygiene batch addressing #969/#970 and the test-body numpy-isms) are all merged. #992 (combine-method mask= bucket, 63 → 54) and #993 (gain_correct/flat_correctdevice=, 54 → 41) are merged too. #995 (namespace dtypes: ccd_process bool, transform_image mask, combine()/ImageFileCollection FITS byte order) and #994 (clip_extrema rank-comparison rewrite) are merged too (2026-08-25, 6724c8e): strict is now 31 on main, and every section-1 bucket that was on the strict failure list is closed. What is left on strict is 18 test-body fixes (section 2) and 13 upstream-blocked tests (section 3). CI exercises numpy, jax, dask (including dask+bottleneck and a dask escape-baseline gate); array-api-strict runs as a soft (continue-on-error) job. CuPy and torch are not in CI. User docs live in docs/array_api.rst. 36 backend_xfail markers remain in the test suite. Later on 2026-08-25, three PRs are open on top of 6724c8e:#996 (narrows the two stale test_cosmicray.py markers, 5 XPASS → 0), #997 (#982 and the correlated add/subtract leak in the wrapper) and #998 (the section-2 test bodies, strict 31 → 15). Merged together locally they give strict 15 failed, 496 passed, 36 skipped, 45 xfailed, 0 xpassed; numpy 557 passed / 35 skipped; jax and dask green apart from xfails. Of the 15, 11 are #929, one each is #936, #983 (test_rebin_ccddata[True-True]) and the two new ccdproc items below (combine(output_file=) writing a namespace array, combine(dtype=int)). All three merged 2026-08-25 (main = 4d7d21b); strict on main verified at exactly those 15.#999 then took the two ccdproc items plus the len(oscan) one-liner; merged 2026-08-26 (main = be80055). Strict on main verified at 13 failed / 503 passed / 36 skipped / 45 xfailed / 0 xpassed, and every remaining failure is upstream-blocked: 8 × #929 via the default sigma_func (median_absolute_deviation), 3 × #929 via Combiner.sigma_clipping (sigma_clip), #936, #983. Section 1 is complete.#946 and #943 are closed. 2026-08-26, later: the #929 half that is fixable on the ccdproc side is now in two PRs off be80055: #1000 (sigma_func → xp-native MAD when the namespace is not numpy; strict 13 → 5) and #1001 (Combiner.sigma_clipping → xp-native clip reproducing astropy.stats.sigma_clip; strict 13 → 10). Numpy keeps astropy in both. Merged together locally: strict 2 failed / 1129 passed / 0 xpassed (#936, #983 only), the two BOUNDARY lines leave the escape baseline, and the dask enforce run is clean.
Current strict result on main (1b9b621): 63 failed, 418 passed, 36 skipped, 46 xfailed, 5 xpassed — down from 85 with #989 + #990. With #993 (711bb26) the strict job was 41; #995 and #994 (6724c8e) take it to 31 failed, 469 passed, 36 skipped, 43 xfailed, 5 xpassed with no new failures; the 5 XPASSes are still the stale test_cosmicray.py markers. The job is continue-on-error, so read the pytest summary line in its log rather than the job conclusion. Every one of the 63 has been traced to a cause (2026-08-25):
#
cause
where
section
12
average_combine/sum_combine/median_combine build the result with CCDData(..., mask=mask); astropy's NDData.mask setter forces numpy and cannot convert a device1 strict array
combiner.py:726, :804, :616
1
9
gain_correct calls xp.asarray(gain_value) without device= ("two different devices")
core.py:968
1
2
flat_correct calls xp.asarray(flat_mean) without device= (same class)
core.py:1057
1
4
ccd_process bad-pixel mask uses builtin bool instead of xp.bool
core.py:383
1
4
clip_extrema fancy-index assignment
combiner.py:406
1
2
combine() passes a numpy dtype (ccd.data.dtype.type) to the requested namespace's asarray — strict warns, filterwarnings=error fails
combiner.py:1082
1
2
uncertainty propagation for multiply/divide delegates to astropy's _propagate_multiply_divide, whose np.abs(...) (nduncertainty.py:832) densifies the strict array and the mixed expression then fails
_ccddata_wrapper_for_array_api.py:305–330
1
1
transform_image applies the user transform to the bool mask; strict rejects 10 * bool_array (numpy silently promotes)
core.py:1153
1
8
astropy.stats densification: default sigma_func (median_absolute_deviation, 5) and sigma_clipping (sigma_clip, 3)
test bodies call astropy CCDData.multiply(...) on strict data to build scaled inputs; astropy's _prepare_then_do_arithmetic does np.result_type(ref, operand) on a strict dtype ("Could not convert Array … to a NumPy dtype") — this is the NDData-arithmetic upstream item already in #940, not a ccdproc code path
combine() given FITS filenames / numpy CCDData with no array_package= returns numpy data, and the tests then compare it with strict arrays via xp.all(xpx.isclose(...)); array_package is opt-in by design, so the tests should pass it
So: 36 are ccdproc bugs fixable now (section 1), 17 are test-body fixes (section 2), and 10 are upstream (#929 ×8, #936, astropy#20268) — plus the rebin/mask-setter one that is fixable with the _mask workaround if we want it. The 5 xpassed are all in test_cosmicray.py: the two test_cosmicray_median_masked_column[masked_array-*] cases (stale since #979) and the three gain_apply=False cases of test_cosmicray_gain_correct_uncertainty_namespace (the gain_apply=True cases still xfail, so that marker needs narrowing to the True parametrizations, not removing).
1. Fixable-now bugs in ccdproc (no upstream dependency)
Combiner.average_combine/sum_combine/median_combine pass mask= to the CCDData(...) constructor (combiner.py:726, :804, :616), so astropy's numpy-forcing mask setter runs on the result. ccd_process already sidesteps the same setter by assigning nccd._mask (core.py:381); do the same here. 12 strict failures — biggest remaining bucket — PR Keep combine result masks in the array namespace #992 (also fixes the same setter sites inside combine() and its numpy-only mask.copy(); the remaining members of the bucket now stop at Array API: Combiner.sigma_clipping and the default sigma_func densify via astropy.stats #929 (median_combine's default sigma_func) or at test_writeable_after_combine, which writes a device1 array to FITS and belongs with the section-2 "write a numpy copy" tests)
gain_correct calls xp.asarray(gain_value) without device= (core.py:968), so the scalar lands on the default device and the multiply fails when the image is elsewhere. Surfaced by Add an array-API median fallback for subtract_overscan #989; 9 strict failures (test_gain.py ×5, test_gain_correct* ×3, test_ccd_process_does_not_change_input) — PR Fix gain_correct and flat_correct on a non-default device #993 (also makes a plain-number gain a float, since the standard does not promote int64 with float64)
flat_correct has the same bug for xp.asarray(flat_mean) (core.py:1057). 2 strict failures (test_flat_correct, test_flat_correct_norm_value) — PR Fix gain_correct and flat_correct on a non-default device #993 (the two tests then needed their flats created on the testing device; fixed in the same PR)
Combiner.clip_extrema assigns through fancy indexing (combiner.py:406, IndexError: Fancy indexing __setitem__ is not supported); needs xpx.at or a where-based rewrite. 4 strict failures — PR Compute Combiner.clip_extrema mask with rank comparison instead of scatter #994 (no scatter at all: argsort twice gives each image's per-pixel rank, and ranks < nlow | ranks >= n - nhigh is the mask; bit-identical to the old code on 8400 random trials incl. ties/NaNs)
combine() converts file-read data with xp.asarray(ccd.data, dtype=ccd.data.dtype.type) (combiner.py:1082, and the uncertainty line below it), handing a numpy dtype to the target namespace; strict warns and the suite's filterwarnings = error turns it into a failure. Let xp.asarray infer the dtype or map it through the namespace. 2 strict failures (test_combine_ccd_with_uncertainty_and_mask_from_fits) — PR Fix array-namespace dtype handling in ccd_process, transform_image, combine, and ImageFileCollection #995 (dropping dtype= alone is not enough: strict and jax reject big-endian >f8 outright, so a _native_numpy helper converts to native byte order in numpy first; the same three sites in ImageFileCollection.ccds/data get the same fix, and its mask has to go through _mask because the public setter forces numpy). Only [mean] clears; [function] then stops at _make_mean_scaler (section 2)
_CCDDataWrapperForArrayAPI uncertainty: _propagate_multiply/_propagate_divide call super()._propagate_multiply_divide, and astropy's implementation uses np.abs(...) (nduncertainty.py:832), which densifies the strict operand; the wrapper needs to own that formula the way it owns add/subtract (upstream fix would be abs() instead of np.abs; worth a note in Tracking: upstream array-API blockers (astropy, reproject, astroscrappy) #940). 2 strict failures (test_flat_correct_deviation, test_flat_correct_data_uncertainty) — PR Fix gain_correct and flat_correct on a non-default device #993 (the mixin now owns _propagate_multiply_divide; after the device= fix this bucket also sat behind the five test_gain.py failures; the two strict xfail markers in test_ccddata_wrapper_for_array_api.py covering it are dropped)
combine(dtype=...)/Combiner(dtype=...) hand the user's dtype straight to xp.astype/xp.asarray (combiner.py:1104, :191); a builtin Python int is a valid NumPy dtype but array-api-strict rejects it (AttributeError: type object 'int' has no attribute '_np_dtype'). 1 strict failure (test_combiner_result_dtype, whose dtype=int case is the point of the test), reachable since Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 cleared the .multiply() failure in front of it — PR Write combine(output_file=) from a NumPy copy; map dtype to the array namespace #999 (core._namespace_dtype resolves the name with numpy.dtype and looks it up on the namespace; the namespace's own dtype objects pass through)
Combiner.scaling setter has an array-API bug exposed when test bodies stop using numpy .mean() — diagnosed 2026-08-25: not a setter bug. test_3d_combiner_with_scaling fails inside the test helper _make_mean_scaler (test_combiner.py:52, .mean()/np.ma.average), and test_combiner_with_scaling fails earlier on ccd_data.multiply(3) in the test body (astropy NDData arithmetic). Both moved to section 2
Combiner._get_nan_substituted_data calls the numpy method self._data_arr_mask.any() (combiner.py:517) — fixed in Make combiner internals array-API compatible #988, along with the further numpy-isms the fix exposed in the same functions (.sum(axis=0) mask counts, len() on arrays, list passed to xp.reshape, int→float promotion before sqrt)
Builtin bool passed where xp.bool is required, in the mask paths of Combiner and _CCDDataWrapperForArrayAPI — fixed in Make combiner internals array-API compatible #988 (wrapper mask setters plus the test sites building masks in the strict namespace)
Six test_combiner.py tests build scaled inputs with astropy CCDData.multiply(...) on strict data (test_combiner_with_scaling, test_combiner_with_scaling_uncertainty ×3, test_combiner_result_dtype, test_combine_overwrite_output); astropy's np.result_type in _prepare_then_do_arithmetic cannot take a strict dtype. Build the inputs in the namespace (CCDData(ccd.data * 3, unit=...)) instead — this is the NDData-arithmetic limitation already listed in Tracking: upstream array-API blockers (astropy, reproject, astroscrappy) #940 and is what _CCDDataWrapperForArrayAPI exists for. 6 strict failures — PR Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 (the four jax backend_xfail markers for the same .multiply() calls are dropped, they XPASS; test_combiner_with_scaling_uncertainty[average|sum] clear, and the other four stop on the next cause: test_combine_overwrite_output on combine(output_file=) (section 3), test_combiner_result_dtype on combine(dtype=int) (section 1), test_combiner_with_scaling and test_combiner_with_scaling_uncertainty[median_combine] on Array API: Combiner.sigma_clipping and the default sigma_func densify via astropy.stats #929)
Five combine() tests feed FITS filenames or numpy CCDData without array_package= and then compare the (numpy) result against strict arrays with xp.all(xpx.isclose(...)) (test_combine_average_fitsimages, test_combine_numpyndarray, test_combine_average_ccddata, test_combine_limitedmem_fitsimages, test_combine_limitedmem_scale_fitsimages). Pass array_package=xp (or compare in numpy). 5 strict failures — PR Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 (array_package=xp plus Combiner(ccd_list, xp=xp) for the reference; test_combine_average_ccddata converts its CCDData list by hand because array_package is ignored for CCDData input)
test_generator_ccds_without_unit fails on the strict job because of an astropy bug, not test ordering per se: the _arithmetic decorator in astropy/nddata/ccddata.py sets the module-global _config_ccd_requires_unit = False and restores it with no try/finally, so any exception inside CCDData.add/subtract/multiply/divide leaves the unit requirement disabled process-wide. test_unit_mismatch_behaves_as_expected raises inside .subtract() by design, so the flag leaks on every backend — reproduced on plain numpy with just those two tests. The full numpy run stays green only because intervening tests (e.g. test_gain) complete a successful arithmetic pass, which resets the flag; on strict those tests fail too, so nothing repairs it. Filed upstream as CCDData arithmetic leaves unit requirement disabled process-wide if the operation raises astropy#20268; listed in Tracking: upstream array-API blockers (astropy, reproject, astroscrappy) #940 — PR Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998: removing the .multiply() calls from test_combiner.py also removed the incidental successful arithmetic that had been resetting the flag, so the leak surfaced on plain numpy too; the test now resets astropy.nddata.ccddata._config_ccd_requires_unit = True itself and passes on every backend regardless of order, and the marker is gone
Escape logger only patches np.asarray/np.asanyarray/np.ma.asanyarray; it misses np.array() and ufunc-driven coercion, so it is a sampler rather than a complete detector
Decide between a parametrized xp fixture (SciPy-style, all backends in one run) and the current one-backend-per-CCDPROC_ARRAY_LIBRARY approach
This is the canonical checklist for finishing the array-API (array-library-agnostic) migration. It supersedes #909 as the place to track status; #909 and its children (#910–#913) remain as the CuPy-specific findings.
State as of 2026-08-25 (post-#989/#990): the August batch (#972–#980, plus #985 CI coverage), #987 (NaN-aware sum/mean/std/median fallbacks, closing #986), #988 (combiner internals:
xp.any/xp.count_nonzero, namespace bool mask dtypes,nbytes-free memory sizing), #989 (array-APImedianfallback forsubtract_overscan, wrappingnanmedianwith numpy's NaN-propagating semantics) and #990 (test-only hygiene batch addressing #969/#970 and the test-body numpy-isms) are all merged. #992 (combine-methodmask=bucket, 63 → 54) and #993 (gain_correct/flat_correctdevice=, 54 → 41) are merged too. #995 (namespace dtypes:ccd_processbool,transform_imagemask,combine()/ImageFileCollectionFITS byte order) and #994 (clip_extremarank-comparison rewrite) are merged too (2026-08-25,6724c8e): strict is now 31 onmain, and every section-1 bucket that was on the strict failure list is closed. What is left on strict is 18 test-body fixes (section 2) and 13 upstream-blocked tests (section 3). CI exercises numpy, jax, dask (including dask+bottleneck and a dask escape-baseline gate);array-api-strictruns as a soft (continue-on-error) job. CuPy and torch are not in CI. User docs live indocs/array_api.rst. 36backend_xfailmarkers remain in the test suite. Later on 2026-08-25, three PRs are open on top of6724c8e: #996 (narrows the two staletest_cosmicray.pymarkers, 5 XPASS → 0), #997 (#982 and the correlated add/subtract leak in the wrapper) and #998 (the section-2 test bodies, strict 31 → 15). Merged together locally they give strict 15 failed, 496 passed, 36 skipped, 45 xfailed, 0 xpassed; numpy 557 passed / 35 skipped; jax and dask green apart from xfails. Of the 15, 11 are #929, one each is #936, #983 (test_rebin_ccddata[True-True]) and the two new ccdproc items below (combine(output_file=)writing a namespace array,combine(dtype=int)). All three merged 2026-08-25 (main=4d7d21b); strict onmainverified at exactly those 15. #999 then took the two ccdproc items plus thelen(oscan)one-liner; merged 2026-08-26 (main=be80055). Strict onmainverified at 13 failed / 503 passed / 36 skipped / 45 xfailed / 0 xpassed, and every remaining failure is upstream-blocked: 8 × #929 via the defaultsigma_func(median_absolute_deviation), 3 × #929 viaCombiner.sigma_clipping(sigma_clip), #936, #983. Section 1 is complete. #946 and #943 are closed. 2026-08-26, later: the #929 half that is fixable on the ccdproc side is now in two PRs offbe80055: #1000 (sigma_func→ xp-native MAD when the namespace is not numpy; strict 13 → 5) and #1001 (Combiner.sigma_clipping→ xp-native clip reproducingastropy.stats.sigma_clip; strict 13 → 10). Numpy keeps astropy in both. Merged together locally: strict 2 failed / 1129 passed / 0 xpassed (#936, #983 only), the twoBOUNDARYlines leave the escape baseline, and the dask enforce run is clean.Current strict result on
main(1b9b621): 63 failed, 418 passed, 36 skipped, 46 xfailed, 5 xpassed — down from 85 with #989 + #990. With #993 (711bb26) the strict job was 41; #995 and #994 (6724c8e) take it to 31 failed, 469 passed, 36 skipped, 43 xfailed, 5 xpassed with no new failures; the 5 XPASSes are still the staletest_cosmicray.pymarkers. The job iscontinue-on-error, so read the pytest summary line in its log rather than the job conclusion. Every one of the 63 has been traced to a cause (2026-08-25):average_combine/sum_combine/median_combinebuild the result withCCDData(..., mask=mask); astropy'sNDData.masksetter forces numpy and cannot convert adevice1strict arraycombiner.py:726,:804,:616gain_correctcallsxp.asarray(gain_value)withoutdevice=("two different devices")core.py:968flat_correctcallsxp.asarray(flat_mean)withoutdevice=(same class)core.py:1057ccd_processbad-pixel mask uses builtinboolinstead ofxp.boolcore.py:383clip_extremafancy-index assignmentcombiner.py:406combine()passes a numpy dtype (ccd.data.dtype.type) to the requested namespace'sasarray— strict warns,filterwarnings=errorfailscombiner.py:1082_propagate_multiply_divide, whosenp.abs(...)(nduncertainty.py:832) densifies the strict array and the mixed expression then fails_ccddata_wrapper_for_array_api.py:305–330transform_imageapplies the user transform to the bool mask; strict rejects10 * bool_array(numpy silently promotes)core.py:1153astropy.statsdensification: defaultsigma_func(median_absolute_deviation, 5) andsigma_clipping(sigma_clip, 3)core.py:1291,combiner.py:491CCDData.multiply(...)on strict data to build scaled inputs; astropy's_prepare_then_do_arithmeticdoesnp.result_type(ref, operand)on a strict dtype ("Could not convert Array … to a NumPy dtype") — this is the NDData-arithmetic upstream item already in #940, not a ccdproc code pathtest_combiner.py(test_combiner_with_scaling*,test_combiner_result_dtype,test_combine_overwrite_output)combine()given FITS filenames / numpyCCDDatawith noarray_package=returns numpy data, and the tests then compare it with strict arrays viaxp.all(xpx.isclose(...));array_packageis opt-in by design, so the tests should pass ittest_combine_average_fitsimages,test_combine_numpyndarray,test_combine_average_ccddata,test_combine_limitedmem_fitsimages,test_combine_limitedmem_scale_fitsimagesccd.write()adevice1strictCCDData;astropy.io.fitscannot coerce ittest_combiner_image_file_collection_input,test_combine_image_file_collection_inputxp.asarray(np_mgrid[...]) / 10.0— integer strict array divided by a floattest_ccdproc.py:304(test_subtract_overscan_model)_make_mean_scaleruses.mean()/np.ma.average— this is the "Combiner.scalingsetter bug" from #990's deferred list; the setter is finetest_combiner.py:52(test_3d_combiner_with_scaling)_arithmeticdecorator leaks_config_ccd_requires_unit=False(astropy/astropy#20268)test_generator_ccds_without_unitNDData.masksetter (#983's blocker)test_rebin_ccddata[True-True]Quantitymachinery on adevice1strict arraytest_unit_mismatch_behaves_as_expectedSo: 36 are ccdproc bugs fixable now (section 1), 17 are test-body fixes (section 2), and 10 are upstream (#929 ×8, #936, astropy#20268) — plus the rebin/mask-setter one that is fixable with the
_maskworkaround if we want it. The 5 xpassed are all intest_cosmicray.py: the twotest_cosmicray_median_masked_column[masked_array-*]cases (stale since #979) and the threegain_apply=Falsecases oftest_cosmicray_gain_correct_uncertainty_namespace(thegain_apply=Truecases still xfail, so that marker needs narrowing to theTrueparametrizations, not removing).1. Fixable-now bugs in ccdproc (no upstream dependency)
Combiner.__init__builds a nestedxp.asarray([...]); needsxp.stack(44 strict failures — biggest single win)subtract_dark(scale=True)device-propagation bugrebin()uses the.astype()method instead ofxp.astypecreate_deviationfloat × bool-mask multiplication (core.py:459)background_deviation_boxdiscards thexpx.atresult (silent no-op on immutable backends)cosmicray_medianclobbers masked-input handlingscipy.ndimageccdproc/_nanfuncs.pysubtract_overscancallsxp.median(core.py:629), which is not in the array API standard — fixed in Add an array-API median fallback for subtract_overscan #989 with amedianfallback in_nanfuncs.pyCombiner.average_combine/sum_combine/median_combinepassmask=to theCCDData(...)constructor (combiner.py:726,:804,:616), so astropy's numpy-forcingmasksetter runs on the result.ccd_processalready sidesteps the same setter by assigningnccd._mask(core.py:381); do the same here. 12 strict failures — biggest remaining bucket — PR Keep combine result masks in the array namespace #992 (also fixes the same setter sites insidecombine()and its numpy-onlymask.copy(); the remaining members of the bucket now stop at Array API: Combiner.sigma_clipping and the default sigma_func densify via astropy.stats #929 (median_combine's defaultsigma_func) or attest_writeable_after_combine, which writes adevice1array to FITS and belongs with the section-2 "write a numpy copy" tests)gain_correctcallsxp.asarray(gain_value)withoutdevice=(core.py:968), so the scalar lands on the default device and the multiply fails when the image is elsewhere. Surfaced by Add an array-API median fallback for subtract_overscan #989; 9 strict failures (test_gain.py×5,test_gain_correct*×3,test_ccd_process_does_not_change_input) — PR Fix gain_correct and flat_correct on a non-default device #993 (also makes a plain-number gain afloat, since the standard does not promoteint64withfloat64)flat_correcthas the same bug forxp.asarray(flat_mean)(core.py:1057). 2 strict failures (test_flat_correct,test_flat_correct_norm_value) — PR Fix gain_correct and flat_correct on a non-default device #993 (the two tests then needed their flats created on the testing device; fixed in the same PR)ccd_processbuilds its bad-pixel mask withxp.asarray(mask, dtype=bool)— Python builtinboolwherexp.boolis required (core.py:383; same class as create_deviation: float * bool-mask multiplication rejected by array-api-strict (core.py:459) #968/Make combiner internals array-API compatible #988's fixes). Surfaced by Add an array-API median fallback for subtract_overscan #989; 4 strict failures — PR Fix array-namespace dtype handling in ccd_process, transform_image, combine, and ImageFileCollection #995 (the same four tests then hitlog_meta._replace_array_with_placeholder, which assumed anything without__len__is NDData and raisedAttributeErroron a strict array; fixed in the same PR, plus the test bodies now build a bool mask and compare with==)Combiner.clip_extremaassigns through fancy indexing (combiner.py:406,IndexError: Fancy indexing __setitem__ is not supported); needsxpx.ator awhere-based rewrite. 4 strict failures — PR Compute Combiner.clip_extrema mask with rank comparison instead of scatter #994 (no scatter at all:argsorttwice gives each image's per-pixel rank, andranks < nlow | ranks >= n - nhighis the mask; bit-identical to the old code on 8400 random trials incl. ties/NaNs)combine()converts file-read data withxp.asarray(ccd.data, dtype=ccd.data.dtype.type)(combiner.py:1082, and the uncertainty line below it), handing a numpy dtype to the target namespace; strict warns and the suite'sfilterwarnings = errorturns it into a failure. Letxp.asarrayinfer the dtype or map it through the namespace. 2 strict failures (test_combine_ccd_with_uncertainty_and_mask_from_fits) — PR Fix array-namespace dtype handling in ccd_process, transform_image, combine, and ImageFileCollection #995 (droppingdtype=alone is not enough: strict and jax reject big-endian>f8outright, so a_native_numpyhelper converts to native byte order in numpy first; the same three sites inImageFileCollection.ccds/dataget the same fix, and its mask has to go through_maskbecause the public setter forces numpy). Only[mean]clears;[function]then stops at_make_mean_scaler(section 2)_CCDDataWrapperForArrayAPIuncertainty:_propagate_multiply/_propagate_dividecallsuper()._propagate_multiply_divide, and astropy's implementation usesnp.abs(...)(nduncertainty.py:832), which densifies the strict operand; the wrapper needs to own that formula the way it owns add/subtract (upstream fix would beabs()instead ofnp.abs; worth a note in Tracking: upstream array-API blockers (astropy, reproject, astroscrappy) #940). 2 strict failures (test_flat_correct_deviation,test_flat_correct_data_uncertainty) — PR Fix gain_correct and flat_correct on a non-default device #993 (the mixin now owns_propagate_multiply_divide; after thedevice=fix this bucket also sat behind the fivetest_gain.pyfailures; the two strict xfail markers intest_ccddata_wrapper_for_array_api.pycovering it are dropped)add/subtractwithuncertainty_correlation != 0: the wrapper still delegates to astropy's_propagate_add_sub, whose correlation term usesnp.sqrt(this * other)(nduncertainty.py:742), so a correlated add/subtract through the wrapper fails on strict withTypeError: Expected Array or Python scalar; got numpy.ndarray. Uncorrelated add/subtract is fine. Not covered by any test and not on the strict failure list; found while covering Fix gain_correct and flat_correct on a non-default device #993's multiply/divide override. Fix is the same shape as Fix gain_correct and flat_correct on a non-default device #993 (the mixin owns the formula withxp.sqrt) — PR Normalise combine()'s raw-module array_package and fix correlated add/subtract uncertainty leak #997 (test_wrapped_arithmetic_correlated_uncertaintynow coversadd/subtracttoo)transform_imageappliestransform_functo the bool mask directly (core.py:1153); numpy promotes10 * masksilently, strict raises. Cast the mask to a numeric dtype before transforming (themask > 0afterwards already assumes a numeric result). 1 strict failure (test_transform_image[True-True]) — PR Fix array-namespace dtype handling in ccd_process, transform_image, combine, and ImageFileCollection #995 (cast to the data dtype; the test body also discarded thexpx.at(...).set()result, same class as background_deviation_box discards the xpx.at result — silent no-op on immutable backends #963, and now checks the transformed mask's contents)combine()does not normalise a raw module passed asarray_package, socombine(files, array_package=dask.array)fails;Combiner.__init__was fixed in Build Combiner data and mask arrays with xp.stack #976 and the two entry points should agree — PR Normalise combine()'s raw-module array_package and fix correlated add/subtract uncertainty leak #997 (the crash itself went inert with Fix array-namespace dtype handling in ccd_process, transform_image, combine, and ImageFileCollection #995, whosedevice=-free conversion a rawdask.arrayaccepts, andcombine()re-derivesxpfrom the converted array anyway; the normalisation now matchesCombiner, an array is still accepted asarray_package, and regression tests cover rawnumpy/dask.array)combine(dtype=...)/Combiner(dtype=...)hand the user's dtype straight toxp.astype/xp.asarray(combiner.py:1104,:191); a builtin Pythonintis a valid NumPy dtype but array-api-strict rejects it (AttributeError: type object 'int' has no attribute '_np_dtype'). 1 strict failure (test_combiner_result_dtype, whosedtype=intcase is the point of the test), reachable since Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 cleared the.multiply()failure in front of it — PR Write combine(output_file=) from a NumPy copy; map dtype to the array namespace #999 (core._namespace_dtyperesolves the name withnumpy.dtypeand looks it up on the namespace; the namespace's own dtype objects pass through)subtract_overscancallslen(oscan)on the overscan array (core.py:698,xp.arange(len(oscan))); array-api-strict has no__len__. Masked on the strict job becausetest_subtract_overscan_modelis xfailed on Array API: subtract_overscan model fitting goes through numpy-only astropy.modeling #933 (withoscan.shape[0]in its place the test stops inastropy.modeling.fitting._convert_input, so the marker is right), but it is a one-line ccdproc numpy-ism — PR Write combine(output_file=) from a NumPy copy; map dtype to the array namespace #999— diagnosed 2026-08-25: not a setter bug.Combiner.scalingsetter has an array-API bug exposed when test bodies stop using numpy.mean()test_3d_combiner_with_scalingfails inside the test helper_make_mean_scaler(test_combiner.py:52,.mean()/np.ma.average), andtest_combiner_with_scalingfails earlier onccd_data.multiply(3)in the test body (astropy NDData arithmetic). Both moved to section 2Combiner._get_nan_substituted_datacalls the numpy methodself._data_arr_mask.any()(combiner.py:517) — fixed in Make combiner internals array-API compatible #988, along with the further numpy-isms the fix exposed in the same functions (.sum(axis=0)mask counts,len()on arrays, list passed toxp.reshape, int→float promotion beforesqrt)combine()memory sizing uses.nbyteson data/uncertainty/mask/flags — fixed in Make combiner internals array-API compatible #988 via_array_size_in_bytes(element count ×finfo/iinfobit width)boolpassed wherexp.boolis required, in the mask paths ofCombinerand_CCDDataWrapperForArrayAPI— fixed in Make combiner internals array-API compatible #988 (wrapper mask setters plus the test sites building masks in the strict namespace)combiner.pymedian/uncertainty paths; CCDData images returned by combiner cannot be written as FITS files #351)2. Test-suite hygiene
test_subtract_overscancalls numpy-only.copy()— fixed in Fix numpy-isms in strict-backend test bodies (test hygiene) #990 (the twomedian=Trueparametrizations went green with Add an array-API median fallback for subtract_overscan #989)np.zeros_likeintest_ccdproc_logging.py/test_rebin.py— fixed in Fix numpy-isms in strict-backend test bodies (test hygiene) #990 (test_rebin_ccddata[True-True]stays red on astropy's numpy-forcingCCDData.masksetter, Consider marray as a uniform masked-array representation across array backends #983's blocker).sum()/.mean()method calls intest_combiner.pytest bodies — fixed in Fix numpy-isms in strict-backend test bodies (test hygiene) #990, along with ellipsis indexing, int/float promotion andxp.asarray(list)→xp.stackcasestest__overscan_schangecallsxp.allclose— fixed in Fix numpy-isms in strict-backend test bodies (test hygiene) #990test_combiner.pytests build scaled inputs with astropyCCDData.multiply(...)on strict data (test_combiner_with_scaling,test_combiner_with_scaling_uncertainty×3,test_combiner_result_dtype,test_combine_overwrite_output); astropy'snp.result_typein_prepare_then_do_arithmeticcannot take a strict dtype. Build the inputs in the namespace (CCDData(ccd.data * 3, unit=...)) instead — this is the NDData-arithmetic limitation already listed in Tracking: upstream array-API blockers (astropy, reproject, astroscrappy) #940 and is what_CCDDataWrapperForArrayAPIexists for. 6 strict failures — PR Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 (the four jaxbackend_xfailmarkers for the same.multiply()calls are dropped, they XPASS;test_combiner_with_scaling_uncertainty[average|sum]clear, and the other four stop on the next cause:test_combine_overwrite_outputoncombine(output_file=)(section 3),test_combiner_result_dtypeoncombine(dtype=int)(section 1),test_combiner_with_scalingandtest_combiner_with_scaling_uncertainty[median_combine]on Array API: Combiner.sigma_clipping and the default sigma_func densify via astropy.stats #929)combine()tests feed FITS filenames or numpyCCDDatawithoutarray_package=and then compare the (numpy) result against strict arrays withxp.all(xpx.isclose(...))(test_combine_average_fitsimages,test_combine_numpyndarray,test_combine_average_ccddata,test_combine_limitedmem_fitsimages,test_combine_limitedmem_scale_fitsimages). Passarray_package=xp(or compare in numpy). 5 strict failures — PR Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 (array_package=xpplusCombiner(ccd_list, xp=xp)for the reference;test_combine_average_ccddataconverts itsCCDDatalist by hand becausearray_packageis ignored forCCDDatainput)test_combiner_image_file_collection_input/test_combine_image_file_collection_inputwrite adevice1strictCCDDatawithccd.write();astropy.io.fitscannot coerce it. Write a numpy copy. 2 strict failures — PR Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 (numpy_copy/numpy_ccddatahelpers inpytest_fixtures.py; also clearstest_writeable_after_combine[average|sum_combine], while[median_combine]stays on Array API: Combiner.sigma_clipping and the default sigma_func densify via astropy.stats #929)test_subtract_overscan_modeldivides an integer strict array by a float (test_ccdproc.py:304); castnp_mgridto float first. 2 strict failures — expect these to then land on Array API: subtract_overscan model fitting goes through numpy-only astropy.modeling #933 (astropy.modeling) — PR Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 (they do, via thelen(oscan)numpy-ism listed in section 1; nowbackend_xfail("array-api-strict")citing Array API: subtract_overscan model fitting goes through numpy-only astropy.modeling #933)_make_mean_scaler(test_combiner.py:52) uses.mean()andnp.ma.average— rewrite inxp. 2 strict failures (test_3d_combiner_with_scaling, andtest_combine_ccd_with_uncertainty_and_mask_from_fits[function]once Fix array-namespace dtype handling in ccd_process, transform_image, combine, and ImageFileCollection #995 lands) — PR Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 (both pass)device=fixes above the remaining device errors are all astropy setters/Quantity, not test bodies; re-check once those land and close if nothing is left — re-checked after Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998: none of the 15 remaining strict failures is a test-body device error; array-api-strict job: device-mismatch noise from inline array creation in test bodies #946 closed 2026-08-25test_generator_ccds_without_unitfails on the strict job because of an astropy bug, not test ordering per se: the_arithmeticdecorator inastropy/nddata/ccddata.pysets the module-global_config_ccd_requires_unit = Falseand restores it with notry/finally, so any exception insideCCDData.add/subtract/multiply/divideleaves the unit requirement disabled process-wide.test_unit_mismatch_behaves_as_expectedraises inside.subtract()by design, so the flag leaks on every backend — reproduced on plain numpy with just those two tests. The full numpy run stays green only because intervening tests (e.g.test_gain) complete a successful arithmetic pass, which resets the flag; on strict those tests fail too, so nothing repairs it. Filed upstream as CCDData arithmetic leaves unit requirement disabled process-wide if the operation raises astropy#20268; listed in Tracking: upstream array-API blockers (astropy, reproject, astroscrappy) #940 — PR Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998: removing the.multiply()calls fromtest_combiner.pyalso removed the incidental successful arithmetic that had been resetting the flag, so the leak surfaced on plain numpy too; the test now resetsastropy.nddata.ccddata._config_ccd_requires_unit = Trueitself and passes on every backend regardless of order, and the marker is goneccds()platform-dependentValueErrorbehaviour (Linux CI only) — same astropy leak as above; the xfail reason attest_image_collection.py:438should cite CCDData arithmetic leaves unit requirement disabled process-wide if the operation raises astropy#20268 rather than "platform-dependent" — PR Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 drops the marker altogether (see the previous bullet); Platform-dependent jax backend behavior: ccds() does not raise ValueError for unit-less files on Linux CI #943 closed 2026-08-25mainind33d7cf3. Design issues / blocked on upstream (tracked in #940)
Combiner.sigma_clippingand the defaultsigma_funcdensify viaastropy.stats— in progress: PR 🍾 Array-API fallback for sigma_func (MAD) when the namespace is not numpy #1000 (sigma_func) and PR Array-API fallback for Combiner.sigma_clipping when the namespace is not numpy #1001 (sigma_clipping), numpy unchanged, every other namespace xp-native; leaves strict at 2 (Array API: units/Quantity handling with non-numpy arrays #936, Consider marray as a uniform masked-array representation across array backends #983). History: 8 strict failures (5 viamedian_absolute_deviationatcore.py:1291, 3 viasigma_clipatcombiner.py:491); the largest remaining bucket once section 1 is done. After Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 it is 11: the threemedian_combinecallerstest_combiner_with_scaling,test_combiner_with_scaling_uncertainty[median_combine]andtest_writeable_after_combine[median_combine]reach the defaultsigma_funconce their first cause is cleared. With Write combine(output_file=) from a NumPy copy; map dtype to the array namespace #999 merged the split is 8 viasigma_func(core.py:1409;test_sigma_func_for_ccddata,test_combiner_dtype,test_combiner_median,test_combiner_with_scaling,test_combine_result_uncertainty_and_mask[median_combine-*],test_writeable_after_combine[median_combine],test_combiner_with_scaling_uncertainty[median_combine]) and 3 viasigma_clipping(combiner.py:480;test_combiner_sigmaclip_{high,low,single_pix}). Thesigma_funchalf is cheap on the ccdproc side (MAD =median(|x - median(x)|)on top of the_nanfuncs.pymedian); thesigma_clippinghalf is harder because the method forwards**kwdand string/callablefunc/dev_functo astropy — options are an xp-native clip orbackend_xfailmarkerscombine(output_file=...)writes the namespace result withccd.write(output_file, ...)(combiner.py:1273);astropy.io.fitsneeds NumPy, so on strict it fails withAttributeError: 'Array' object has no attribute 'astype'(ccddata.py:399). 1 strict failure (test_combine_overwrite_output, reachable since Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998). Same class as the CPU-only policy (Array API: decide a policy for inherently CPU-only operations #935): the writer needs a NumPy copy — PR Write combine(output_file=) from a NumPy copy; map dtype to the array namespace #999 (newcore._to_numpyhost-copy helper, device-generic; the writer gets a NumPyCCDDataand the namespace result is returned unchanged. This is the first library-side host-copy helper, so Array API: decide a policy for inherently CPU-only operations #935 can build on it)Quantity/units handling with non-numpy arrays — 1 strict failure (test_unit_mismatch_behaves_as_expected)cosmicray_mediandoes not exclude masked pixels from the median filter itself, so bright masked regions pull the local median (follow-up to Array API: dead code in cosmicray_median clobbers masked-input handling #932/Honor the input mask in cosmicray_median #979; runs in numpy, so it also belongs with the CPU-only policy in Array API: decide a policy for inherently CPU-only operations #935)marrayas a uniform masked-array representation across backends; bounded by astropy's numpy-onlyNDData.masksettersubtract_overscanmodel fitting goes through numpy-onlyastropy.modelingwcs_projectmixes numpy output fromreprojectwith xp operationsastropy.stats.sigma_clip,median_absolute_deviation,block_reduce/block_replicate, NDData arithmetic (np.result_typein_prepare_then_do_arithmetic,np.absin_propagate_multiply_divide; the_CCDDataWrapperForArrayAPIworkaround), the_config_ccd_requires_unitleak (CCDData arithmetic leaves unit requirement disabled process-wide if the operation raises astropy#20268),astropy.modeling,Quantity,reproject(CPU-only), astroscrappy (permanent → document under the CPU-only policy, Array API: decide a policy for inherently CPU-only operations #935),__array__(copy=)warning (Array API: locate the source of __array__(copy=...) DeprecationWarnings in CuPy runs #934)4. Tooling / infrastructure
_escape_triage.py; register pytest hooks explicitlynp.asarray/np.asanyarray/np.ma.asanyarray; it missesnp.array()and ufunc-driven coercion, so it is a sampler rather than a complete detectorxpfixture (SciPy-style, all backends in one run) and the current one-backend-per-CCDPROC_ARRAY_LIBRARYapproacharray-api-strictjob from soft to required — sections 1–2 have landed (2026-08-26); the residue is exactly 13 upstream-blocked tests (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), of which 11 are fixed by 🍾 Array-API fallback for sigma_func (MAD) when the namespace is not numpy #1000/Array-API fallback for Combiner.sigma_clipping when the namespace is not numpy #1001; the residue after those is Array API: units/Quantity handling with non-numpy arrays #936 and Consider marray as a uniform masked-array representation across array backends #983, which needbackend_xfailmarkers citing the astropy items so the job can be green5. Close out
backend_xfailmarkers after the fixes above and drop the stale ones — the five known-stale ones below are narrowed in PR Narrow two stale backend_xfail markers in test_cosmicray.py #996 (merged; strict XPASS 5 → 0), and Fix strict array-API test bodies in test_combiner/test_ccdproc/test_image_collection #998 drops five jax markers (four.multiply()ones intest_combiner.py, thetest_generator_ccds_without_unitone). Known stale before Narrow two stale backend_xfail markers in test_cosmicray.py #996:test_cosmicray.py::test_cosmicray_median_masked_column[masked_array-1.0]and[masked_array-None]XPASS on the strict job, and their reason (ascipy.ndimage.median_filterdevice failure) no longer applies after Honor the input mask in cosmicray_median #979; thetest_cosmicray_gain_correct_uncertainty_namespacemarker (test_cosmicray.py:154) XPASSes for the threegain_apply=Falsecases and should be narrowed to thegain_apply=Trueparametrizationstest_image_collection#913 that are subsumed by the issues abovedocs/array_api.rstandCHANGES.rstreflect the final set of supported backends