Skip to content

Masked frames and SkyCoord - #17016

Merged
mhvk merged 15 commits into
astropy:mainfrom
mhvk:masked-frames
Oct 26, 2024
Merged

mhvk merged 15 commits into
astropy:mainfrom
mhvk:masked-frames

Conversation

@mhvk

@mhvk mhvk commented Sep 15, 2024 •

Copy link
Copy Markdown
Contributor

This pull request is the follow-up to #16845, using the mask on representations in SkyCoord and coordinate frames. It also includes brief docs and a what's-new entry.

Notes:

  • Based on Allow representations to be masked, with possibly different masks for the components #16845 (hence opened as draft for now), but feel free to just look at the whole here;
  • Because the coordinates API is shown without inherited members, I cannot link to the new .mask property and other features in the what's-new. I tried just adding :inherited-members: to ref_api.py, but that leads to large numbers of failures that seem out of scope of this PR.
  • So far, I've stuck to the standard Masked approach, that masks just get propagated. For some methods, like apply_space_motion, as well as for coordinate transformations, it might make more sense to treat masked values truly as missing ones, so that, e.g., a masked distance is taken to be the same as if the coordinate had UnitSpherical representation. The question then is whether that is in fact what the user wants, i.e., to what extent one should refuse the temptation to guess...
  • By checking this box, the PR author has requested that maintainers do NOT use the "Squash and Merge" button. Maintainers should respect this when possible; however, the final decision is at the discretion of the maintainer that merges the PR.

@mhvk mhvk added this to the v7.0.0 milestone Sep 15, 2024
@github-actions github-actions Bot added the Docs label Sep 15, 2024
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for rebase and squash.
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the bot.
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  • Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) before merge.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Thank you for your draft pull request! Do you know that you can use [ci skip] or [skip ci] in your commit messages to skip running continuous integration tests until you are ready?

@mhvk

mhvk commented Oct 10, 2024

Copy link
Copy Markdown
Contributor Author

I'm taking this out of draft given the upcoming feature-freeze deadline -- if we miss that, masked SkyCoord will not be possible until astropy 8.0.

I think this is complete if somewhat minimal, with the main question whether one should go beyond simply propagating masks (as done here). E.g., what should expected behaviour be for coordinate conversions and methods like apply_space_motion for spherical representations with some masked distances, PMs, and radial velocities (see also on top).

My own 2¢ is that we should simply go with the minimal implementation I have, mark coordinate masks as experimental, and allow masks not to propagate in some specific cases later.

cc @taldcroft, @StuartLittlefair, @adrn, @eerovaher, @nstarman

@mhvk
mhvk marked this pull request as ready for review October 10, 2024 20:57
@nstarman

Copy link
Copy Markdown
Member

I like the look of this a lot, but I'm not sure about the special treatment of spherical coordinates to disregard the distance. A few things: 1. If a user passes a masked distance and unmasked angles I think they would be very surprised to not find that in the total mask. 2. I think if a user wants to mark a distance as truly missing they should use Nan. Which of course can be combined with a mask, but these have different meanings, right?

@mhvk

mhvk commented Oct 11, 2024

Copy link
Copy Markdown
Contributor Author

I like the look of this a lot, but I'm not sure about the special treatment of spherical coordinates to disregard the distance.

Thanks for bringing that up! It is the thing I have most struggled with, what exactly to do with missing distances, as well as incomplete differentials (proper motions and radial velocities). The basic problem is that a SkyCoord often just has RA, Dec, and for many catalogues there will be more informationfor only a subset of the data. But RA, Dec is enough to know where to point one's telescope!

For the mask, I hope my .get_mask() function provides the flexibility to get what exactly a user wants, but it is then a question of defaults, with two most logical options:

  1. .mask tells whether "one can point a telescope" and default .get_mask() combines the masks of everything.
  2. .mask combines everything, while .get_mask("ra", "dec") (perhaps the default) just does RA, Dec.

Which one would you prefer?

p.s. Part of the reason to have masks is to allow table joins in which one table does not have the given SkyCoord column. For that, of course, just any mask will do.

