-
Notifications
You must be signed in to change notification settings - Fork 18
feat: move slow jumps and jumpfix to so3g #1081
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've looked into the changes. I have only two small comments for my ease of understanding, otherwise it looks good to me.
sotodlib/tod_ops/jumps.py
Outdated
if isinstance(jumps, np.ndarray): | ||
jumps = RangesMatrix.from_mask(np.atleast_2d(jumps)) | ||
elif isinstance(jumps, Ranges): | ||
jumps = RangesMatrix.from_mask(np.atleast_2d(jumps.mask())) | ||
if not isinstance(jumps, RangesMatrix): | ||
raise TypeError("jumps not RangesMatrix or convertable to RangesMatrix") | ||
jumps = cast(RangesMatrix, jumps) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems redundant to me, but it probably isn't. Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly that was just to make my typechecker shut up...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough!
@@ -310,6 +311,10 @@ def test_jumpfinder(self): | |||
heights = heights[heights.nonzero()].ravel() | |||
self.assertTrue(np.all(np.abs(np.array([10, -13, -8]) - np.round(heights)) < 3)) | |||
|
|||
# Check fixing | |||
ptp_fix = np.ptp(fixed.ravel()[~jumps.buffer(10).mask().ravel()]) | |||
self.assertTrue(ptp_fix < 1.1*ptp_orig) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really checking the jump fixing? It seems to me this is just checking that the fixed matrix is not messed up in the places where there were no jumps in the first place...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah so the jump fixing assumes everything within the jump range is the jump so you end up with an offset still inside the jump range (which we ignore since we gapfill those ranges anyways).
The ptp checks if we fixed because after the jump there is a DC offset that will be gone in the fixed TOD.
See below for an example (blue is before fixing, orange after):
If we wanted to not have the errors within the jump ranges in the fixed TOD you just need to lower the buffer size for the ranges matrix (but then your jump could be not in the range). The current range is set by the window size used for jump finding to give a reasonable margin of error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, thanks for the visual explanation, the ptp matter is clear now. Just for my understanding: is jumps.buffer(10).mask()
flagging the jump regions plus a buffer region covering the 10 samples before and the 10 samples after the respective jump region?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, upped the buffer in the test to be conservative so that things never fail in CI. But also I set the PRNG key so I think that is unneeded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks fine other than the failing checks.
simonsobs/so3g#196 is now merged so this should be good to go very soon |
sotodlib/tod_ops/jumps.py
Outdated
from scipy.sparse import csr_array | ||
from skimage.restoration import denoise_tv_chambolle | ||
from so3g import ( | ||
matched_jumps, | ||
matched_jumps64, | ||
block_minmax, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One way to create some backwards-compatibility is to not import these by name. Just import so3g. Then the code will crash when you try to use those functions, rather than when you try to import them.
As written now, everyone's installation will break, if they try to use anything from sotodlib.tod_ops
, unless they've updated so3g.
7aa611f
to
c6dc69c
Compare
Now that the new so3g is out can someone approve this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor comments ... looking forward to seeing this out in the wild.
sotodlib/tod_ops/jumps.py
Outdated
from numpy.typing import NDArray | ||
from pixell.utils import block_expand, block_reduce, moveaxis | ||
from pixell.utils import moveaxis |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace the one remaining moveaxis with np.moveaxis.
sotodlib/tod_ops/jumps.py
Outdated
x_fixed = x | ||
if not inplace: | ||
x_fixed = x.copy() | ||
orig_shape = x.shape | ||
x_fixed = np.atleast_2d(x_fixed) | ||
x_fixed = np.ascontiguousarray(x_fixed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To reduce unnecessary reallocations, make use of np.asarray
. To do these two lines, for example, you can just: x_fixed = np.asarray(x, order='C', copy=True)
sotodlib/tod_ops/jumps.py
Outdated
heights = heights.astype(x.dtype) | ||
heights = np.ascontiguousarray(heights) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# This will only copy if needed for dtype/order.
heights = np.asarray(heights, dtype=x.dtype, order='C')
sotodlib/tod_ops/jumps.py
Outdated
|
||
orig_shape = x.shape | ||
x = np.atleast_2d(x) | ||
x = np.ascontiguousarray(x) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If user requested inplace, and x is not contiguous, then the change won't be in-place. So you need to check for that.
For example
x = np.asarray(x, order='C', copy=(False if inplace else None))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really want to raise a value error if inplace is requested but the array is not contiguous?
I could instead raise a warning, make a copy, and then copy back into the original x
later. But that may be babying the user too much.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually assuming we want to raise the error in that case is the most correct block something like:
x_fixed = np.asarray(np.atleast_2d(x), order="C", copy=(False if inplace else None))
That way the cases are as follows:
- in place and already contiguous ->
x_fixed
just referencesx
- in place and not contiguous ->
ValueError
- no in place and already contiguous ->
x_fixed
is a copy ofx
- no in place and not contiguous ->
x_fixed
is a contiguous copy ofx
Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
W.r.t. "no in place and already contiguous -> x_fixed is a copy of x" -- actually not true; np.asarray(... copy=None)
will not copy if it doesn't need to.
Another way to approach this, to minimize copies and simplify logic:
- Apply all the conversions you need to get
x_fixed
, minimizing copies where possible. - Check whether you ended up making a copy, or not --
fixed_is_a_copy = np.may_share_memory(x, x_fixed)
- Take different actions depending on
(fixed_is_a_copy, inplace)
.
I'm ok with your suggestion of making a copy and then updating x at the end.
The reason to ValueError would be that "inplace" is sometimes used not just to compress code, but to minimize copying of the data -- it's sort of implied that doing things inplace will use less RAM, less time. But I think printing a warning is sufficient to help super-optimizers tweak things up.
Oh, darn... asarray(copy=...) is numpy>=2 :( |
Tests failing with I'll redo some logic to sidestep this... but hopefully in the future we can switch to it |
24bd10f
to
b007892
Compare
b007892
to
56dd87d
Compare
...the problem with devving on both my laptop and site computing is sometimes I don't push all my fixes. |
Relies on simonsobs/so3g#196
This is a direct rewrite for the most part, no algorithmic changes.