[Debian-astro-maintainers] Bug#1114394: specreduce: FTBFS: dh_auto_test: error: pybuild --test --test-pytest -i python{version} -p 3.13 returned exit code 13

Santiago Vila sanvila at debian.org
Fri Sep 5 19:26:53 BST 2025


Package: src:specreduce
Version: 1.6.0-1
Severity: serious
Tags: ftbfs forky sid

Dear maintainer:

During a rebuild of all packages in unstable, your package failed to build.

Below you will find how the build ends (probably the most relevant part,
but not necessarily). If required, the full build log is available here:

https://people.debian.org/~sanvila/build-logs/202509/

About the archive rebuild: The build was made on virtual machines from AWS,
using sbuild and a reduced chroot with only build-essential packages.

If you could not reproduce the bug please contact me privately, as I
am willing to provide ssh access to a virtual machine where the bug is
fully reproducible.

If this is really a bug in one of the build-depends, please use
reassign and add an affects on src:specreduce, so that this is still
visible in the BTS web page for this package.

Thanks.

--------------------------------------------------------------------------------
[...]
 debian/rules clean
dh clean --with python3 --buildsystem=pybuild
   dh_auto_clean -O--buildsystem=pybuild
   dh_autoreconf_clean -O--buildsystem=pybuild
   dh_clean -O--buildsystem=pybuild
 debian/rules binary
dh binary --with python3 --buildsystem=pybuild
   dh_update_autotools_config -O--buildsystem=pybuild
   dh_autoreconf -O--buildsystem=pybuild
   dh_auto_configure -O--buildsystem=pybuild
   dh_auto_build -O--buildsystem=pybuild
I: pybuild plugin_pyproject:129: Building wheel for python3.13 with "build" module
I: pybuild base:311: python3.13 -m build --skip-dependency-check --no-isolation --wheel --outdir /<<PKGBUILDDIR>>/.pybuild/cpython3_3.13  
* Building wheel...

[... snipped ...]

        # Attempt to parse the spectral axis. If none is given, try instead to
        # parse a given wcs. This is put into a GWCS object to
        # then be used behind-the-scenes for all specutils operations.
        if spectral_axis is not None:
            # Ensure that the spectral axis is an astropy Quantity
            if not isinstance(spectral_axis, u.Quantity):
                raise ValueError("Spectral axis must be a `Quantity` or "
                                 "`SpectralAxis` object.")
    
            # If spectral axis is provided as an astropy Quantity, convert it
            # to a specutils SpectralAxis object.
            if not isinstance(spectral_axis, SpectralAxis):
                self._spectral_axis = SpectralAxis(
                    spectral_axis, redshift=redshift,
                    radial_velocity=radial_velocity, doppler_rest=rest_value,
                    doppler_convention=velocity_convention,
                    bin_specification=bin_specification)
            # If a SpectralAxis object is provided, we assume it doesn't need
            # information from other keywords added
            else:
                for a in [radial_velocity, redshift]:
                    if a is not None:
                        raise ValueError("Cannot separately set redshift or "
                                         "radial_velocity if a SpectralAxis "
                                         "object is input to spectral_axis")
    
                self._spectral_axis = spectral_axis
    
            if wcs is None:
                wcs = gwcs_from_array(self._spectral_axis,
                                      flux.shape,
                                      spectral_axis_index=self.spectral_axis_index
                                      )
    
        elif wcs is None:
            # If no spectral axis or wcs information is provided, initialize
            # with an empty gwcs based on the flux.
            if self.spectral_axis_index is None:
                if flux.ndim == 1:
                    self._spectral_axis_index = 0
                else:
>                   raise ValueError("Must specify spectral_axis_index if no WCS or spectral"
                                     " axis is input.")
E                   ValueError: Must specify spectral_axis_index if no WCS or spectral axis is input.

/usr/lib/python3/dist-packages/specutils/spectra/spectrum.py:351: ValueError
____________ TestMeasureCrossDispersionProfile.test_errors_warnings ____________

self = <specreduce.tests.test_utils.TestMeasureCrossDispersionProfile object at 0x7f8286e22050>

    def test_errors_warnings(self):
        img = mk_gaussian_img(nrows=10, ncols=10)
        with pytest.raises(ValueError,
                           match='`crossdisp_axis` must be 0 or 1'):
            measure_cross_dispersion_profile(img, crossdisp_axis=2)
    
        with pytest.raises(ValueError, match='`trace` must be Trace object, '
                                             'number to specify the location '
                                             'of a FlatTrace, or None to use '
                                             'center of image.'):