@nstarman

nstarman commented Oct 11, 2024 •

Copy link
Copy Markdown
Member

Which one would you prefer?

I lean towards the 2nd one. In the lower classes .mask combines all masks. IMO we should keep that pattern and make sure that other common use cases are deeply supported.

@eerovaher

Copy link
Copy Markdown
Member

Have you thought about how SkyCoord.apply_space_motion() should work with masked time? I don't see any tests for that.

@mhvk

mhvk commented Oct 11, 2024

Copy link
Copy Markdown
Contributor Author

Have you thought about how SkyCoord.apply_space_motion() should work with masked time? I don't see any tests for that.

If I'm right, it will "just work", i.e., propagate, but let's add a test!

@mhvk

mhvk commented Oct 11, 2024

Copy link
Copy Markdown
Contributor Author

Test added - it works as expected.

@mhvk

mhvk commented Oct 11, 2024

Copy link
Copy Markdown
Contributor Author

I lean towards the 2nd one. In the lower classes .mask combines all masks. IMO we should keep that pattern and make sure that other common use cases are deeply supported.

Just to be sure: still exclude in the mask possible attributes, right, i.e., effectively just return self.data.mask (would be consistent with Time).

One big advantage of this approach is that it gives a bit more leeway in postponing worrying about how, e.g., apply_space_motion could special-case masked distances and radial velocities. It might then make more sense to simply add an argument to the method about how to deal with masked data (not sure what is best, but we would now have the benefit of not having to decide too much...).

Anyway, I can go either way. Would be good to have input from others, as the question of how to deal with different masks for different coordinate parts is really at the heart of it all.

@nstarman

nstarman commented Oct 12, 2024 •

Copy link
Copy Markdown
Member

Just to be sure: still exclude in the mask possible attributes, right, i.e., effectively just return self.data.mask (would be consistent with Time).

That's an interesting Q. Have to think on that more. I would say that if we're marking this as an experimental feature then yes, for now. We can revisit the question of attributes after v7.

It might then make more sense to simply add an argument to the method about how to deal with masked data (not sure what is best, but we would now have the benefit of not having to decide too much...).

Agreed, if I'm interpreting this right and you mean figuring out the added arguments can happen post v7.
The alternative to added args is a context manager approach, which might also be good

e.g.

with mask_propagator(mode="on-sky"):
    ...

But can we decide this for v7.1?

@eerovaher eerovaher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I lean towards the 2nd one. In the lower classes .mask combines all masks. IMO we should keep that pattern and make sure that other common use cases are deeply supported.

Just to be sure: still exclude in the mask possible attributes, right, i.e., effectively just return self.data.mask (would be consistent with Time).

Consistency is good!

Comment thread astropy/coordinates/baseframe.py Outdated
return out


class CoordinateSharedMethods:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need this class? Why can't we implement the methods here on BaseCoordinateFrame and make them dynamically available on SkyCoord through __getattr__()? I wouldn't normally suggest code that is this dynamic, but SkyCoord is designed to be like that so we might as well make use of it.

@mhvk mhvk Oct 15, 2024 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The problem is that while just letting __getattr__ take care works for masked and mask, it does not work for .get_mask, since it takes a series of attributes of the class and not all SkyCoord attributes are available on the frame (this is explicitly tested with pressure), i.e., self is required to be the SkyCoord, not its underlying frame.

p.s. Of course, having this shared method mixin will also help remove duplication between the two like for .insert().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

p.p.s. A side benefit of having a mixin class is that, eventually, we can actually have sphinx render the relevant properties and methods (once :inherited-members: works properly).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's the reasoning of having masked and mask here? Is it just to keep the masking-related code together?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, just to keep things together, and with a view of expanding later. But if this is something that will prevent this PR from getting into 7.0, I'm fine with removing this update, and just duplicating SkyCoord.get_mask...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CoordinateSharedMethods looks like a private implementation detail that we should be able to change without notice, but it does implement methods that are clearly a part of the public API. I am very hesitant to introduce such classes to coordinates until the APE about public API has been accepted.

