Skip to content

crop(wcs=cube.extra_coords): dummy pixel components leak into points and errors #978

Description

@nabobalis

Describe the bug

When cropping with wcs=cube.extra_coords, sanitize_crop_inputs pads each point with None for the dummy PIXEL components ndcube adds for array axes without extra coords. That shows up in a few ways:

  1. The count error counts the padding and (with Fix crop world-object order for WCSes with non-contiguous components #977) lists the dummy components. A 1-entry point on a 3-D cube with two extra coords on axis 0 reports "3 components ... 4 components", and passing the 4 listed entries reports "6 components".
  2. The correctly sized point [time, None] fails with ValueError: Number of world inputs (1) does not match expected (2) when the extra coords share an array axis. This is the same on main and with Fix crop world-object order for WCSes with non-contiguous components #977.
  3. With the class matching from Fix crop world-object order for WCSes with non-contiguous components #977, a wrongly typed Quantity given for a Time extra coord matches the dummy Quantity slot and is moved there. Main raised a clear TypeError; now it raises a UnitConversionError about pix, or, if the unit is pix, crops the dummy axis by pixel index.

One option for 1 and 3: in _get_crop_item, drop the trailing dummy components from the object list and the count, and re-append Nones after matching. That reads the private ExtraCoords._cube_array_axes_without_extra_coords, as sanitize_crop_inputs already does. 2 needs a separate fix in the default crop path.

To Reproduce

import numpy as np

import astropy.units as u
from astropy.time import Time
from astropy.wcs import WCS

from ndcube import NDCube

t = Time("2020-01-01") + np.arange(4) * u.s


def run(cube, label, *points):
    try:
        result = cube.crop(*points, wcs=cube.extra_coords).shape
    except Exception as e:
        result = f"{type(e).__name__}: {e}"
    print(f"{label:28} -> {result}")


# Two extra coords sharing array axis 0 of a 3-D cube.
cube = NDCube(np.zeros((4, 5, 6)), wcs=WCS(naxis=3))
cube.extra_coords.add("time", 0, t)
cube.extra_coords.add("exposure", 0, np.arange(4) * u.s)
run(cube, "[time]", [t[1]], [t[2]])
run(cube, "[time, None]", [t[1], None], [t[2], None])
run(cube, "[time, None, None, None]", [t[1], None, None, None], [t[2], None, None, None])
run(cube, "[time, exposure]", [t[1], 1 * u.s], [t[2], 2 * u.s])

# One time extra coord on array axis 0 of a 2-D cube; a Quantity is the wrong type.
cube = NDCube(np.zeros((4, 5)), wcs=WCS(naxis=2))
cube.extra_coords.add("time", 0, t)
run(cube, "[time]", [t[1]], [t[2]])
run(cube, "[1 pix] (wrong type)", [1 * u.pix], [2 * u.pix])
run(cube, "[1 m] (wrong type)", [1 * u.m], [2 * u.m])

On main (49b70c1):

[time]                       -> ValueError: 3 components in point 0 do not match WCS with 4 components.
[time, None]                 -> ValueError: Number of world inputs (1) does not match expected (2)
[time, None, None, None]     -> ValueError: 6 components in point 0 do not match WCS with 4 components.
[time, exposure]             -> (2, 5, 6)
[time]                       -> (2, 5)
[1 pix] (wrong type)         -> TypeError: <class 'astropy.units.quantity.Quantity'> of component 0 in point 0 is incompatible with WCS component temporal_0 <class 'astropy.time.core.Time'>.
[1 m] (wrong type)           -> TypeError: <class 'astropy.units.quantity.Quantity'> of component 0 in point 0 is incompatible with WCS component temporal_0 <class 'astropy.time.core.Time'>.

With #977:

[time]                       -> ValueError: 3 components in point 0 do not match WCS with 4 components. Each point must have one entry per world object (use None for a component that should not be cropped), in order: temporal_0 (Time), CUSTOM_0 (Quantity), pixel_1 (Quantity), pixel__1 (Quantity).
[time, None]                 -> ValueError: Number of world inputs (1) does not match expected (2)
[time, None, None, None]     -> ValueError: 6 components in point 0 do not match WCS with 4 components. Each point must have one entry per world object (use None for a component that should not be cropped), in order: temporal_0 (Time), CUSTOM_0 (Quantity), pixel_1 (Quantity), pixel__1 (Quantity).
[time, exposure]             -> (2, 5, 6)
[time]                       -> (2, 5)
[1 pix] (wrong type)         -> (4, 2)
[1 m] (wrong type)           -> UnitConversionError: 'm' (length) and 'pix' are not convertible

Screenshots

No response

System Details

Installation method

git checkout

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