-
Notifications
You must be signed in to change notification settings - Fork 106
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
Improvement to spatial flexure #1870
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.
Thanks @debora-pe - Overall this looks good, but it will be good to make sure the dev suite passes. Also, it would be good to check what happens when the slit edges are quite diagonal. I also have a few general questions:
(1) Could you mention the reason why this new algorithm is required?
(2) My main concern is that the spectral smashing might smear out the slit edges. Is there are reason that the ross-correlation needs to be done in 1D instead of 2D (along one dimension)?
(3) I quite like that the cross-correlation is done on the sobel images with this algorithm. Would it also work to use the old algorithm, but instead use the sobel edges? This would avoid the smashing.
pypeit/core/flexure.py
Outdated
sci_sobel, _ = trace.detect_slit_edges(_sciimg, bpm=bpm) | ||
slits_sobel, _ = trace.detect_slit_edges(slitmask, bpm=bpm) | ||
# collapse both sobel images along the spectral direction | ||
sci_smash, _, _ = sigma_clipped_stats(sci_sobel, axis=0, mask=bpm) |
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.
Does this work well in cases where the slit edges are significantly tilted wrt detector columns?
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.
Yes, it does seems to work well. I was worried too at first, but if the slits edges are tilted for the trace image they will be tilted also for the science image.
pypeit/core/flexure.py
Outdated
sci_smash, _, _ = sigma_clipped_stats(sci_sobel, axis=0, mask=bpm) | ||
slits_smash, _, _ = sigma_clipped_stats(slits_sobel, axis=0, mask=bpm) | ||
# remove nan values | ||
sci_smash[np.isnan(sci_smash)] = 0 |
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 it possible to remove the nan values from the 2D image, instead (before smashing)?
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.
The nan values are actually created by sigma_clipped_stats
when encounters the masked values. This is why I added it here.
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, OK... Since the default is cenfunc=median
, could you instead call sigma_clipped_stats
with cenfunc=np.nanmedian
, as this will ignore the masked pixels instead of making them nans. Could this work?
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'm also very surprised that a median operation is still able to correctly determine the flexure when the slits are tilted. Intuitively, this doesn't make sense to me, especially given that a median operation will reject the significant edges that are enhanced by the sobel filter. Do you have any example QA plots to share showing the two 1D inputs (sci_smash
and slits_smash
), as well as the peak of the cross-correlation for your example (LRIS) as well as a tilted spectrograph (either ESI or HIRES)?
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.
Perhaps one possible solution is to consider a row-by-row cross-correlation of the Sobel images (possibly with a median filter applied), and then do the cross-correlation along one axis. Something like this:
corr = signal.fftconvolve(sci, np.fliplr(slits), mode='same', axes=0)
(although, you'd need to check if it's a fliplr
or flipud
, and if the axes
variable is correct... you'd want them both along the spatial direction).
pypeit/core/flexure.py
Outdated
sci_smash[np.isnan(sci_smash)] = 0 | ||
slits_smash[np.isnan(slits_smash)] = 0 | ||
# invert the negative values | ||
sci_smash[sci_smash < 0] *= -1 |
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 needed? I think keeping the left edges and right edges as +ve/-ve might actually help the cross-correlation.
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.
good point
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 tested this a lot and it seems that inverting the negative value is still better than keeping them negative. In many cases it does not make much difference, but in some cases the cross-correlation is better with the inversion.
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 wonder if the median operation is "cancelling" the positive and negative values for slits that are close together? This could explain why making both left and right edges positive helps determine the slit location.
|
||
""" | ||
debug = True if outfile is None else False | ||
# TODO: should we use initial or tweaked slits in this plot? |
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 it's the tweaked slits that you're after. As you noted above on line 85, whatever is used here needs to be consistent with that choice. I think once the tweaked slits are available, they get used.
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.
yes, I believe that is correct.
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.
Well in the code above I'm still using the initial slits (not the tweaked ones) since:
slitmask = slits.slit_img(initial=True, exclude_flag=slits.bitmask.exclude_for_flexure)
It seems to me that the initial ones should work better in the cross-correlation, and therefore this plot should be consistent with that. But I do not want users to get confused when seeing this plot and comparing with what is actually use for reduction, i.e., tweaked slits if tweak_slits=True
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.
a few minor comments from me.
am really happy to see this code advanced.
pypeit/core/flexure.py
Outdated
sci_smash[np.isnan(sci_smash)] = 0 | ||
slits_smash[np.isnan(slits_smash)] = 0 | ||
# invert the negative values | ||
sci_smash[sci_smash < 0] *= -1 |
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.
good point
#tslits_shift = trace_slits.shift_slits(tslits_dict, lag_max) | ||
# Now translate the tilts | ||
# 2D plot | ||
spat_flexure_qa(sciimg, slits, shift, gpm=np.logical_not(bpm), vrange=qa_vrange) |
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.
remove this line? it is duplicated below.
or is the idea to generate 2 PNGs?
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.
No, this one is used only in debug mode and plot in a matplot window the whole image. No PNG is created. I uses the same script, but it does slightly different things.
|
||
""" | ||
debug = True if outfile is None else False | ||
# TODO: should we use initial or tweaked slits in this plot? |
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.
yes, I believe that is correct.
if np.sum(no_nan) > 0: | ||
if np.any(np.absolute(np.diff(spat_flex[no_nan])) > 0.1): | ||
msgs.warn(f'Spatial flexure is not consistent for all images being combined: {spat_flex}.') | ||
comb_spat_flex = np.round(np.mean(spat_flex[no_nan]),3) |
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 there a preference to rounding to the nearest pixel instead of staying a float
?
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.
It is only rounding to the 3rd decimal. It's still a float. But the mean calculation can make it to be a very long number.
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.
@rcooke-ast @profxj I have addressed your comments.
I have extensively tested these changes with all the Keck LRIS data in the Dev Suite and all looks good. I will anyway re-run the whole Dev Suite.
@rcooke-ast Here are my answers to your questions:
(1) Could you mention the reason why this new algorithm is required?
The old algorithm was not working well. A peak in the cross-correlation was often not found and the spatial flexure was not corrected.
(2) My main concern is that the spectral smashing might smear out the slit edges. Is there are reason that the ross-correlation needs to be done in 1D instead of 2D (along one dimension)?
As I mentioned in another comment, I was little worried too, but after a lot of testing (also with more tilted slit edges) the correction looks reasonable. I thought about a 2D cross-correlation but I wasn't able to find a way to do it without adding a lot of runtime (it's probably me that I don't know how to deal with it)
(3) I quite like that the cross-correlation is done on the sobel images with this algorithm. Would it also work to use the old algorithm, but instead use the sobel edges? This would avoid the smashing.
I have also tested a lot using the sobel edges, and the correction is not consistently good.
pypeit/core/flexure.py
Outdated
sci_sobel, _ = trace.detect_slit_edges(_sciimg, bpm=bpm) | ||
slits_sobel, _ = trace.detect_slit_edges(slitmask, bpm=bpm) | ||
# collapse both sobel images along the spectral direction | ||
sci_smash, _, _ = sigma_clipped_stats(sci_sobel, axis=0, mask=bpm) |
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.
Yes, it does seems to work well. I was worried too at first, but if the slits edges are tilted for the trace image they will be tilted also for the science image.
pypeit/core/flexure.py
Outdated
sci_smash[np.isnan(sci_smash)] = 0 | ||
slits_smash[np.isnan(slits_smash)] = 0 | ||
# invert the negative values | ||
sci_smash[sci_smash < 0] *= -1 |
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 tested this a lot and it seems that inverting the negative value is still better than keeping them negative. In many cases it does not make much difference, but in some cases the cross-correlation is better with the inversion.
#tslits_shift = trace_slits.shift_slits(tslits_dict, lag_max) | ||
# Now translate the tilts | ||
# 2D plot | ||
spat_flexure_qa(sciimg, slits, shift, gpm=np.logical_not(bpm), vrange=qa_vrange) |
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.
No, this one is used only in debug mode and plot in a matplot window the whole image. No PNG is created. I uses the same script, but it does slightly different things.
|
||
""" | ||
debug = True if outfile is None else False | ||
# TODO: should we use initial or tweaked slits in this plot? |
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.
Well in the code above I'm still using the initial slits (not the tweaked ones) since:
slitmask = slits.slit_img(initial=True, exclude_flag=slits.bitmask.exclude_for_flexure)
It seems to me that the initial ones should work better in the cross-correlation, and therefore this plot should be consistent with that. But I do not want users to get confused when seeing this plot and comparing with what is actually use for reduction, i.e., tweaked slits if tweak_slits=True
if np.sum(no_nan) > 0: | ||
if np.any(np.absolute(np.diff(spat_flex[no_nan])) > 0.1): | ||
msgs.warn(f'Spatial flexure is not consistent for all images being combined: {spat_flex}.') | ||
comb_spat_flex = np.round(np.mean(spat_flex[no_nan]),3) |
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.
It is only rounding to the 3rd decimal. It's still a float. But the mean calculation can make it to be a very long number.
pypeit/core/flexure.py
Outdated
sci_smash, _, _ = sigma_clipped_stats(sci_sobel, axis=0, mask=bpm) | ||
slits_smash, _, _ = sigma_clipped_stats(slits_sobel, axis=0, mask=bpm) | ||
# remove nan values | ||
sci_smash[np.isnan(sci_smash)] = 0 |
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.
The nan values are actually created by sigma_clipped_stats
when encounters the masked values. This is why I added it here.
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.
Thanks for the responses @debora-pe! I think it would be helpful to see some example plots of the two signals that you are correlating, for examples with tilted and non-tilted slits.
I always thought the flexure was done by correlating the slits and science frame along the spatial direction, but this seems to never have been the case. I can see that your algorithm is an improvement over what was previously in the code, but I've also proposed another possible solution (correlating 2D images along one direction). I think this would work best, particularly if we used the sobel filtering (without converting -ve numbers to +ve numbers). What do you think?
pypeit/core/flexure.py
Outdated
sci_smash, _, _ = sigma_clipped_stats(sci_sobel, axis=0, mask=bpm) | ||
slits_smash, _, _ = sigma_clipped_stats(slits_sobel, axis=0, mask=bpm) | ||
# remove nan values | ||
sci_smash[np.isnan(sci_smash)] = 0 |
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, OK... Since the default is cenfunc=median
, could you instead call sigma_clipped_stats
with cenfunc=np.nanmedian
, as this will ignore the masked pixels instead of making them nans. Could this work?
pypeit/core/flexure.py
Outdated
sci_smash, _, _ = sigma_clipped_stats(sci_sobel, axis=0, mask=bpm) | ||
slits_smash, _, _ = sigma_clipped_stats(slits_sobel, axis=0, mask=bpm) | ||
# remove nan values | ||
sci_smash[np.isnan(sci_smash)] = 0 |
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'm also very surprised that a median operation is still able to correctly determine the flexure when the slits are tilted. Intuitively, this doesn't make sense to me, especially given that a median operation will reject the significant edges that are enhanced by the sobel filter. Do you have any example QA plots to share showing the two 1D inputs (sci_smash
and slits_smash
), as well as the peak of the cross-correlation for your example (LRIS) as well as a tilted spectrograph (either ESI or HIRES)?
pypeit/core/flexure.py
Outdated
sci_smash[np.isnan(sci_smash)] = 0 | ||
slits_smash[np.isnan(slits_smash)] = 0 | ||
# invert the negative values | ||
sci_smash[sci_smash < 0] *= -1 |
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 wonder if the median operation is "cancelling" the positive and negative values for slits that are close together? This could explain why making both left and right edges positive helps determine the slit location.
pypeit/core/flexure.py
Outdated
sci_smash, _, _ = sigma_clipped_stats(sci_sobel, axis=0, mask=bpm) | ||
slits_smash, _, _ = sigma_clipped_stats(slits_sobel, axis=0, mask=bpm) | ||
# remove nan values | ||
sci_smash[np.isnan(sci_smash)] = 0 |
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.
Perhaps one possible solution is to consider a row-by-row cross-correlation of the Sobel images (possibly with a median filter applied), and then do the cross-correlation along one axis. Something like this:
corr = signal.fftconvolve(sci, np.fliplr(slits), mode='same', axes=0)
(although, you'd need to check if it's a fliplr
or flipud
, and if the axes
variable is correct... you'd want them both along the spatial direction).
Allright @rcooke-ast . Thank you for pushing back on this. I have extensively tests several methods and indeed using a row-by-row cross-correlation works better for very curved slits (like ESI). Bright objects can affect the cross-correlation a lot, so I have added a way to approximately mask the object. Also, I found that the cross-correlation works better with the edge image generated from the sobel image, so I am using that. Let me know what you think. @profxj , if you can, take a look as well, since I have made substantial changes. I'll run the full Dev-Suite as well. |
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.
Looks good to me, thanks @debora-pe! Just two minor comments from me, and you have my 👍 to merge when the dev suite passes.
pypeit/core/flexure.py
Outdated
# mask (as much as possible) the objects on the slits to help the cross-correlation | ||
# need to copy the bpm to avoid changing the input bpm | ||
_bpm = np.zeros_like(_sciimg, dtype=int) if bpm is None else copy.deepcopy(bpm) | ||
for i in range(slits.left_init.shape[1]): |
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.
slits.nslits
might be a little clearer
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.
done
pypeit/core/flexure.py
Outdated
# need to copy the bpm to avoid changing the input bpm | ||
_bpm = np.zeros_like(_sciimg, dtype=int) if bpm is None else copy.deepcopy(bpm) | ||
for i in range(slits.left_init.shape[1]): | ||
left_edge = slits.left_init[:, i].astype(int) |
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.
Not a big deal, but should we do:
np.round(slits.left_init[:, i]).astype(int)
and likewise for right_edge
?
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.
done
…nto spatflex_improve
Finally I was able to run the Dev Suite (my computer was annoying!). All good. The only failure is the usual
|
This work makes improvements to the spatial flexure calculation and add a QA plot to check the flexure.
Documentation was also added.
It addresses issue #1843
Dev-Suite PR pypeit/PypeIt-development-suite#339