I would implement methods on BaseCoordinateFrame if SkyCoord can use them via __getattr__() because there are several methods that are implemented like that already. For get_mask() it might be best to avoid code duplication by introducing a private _get_mask() that both SkyCoord.get_mask() and BaseCoordinateFrame.get_mask() can call.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, fair point that really it is best not to mix new types of implementation in. Will make the suggested changes.

Comment thread astropy/coordinates/baseframe.py Outdated
Comment on lines +411 to +412
For spherical representations, the mask of the distance is ignored,
i.e., one gets the combined mask of the longitude and latitude.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't like this special casing at all. There are very good reasons for providing convenience methods that combine the masks of longitude- and latitude-like coordinates, but those convenience methods belong in the spherical representation classes and not in the base class. Also, I think we can afford implementing them in v7.1 instead of rushing them right now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK on no special casing (see general comments). But I disagree this should be pushed down to the representations - I'd like those to be ignorant of astronomy! Though once we move the option to special-case to get_mask(), I can see pushing an option to just do angles down to SpecialRepresentation.get_mask().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree. Separation of concerns + fine-grain options in get_mask.

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, @nstarman and @eerovaher. Looks like both of you are pretty clear you want the standard .mask just OR together all the masks in the representation and differential. That certainly is a valid choice, and simpler, so I'll go ahead and change it. Some smaller replies inline.

Comment thread astropy/coordinates/baseframe.py Outdated
return out


class CoordinateSharedMethods:

@mhvk mhvk Oct 15, 2024 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The problem is that while just letting __getattr__ take care works for masked and mask, it does not work for .get_mask, since it takes a series of attributes of the class and not all SkyCoord attributes are available on the frame (this is explicitly tested with pressure), i.e., self is required to be the SkyCoord, not its underlying frame.

p.s. Of course, having this shared method mixin will also help remove duplication between the two like for .insert().

Comment thread astropy/coordinates/baseframe.py Outdated
Comment on lines +411 to +412
For spherical representations, the mask of the distance is ignored,
i.e., one gets the combined mask of the longitude and latitude.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK on no special casing (see general comments). But I disagree this should be pushed down to the representations - I'd like those to be ignorant of astronomy! Though once we move the option to special-case to get_mask(), I can see pushing an option to just do angles down to SpecialRepresentation.get_mask().

@mhvk

mhvk commented Oct 15, 2024

Copy link
Copy Markdown
Contributor Author

OK, now implemented a default mask that combines representation and possible differentials. It makes things quite a bit simpler, so that argues that it was the right change to make.

Check PR changelog error is because this is based on the mask for representations PR, #16845, which is not yet merged.

Comment thread docs/coordinates/index.rst Outdated
[(15.00000668, 3.00000101, 0.1), ( ———, ———, ———)]>