>           measure_cross_dispersion_profile(img, trace='not a trace or a number')

specreduce/tests/test_utils.py:147: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
specreduce/utils/utils.py:92: in measure_cross_dispersion_profile
    image = parser._parse_image(image, disp_axis=disp_axis)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
specreduce/core.py:95: in _parse_image
    return self._get_data_from_image(image, disp_axis=disp_axis, mask_treatment=mask_treatment)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
specreduce/core.py:155: in _get_data_from_image
    img = Spectrum(
/usr/lib/python3/dist-packages/astropy/utils/decorators.py:143: in deprecated_func
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3/dist-packages/specutils/spectra/spectrum.py:960: in __init__
    super().__init__(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'Spectrum1D' object has no attribute '_data'") raised in repr()] Spectrum1D object at 0x7f8286c854f0>
flux = <Quantity [[0.04393693, 0.04393693, 0.04393693, 0.04393693, 0.04393693,
            0.04393693, 0.04393693, 0.04393693...23323, 0.96923323, 0.96923323, 0.96923323,
            0.96923323, 0.96923323, 0.96923323, 0.96923323, 0.96923323]] DN>
spectral_axis = <Quantity [0., 1., 2., 3., 4., 5., 6., 7., 8., 9.] pix>
spectral_axis_index = None, wcs = None, velocity_convention = None
rest_value = None, redshift = None, radial_velocity = None
bin_specification = None, move_spectral_axis = None
kwargs = {'mask': array([[False, False, False, False, False, False, False, False, False,
        False],
       [False, False, ..., 1., 1., 1., 1., 1., 1., 1., 1.],
                     [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]]), 'unit': Unit("DN")}
unknown_kwargs = set(), matching_axes = [0, 1], add_elements = [0, 1], i = 1
add_element = 1

    def __init__(self, flux=None, spectral_axis=None, spectral_axis_index=None,
                 wcs=None, velocity_convention=None, rest_value=None,
                 redshift=None, radial_velocity=None, bin_specification=None,
                 move_spectral_axis=None, **kwargs):
    
        if spectral_axis_index == -1:
            spectral_axis_index = flux.ndim - 1
    
        # If the flux (data) argument is already a Spectrum (as it would
        # be for internal arithmetic operations), avoid setup entirely.
        if isinstance(flux, Spectrum):
            self._spectral_axis_index = flux.spectral_axis_index
            self._spectral_axis = flux.spectral_axis
            super().__init__(flux)
            return
    
        self._spectral_axis_index = spectral_axis_index
        # Might as well handle this right away
        if spectral_axis_index is None and flux is not None:
            if flux.ndim == 1:
                self._spectral_axis_index = 0
        elif flux is None:
            self._spectral_axis_index = 0
    
        # Check for pre-defined entries in the kwargs dictionary.
        unknown_kwargs = set(kwargs).difference(
            {'data', 'unit', 'uncertainty', 'meta', 'mask', 'copy',
             'extra_coords'})
    
        if len(unknown_kwargs) > 0:
            raise ValueError("Initializer contains unknown arguments(s): {}."
                             "".format(', '.join(map(str, unknown_kwargs))))
    
        # Handle initializing from NDCube objects
        elif isinstance(flux, NDCube):
            if flux.unit is None:
                raise ValueError("Input NDCube missing unit parameter")
    
            # Change the flux array from bare ndarray to a Quantity
            q_flux = flux.data << u.Unit(flux.unit)
    
            self.__init__(flux=q_flux, wcs=flux.wcs, mask=flux.mask,
                          uncertainty=flux.uncertainty)
            return
    
        # If the mask kwarg is not passed to the constructor, but the flux array
        # contains NaNs, add the NaN locations to the mask.
        if "mask" not in kwargs and flux is not None:
            nan_mask = np.isnan(flux)
            if nan_mask.any():
                if hasattr(self, "mask"):
                    kwargs["mask"] = np.logical_or(nan_mask, self.mask)
                else:
                    kwargs["mask"] = nan_mask.copy()
            del nan_mask
    
        # Ensure that the flux argument is an astropy quantity
        if flux is not None:
            if not isinstance(flux, u.Quantity):
                raise ValueError("Flux must be a `Quantity` object.")
            elif flux.isscalar:
                flux = u.Quantity([flux])
    
        # Ensure that only one or neither of these parameters is set
        if redshift is not None and radial_velocity is not None:
            raise ValueError("Cannot set both radial_velocity and redshift at "
                             "the same time.")
    
        # In cases of slicing, new objects will be initialized with `data`
        # instead of ``flux``. Ensure we grab the `data` argument.
        if flux is None and 'data' in kwargs:
            flux = kwargs.pop('data')
    
        # Ensure that the unit information codified in the quantity object is
        # the One True Unit.
        kwargs.setdefault('unit', flux.unit if isinstance(flux, u.Quantity)
                                            else kwargs.get('unit'))
    
        # In the case where the arithmetic operation is being performed with
        # a single float, int, or array object, just go ahead and ignore wcs
        # requirements
        if np.ndim(flux) == 0 and spectral_axis is None and wcs is None:
            super(Spectrum, self).__init__(data=flux, wcs=wcs, **kwargs)
            return
    
        if rest_value is None:
            if hasattr(wcs, 'rest_frequency') and wcs.rest_frequency != 0:
                rest_value = wcs.rest_frequency * u.Hz
            elif hasattr(wcs, 'rest_wavelength') and wcs.rest_wavelength != 0:
                rest_value = wcs.rest_wavelength * u.AA
            elif hasattr(wcs, 'wcs') and hasattr(wcs.wcs, 'restfrq') and wcs.wcs.restfrq > 0:
                rest_value = wcs.wcs.restfrq * u.Hz
            elif hasattr(wcs, 'wcs') and hasattr(wcs.wcs, 'restwav') and wcs.wcs.restwav > 0:
                rest_value = wcs.wcs.restwav * u.m
            else:
                rest_value = None
        else:
            if not isinstance(rest_value, u.Quantity):
                warnings.warn("No unit information provided with rest value. "
                              f"Assuming units of spectral axis ('{spectral_axis.unit}').")
                rest_value = u.Quantity(rest_value, spectral_axis.unit)
            elif not rest_value.unit.is_equivalent(u.AA, equivalencies=u.spectral()):
                raise u.UnitsError("Rest value must be "
                                   "energy/wavelength/frequency equivalent.")
    
        # If flux and spectral axis are both specified, check that their lengths
        # match or are off by one (implying the spectral axis stores bin edges).
        # If we can't determine which flux axis corresponds to the spectral axis
        # we raise an error.
        if flux is not None and spectral_axis is not None:
            if spectral_axis_index is None:
                if flux.ndim == 1:
                    self._spectral_axis_index = 0
                else:
                    matching_axes = []
                    if bin_specification == "centers":
                        add_elements = [0,]
                    elif bin_specification == "edges":
                        add_elements = [1,]
                    elif bin_specification is None:
                        add_elements = [0,1]
                    for i in range(flux.ndim):
                        for add_element in add_elements:
                            if spectral_axis.shape[0] == flux.shape[i] + add_element:
                                matching_axes.append(i)
    
                    if len(matching_axes) == 1:
                        self._spectral_axis_index = matching_axes[0]
                    else:
>                       raise ValueError("Unable to determine which flux axis corresponds to "
                                         "the spectral axis. Please specify spectral_axis_index"
                                         " or provide a spectral_axis matching a flux axis.")
E                       ValueError: Unable to determine which flux axis corresponds to the spectral axis. Please specify spectral_axis_index or provide a spectral_axis matching a flux axis.

/usr/lib/python3/dist-packages/specutils/spectra/spectrum.py:213: ValueError

During handling of the above exception, another exception occurred:

self = <specreduce.tests.test_utils.TestMeasureCrossDispersionProfile object at 0x7f8286e22050>

    def test_errors_warnings(self):
        img = mk_gaussian_img(nrows=10, ncols=10)
        with pytest.raises(ValueError,
                           match='`crossdisp_axis` must be 0 or 1'):
            measure_cross_dispersion_profile(img, crossdisp_axis=2)
    
>       with pytest.raises(ValueError, match='`trace` must be Trace object, '
                                             'number to specify the location '
                                             'of a FlatTrace, or None to use '
                                             'center of image.'):
E                                            AssertionError: Regex pattern did not match.
E                                             Regex: '`trace` must be Trace object, number to specify the location of a FlatTrace, or None to use center of image.'
E                                             Input: 'Unable to determine which flux axis corresponds to the spectral axis. Please specify spectral_axis_index or provide a spectral_axis matching a flux axis.'

specreduce/tests/test_utils.py:143: AssertionError
=========================== short test summary info ============================
FAILED specreduce/tests/test_background.py::TestMasksBackground::test_fully_masked_column[apply] - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_background.py::TestMasksBackground::test_fully_masked_column[propagate] - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_background.py::TestMasksBackground::test_fully_masked_column[zero_fill] - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_background.py::TestMasksBackground::test_fully_masked_image[apply] - AssertionError: Regex pattern did not match.
FAILED specreduce/tests/test_background.py::TestMasksBackground::test_fully_masked_image[propagate] - AssertionError: Regex pattern did not match.
FAILED specreduce/tests/test_background.py::TestMasksBackground::test_mask_treatment_bkg_img_spectrum[apply-expected0] - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_background.py::TestMasksBackground::test_mask_treatment_bkg_img_spectrum[propagate-expected1] - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_background.py::TestMasksBackground::test_mask_treatment_bkg_img_spectrum[zero_fill-expected2] - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_background.py::TestMasksBackground::test_sub_bkg_image - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_tracing.py::TestMasksTracing::test_flat_and_basic_trace_mask - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_tracing.py::TestMasksTracing::test_array_trace_masking - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_tracing.py::TestMasksTracing::test_fit_trace_fully_masked_cols[apply] - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_tracing.py::TestMasksTracing::test_fit_trace_fully_masked_cols[propagate] - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_tracing.py::TestMasksTracing::test_fit_trace_fully_masked_cols[apply_nan_only] - ValueError: Unable to determine which flux axis corresponds to the spectral...
FAILED specreduce/tests/test_utils.py::TestMeasureCrossDispersionProfile::test_measure_cross_dispersion_profile[10-None] - ValueError: Must specify spectral_axis_index if no WCS or spectral axis is ...
FAILED specreduce/tests/test_utils.py::TestMeasureCrossDispersionProfile::test_measure_cross_dispersion_profile[10-1] - ValueError: Must specify spectral_axis_index if no WCS or spectral axis is ...
FAILED specreduce/tests/test_utils.py::TestMeasureCrossDispersionProfile::test_measure_cross_dispersion_profile[10-pixel2] - ValueError: Must specify spectral_axis_index if no WCS or spectral axis is ...
FAILED specreduce/tests/test_utils.py::TestMeasureCrossDispersionProfile::test_measure_cross_dispersion_profile[9-None] - ValueError: Must specify spectral_axis_index if no WCS or spectral axis is ...
FAILED specreduce/tests/test_utils.py::TestMeasureCrossDispersionProfile::test_measure_cross_dispersion_profile[9-1] - ValueError: Must specify spectral_axis_index if no WCS or spectral axis is ...
FAILED specreduce/tests/test_utils.py::TestMeasureCrossDispersionProfile::test_measure_cross_dispersion_profile[9-pixel2] - ValueError: Must specify spectral_axis_index if no WCS or spectral axis is ...
FAILED specreduce/tests/test_utils.py::TestMeasureCrossDispersionProfile::test_errors_warnings - AssertionError: Regex pattern did not match.
ERROR specreduce/tests/test_background.py::test_background - ValueError: Must specify spectral_axis_index if no WCS or spectral axis is ...
ERROR specreduce/tests/test_background.py::test_warnings_errors - ValueError: Must specify spectral_axis_index if no WCS or spectral axis is ...
ERROR specreduce/tests/test_image_parsing.py::test_parse_general - ValueError: Must specify spectral_axis_index if no WCS or spectral axis is ...
ERROR specreduce/tests/test_image_parsing.py::test_parse_horne - ValueError: Must specify spectral_axis_index if no WCS or spectral axis is ...
============= 21 failed, 55 passed, 24 skipped, 4 errors in 2.95s ==============
E: pybuild pybuild:389: test: plugin pyproject failed with: exit code=1: cd /<<PKGBUILDDIR>>/.pybuild/cpython3_3.13/build; python3.13 -m pytest 
dh_auto_test: error: pybuild --test --test-pytest -i python{version} -p 3.13 returned exit code 13
make: *** [debian/rules:7: binary] Error 25
dpkg-buildpackage: error: debian/rules binary subprocess returned exit status 2
--------------------------------------------------------------------------------



More information about the Debian-astro-maintainers mailing list