In the last example, you will notice that the angles of the second item have
become masked too. This is because the distance is used in the conversion

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not for this PR, but for frame transforms that are only rotations we could avoid going to the cartesian representation, instead doing change of basis stuff on the rotation matrix.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, indeed, that would be much nicer. I guess that would need for the Spherical case to first transform to UnitSpherical, and then add the distance back in. Actually, I think you already have set this up for Spherical.transform, so it would only be a matter of using it inside transformations.
...
A quick tests shows that indeed spherical.transform(m) properly already propagates the mask - angular coordinates keep the angular mask (I'll push the test to #16845). But transformations bypasses this by first converting to cartesian; that should be solvable, but will need a bit more thought.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wonder if it might be as simple as taking the frame transformation and adding a change of basis on either side to match the input and output representation types. For Cartesian this would end up being the identity matrix.

@mhvk mhvk Oct 16, 2024 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The problematic code is

# Convert the representation and differentials to cartesian without
# having them attached to a frame
rep = data.to_cartesian()
diffs = {
k: diff.represent_as(CartesianDifferential, data)
for k, diff in data.differentials.items()
}
rep = rep.with_differentials(diffs)
# Only do transform if matrix is specified. This is for speed in
# transformations that only specify an offset (e.g., LSR)
if matrix is not None:
# Note: this applies to both representation and differentials
rep = rep.transform(matrix)

I tried a simple removal of the conversion to cartesian, since the matrix multiplication can be handled by the different representation classes internally. Unfortunately, adding the offset right now does require moving to cartesian. Obviously, this is solvable too, but best in a different PR! (EDIT: the simplest solutions would involve going back from Cartesian to Spherical in cases where one does not want that; possibly, the .transform method could be upgraded to also have an optional offset?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree that we shouldn't try to change how transformations work in this pull request, but perhaps it would be better to use a different example in our documentation. For example, when converting from ICRS to GCRS distance determines parallax, so we do expect the on-the-sky coordinates to become masked when the distance is masked.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, sounds like a good idea to change the transformation example -- and maybe mention in the general warning about this being new that sometimes masks may propagate further than expected.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should open an issue so that any users who might want to report this wouldn't have to write one up themselves, but doing that can wait until the feature freeze has happened.

@eerovaher eerovaher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Based on the tests and the documentation the public API seems mostly reasonable. The one question I have is whether the output of get_mask() should be a structured array so that its components could be accessed by the name of the corresponding attribute. This would be especially useful when get_mask() is called without any arguments.

The documentation should be expanded. For example, get_mask() doesn't have any documentation outside its docstring, but it really should.

Comment thread astropy/coordinates/baseframe.py Outdated
return out


class CoordinateSharedMethods:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's the reasoning of having masked and mask here? Is it just to keep the masking-related code together?

Comment thread astropy/coordinates/baseframe.py Outdated

class CoordinateSharedMethods:
@property
def masked(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why don't you annotate at least the functions that are simple to annotate?

Suggested change
def masked(self):
def masked(self) -> bool:

@mhvk mhvk Oct 20, 2024 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I still don't typically think of typing at all, but, yes, I can do that. If we keep the mixin, that actually would be a nice bit of self-contained annotated code (is it ndarray[bool] for .mask?)

EDIT: looking at numpy, I guess it would be np.ndarray[Any, np.dtype[np.bool]]or np.ndarray[tuple[int, ...], np.dtype[np.bool]]. Yikes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With numpy.typing.NDArray the annotation can be simplified to NDArray[np.bool].

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm going to leave this to a typing PR; time is definitely running out.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not allowed to demand that you write the annotations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sorry, this really just a matter of spending time wisely - once a module is typed, I think requiring the rest to remain typed is fine, of course.

Comment thread astropy/coordinates/baseframe.py Outdated
Comment on lines +407 to +414
mask = property(
get_mask,
doc="""The mask associated with these coordinates.

Combines the masks of all components of the underlying representation,
including possible differentials.
""",
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is property not being used as a decorator?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It would be an extra function call (as it would just contain return self.get_mask()).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Mypyc should be able to optimize away such intermediate function calls, but we are still very far from compiling coordinates.

If you think this is code is that performance critical then the implementation makes sense, but do add a comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is just standard use of property - it is certainly not performance critical.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed. This happens in the Python docs https://docs.python.org/3/library/functions.html#property and is reasonable when we want a public getter.

Comment thread docs/whatsnew/7.0.rst Outdated
Like more generally for |Masked| instances, one can access the mask of using
``.mask``, check whether the underlying data are masked using ``masked``,
access underlying unmasked data using ``.unmasked``, and get a new instance
with masked data replaced with a fill value using the ``filled()`` method.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why aren't there any tests for this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because it just uses __setitem__... But fair enough, one should at least have one test!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread docs/whatsnew/7.0.rst Outdated
``.get_mask()`` method. For instance, ``sc.get_mask("ra", "dec")`` would
get the combined masks of just the angular components, which may be useful
if dealing with, e.g., a catalogue of stars where distances are available
only for some of the objects.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It should also be mentioned that the frame attributes can be masked too and accessed with get_mask().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Arguably too much for the what's-new, but would definitely be good to add to the actual docs.

Comment thread astropy/coordinates/baseframe.py Outdated
Comment on lines +381 to +384
*attrs : str
Attributes from which to get the masks to combine. Items can be
dotted, like ``"data.lon", "data.lat"``. By default, get the
combined mask of all components (including from differentials).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The docstring should mention that frame attributes can accessed too, but they are not accessed by default.

Comment thread astropy/coordinates/baseframe.py Outdated
Attributes from which to get the masks to combine. Items can be
dotted, like ``"data.lon", "data.lat"``. By default, get the
combined mask of all components (including from differentials).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is there no Returns section?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oops...

@mhvk

mhvk commented Oct 20, 2024

Copy link
Copy Markdown
Contributor Author

Based on the tests and the documentation the public API seems mostly reasonable. The one question I have is whether the output of get_mask() should be a structured array so that its components could be accessed by the name of the corresponding attribute. This would be especially useful when get_mask() is called without any arguments.

The way this is written, I think not - it is meant to give a array of plain bool back. It would not be too hard to extend (e.g., with a structured=True/False keyword argument), but I'm not sure I see a big benefit over just accessing the relevant masks directly.

@nstarman

nstarman commented Oct 20, 2024 •

Copy link
Copy Markdown
Member

In particular there is very little support for structured arrays outside of numpy. Supporting it as an option is fine, less so as the default.

@mhvk

mhvk commented Oct 26, 2024

Copy link
Copy Markdown
Contributor Author

Let me take this out of the thread, since it is less about .filled() than about __setitem__: basically, as implemented, if one has an unmasked representation (or frame or SkyCoord) and uses sc[...] = other_sc, then if other_sc is masked, sc will become masked too. This in itself seems fairly logical, but it is not how it works for ndarray (where we have no choice: we cannot override __setitem__, and numpy just ignores the mask), nor for Quantity (which by itself does not know about masks, and cannot change its own class to MaskedQuantity anyway).

For Time, we do check whether the value is masked, so it does not become masked either. So, options would seem to be:

  1. Adjust the PR here to ignore the mask for __setitem__ too;
  2. Adjust Time to take the mask into account;
  3. Live with the inconsistency.

I really do not like (3). Maybe the cleanest (as far as possible) is to go with (1) for now - I am traveling next 2 weeks but can probably do a quick bugfix for both Time and coordinates if we want to go with (2) instead.

@mhvk

mhvk commented Oct 26, 2024

Copy link
Copy Markdown
Contributor Author

OK, did the push, and tests pass. Very happy to see that table joining now works properly!

And, just to reiterate it, I'm very much in favour of following this up with adding other methods like fill_attrs or a set_mask or so.

@taldcroft taldcroft left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🎉

@saimn saimn modified the milestones: v7.0.0, v7.1.0 Oct 26, 2024
@mhvk

mhvk commented Oct 26, 2024

Copy link
Copy Markdown
Contributor Author

Thanks, @nstarman, @eerovaher, @taldcroft and @StuartLittlefair - it's going in!

@mhvk
mhvk merged commit 3666de6 into astropy:main Oct 26, 2024
@mhvk
mhvk deleted the masked-frames branch October 26, 2024 20:04
ryantqiu pushed a commit to snorkel-marlin-repos/astropy_astropy_pr_17016_12a61849-70cf-4f5d-a2d2-d3f6cec59d0c that referenced this pull request Oct 1, 2025
Original PR #17016 by mhvk
Original: astropy/astropy#17016
ryantqiu added a commit to snorkel-marlin-repos/astropy_astropy_pr_17016_12a61849-70cf-4f5d-a2d2-d3f6cec59d0c that referenced this pull request Oct 1, 2025
ryantqiu pushed a commit to snorkel-marlin-repos/astropy_astropy_pr_17016_923c4889-e4aa-4f98-a983-b09f7bc974fc that referenced this pull request Oct 2, 2025
Original PR #17016 by mhvk
Original: astropy/astropy#17016
ryantqiu added a commit to snorkel-marlin-repos/astropy_astropy_pr_17016_923c4889-e4aa-4f98-a983-b09f7bc974fc that referenced this pull request Oct 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants