CoAdd2D now correctly uses manually extracted objects for offsets/weights#2092
CoAdd2D now correctly uses manually extracted objects for offsets/weights#2092tbowers7 wants to merge 16 commits intopypeit:developfrom
Conversation
In the Coadd2d process, the input images are rebinned and an attempt is made to refind objects. If the coadd is being done with very low-signal objects, it should be possible to manually extract objects at pre-defined locations and bypass the automated object finding. This is the first step in this direction. modified: pypeit/coadd2d.py modified: pypeit/core/coadd.py modified: pypeit/flatfield.py modified: pypeit/par/parset.py modified: pypeit/par/pypeitpar.py
For code development, add TEST logging level, between INFO and WARNING.
The combination of manual extraction of individual frames and use of the [coadd2d][user_obj_ids] parameter now correctly sets the offets and weights for the desired objects. This allows for manually extracted objects to be correctly coadded even when the object drifts along the slit during a series of exposures. modified: pypeit/coadd2d.py modified: pypeit/core/coadd.py modified: pypeit/core/findobj_skymask.py modified: pypeit/manual_extract.py
modified: pypeit/core/coadd.py modified: pypeit/logger.py
modified: pypeit/core/coadd.py
|
Tests pass against the modified DevSuite (pypeit/PypeIt-development-suite#397) -- |
5f9e8ef to
5feba82
Compare
5feba82 to
ae66211
Compare
kbwestfall
left a comment
There was a problem hiding this comment.
Thanks @tbowers7 ! I appreciate all the docs and the code clean-up! I have some rather minor comments, but I'm approving now.
I didn't realize we were doing the parameter copying. I think we should reconsider the approach, but that's outside the scope of this PR, I think. Let's create some GitHub issues to track this though.
|
|
||
| # Make changes to parset specific to 2d coadds | ||
| parcopy = copy.deepcopy(self.par) | ||
| parcopy = self.par.copy() |
There was a problem hiding this comment.
Ah, I didn't realize that there was this parcopy in the code...
I think we need a different approach here, but I expect that is outside the scope of this PR.
| sci_list_rebin, var_list_rebin, norm_rebin_stack, nsmp_rebin_stack, obj_list_rebin \ | ||
| = rebin2d(wave_bins, dspat_bins, waveimg_stack, dspat_stack, thismask_stack, | ||
| inmask_stack, sci_list, var_list) | ||
| inmask_stack, sci_list, var_list, obj_list=None) |
There was a problem hiding this comment.
Just checking: Is obj_list always None here?
There was a problem hiding this comment.
Yes. The need for an obj_list when rebinning is only needed for computing the offsets between inoput frames. This call to rebin2d is the final coaddition computation, and so the offsets would have already been computed.
|
|
||
| # Bilinear interpolation (matrix form) | ||
| spec_val = wi @ patch_wave @ wj | ||
| spat_val = wi @ patch_spat @ wj |
There was a problem hiding this comment.
Is there a numpy or scipy function that does this?
There was a problem hiding this comment.
Yes, scipy.interpolate.interpn. Changed to use the library function.
Still need to flesh out ``CoAdd2dStack`` attributes.
e56815d to
4f33c5a
Compare
|
These last few commits were related to docstrings and the rendered documentation -- no additional changes to the operational pipeline were made. |
Biggest change was in how the `@dataclass` is rendered... needed to do some manual adjusting of the auto-generated api/*.rst files for modules in which @DataClass object exist. This is encapsulated in the new script patch_apidoc_for_dataclasses.py. At the moment, there is just the one in coadd2d.py, but we may expand on this in a campaign to change odd dictionaries and numpy arrays to dataclasses.
debora-pe
left a comment
There was a problem hiding this comment.
Thanks for this @tbowers7. In general, it looks good, but I found a little bug (not introduced here!), which manifests itself with this new changes. Please, see my comments and let's see if @kbwestfall has some strong opinion on this. Thanks!
| # Treat the object finding as a manual process | ||
| manual_obj = ManualExtractionObj.by_fitstbl_input( | ||
| self.spec2d[iexp], | ||
| f"{detnum}:{spat}:{spec}:{self.par['reduce']['findobj']['find_fwhm']}", |
There was a problem hiding this comment.
I found a problem here in case of mosaic. Because of how det otype is defined in the DetectorContainer, self.coadd2d_stack.detectors[iexp].det for a mosaic is something like (np.int64(4), np.int64(8)), which in the manual_obj inp above looks like "(np.int64(4), np.int64(8)):17.170004906250597:4105.93145801127:7.0".
I found that changing det otype in the DetectorContainer from otype=(int, np.integer) to just otype=int will fix this, but I would like to know from @kbwestfall if he thinks it's a correct change.
There was a problem hiding this comment.
My sense is that shifting towards python-native types is the correct way to go here. I will make the otype=int change in DetectorContainer and re-run the DevSuite to make sure that doesn't cause other issues elsewhere. In the meantime, we can wait for @kbwestfall to comment on whether it's the correct change.
There was a problem hiding this comment.
@debora-pe In the DevSuite, the Keck/DEIMOS coadd2d run failed after I changed the DetectorContainer otype. I'm investigating now...
| # The user passed in user_obj_ids that we will use these for the brighest object to | ||
| # be optionally used for offsets and weights. | ||
| if self.par['coadd2d']['weights'] != 'auto': | ||
| # TODO: Is this strictly correct? TPEB 2026-03-18 |
There was a problem hiding this comment.
I think this should be correct, but not only for weights but also for offsets.
There was a problem hiding this comment.
Added offsets to the check.
There was a problem hiding this comment.
@debora-pe : Actually, should not add offsets to the check. For instance, in Keck/DEIMOS 830G_M_9000_dither in the DevSuite, the .coadd2d file has:
[coadd2d]
offsets = maskdef_offsets
weights = auto
user_obj_ids = 1303, 1305
manual = (4,8):1395.4:609.9:4.2:2;(4,8):495.:5900.:4.2:2
This caused the failure of the Keck/DEIMOS coadd2d test in the DevSuite.
Expanding check on "auto" for using "user_obj_ids", and changing `DetectorContainer`'s `det` `otype`. modified: pypeit/coadd2d.py modified: pypeit/images/detector_container.py
|
Re-running DevSuite tests (job Results ( Need to investigate failed Keck/DEIMOS 🤦 Hadn't kept the DevSuite repo (pypeit/PypeIt-development-suite#397) up to date... trying again (job |
|
Tests pass (when done against the correct DevSuite -- This is with the change to @debora-pe , do you have a suggestion for a DevSuite UNIT test that would check on the format of the manual_obj |
It may need to be more like a |
Background
This PR came about from a bug discovered while coadding LDT/DeVeny data on a night with intermittent high clouds. As a result, the S/N of the target from frame to frame varied, sometimes falling below the automatic object-finding threshold. Furthermore, these observations were manually guided, and the object drifted spatially along the slit over the course of the sequence of exposures. Below, I outline the failure mode, the principal change, and new workflow for these types of sequences.
An example set of these data are now encapsulated in the DevSuite (through a companion PR -- see below) to test the fix included in this PR.
Failure Mode
When initially reducing this sequence with PypeIt, several frames failed to find the object at the required S/N ratio as specified in
[reduce] [[findobj]] snr_thresh. For several reasons, it was most expedient to identify the object in thespec2dframe and run a manual extraction on the object.In the process of performing the 2D coaddition, however, it became apparent that the
[coadd2d] offsets = autoparameter -- default value, and needed because the object drifted spatially during the sequence -- was attempting to re-find all objects in the input frames regardless of the existence of objects recorded in thespec1dfiles. Where this became a problem was when one of the input frames did not have any objects detected above the specifiedsnr_thresh, at which point the coaddition crashed with an error. What indicated a deeper bug was when[coadd2d] user_obj_ids = NNN, NNN, NNN...was supplied in the.coadd2dfile to indicate theSPATcode of the desired object in each of the input frames -- the code still proceeded to perform an object detection de nouveau and crashed in the same manner.The failure occurs only when
[coadd2d] offsets = autobecause of the need to compute spatial offsets between the input frames for the purposes of the coaddition. The presumed thought process behind the original code was:Principal Change
The LDT/DeVeny data (longslit, ~2.5') of solar system bodies (the basis for this analysis) shows various field stars crossing the slit as the telescope tracks the object of interest. While the first point above is still valid, the others need revisiting. The third point is false for this particular analysis, as solar system bodies move against the background of stars and different objects will move across the slit during different input frames. For the fourth point, nights with intermittent cirrus can cause the S/N of the object to vary significantly from frame to frame. And, while a simple re-find is easy, doing the proper mathematical transform between input and rebinned image can allow the code to follow the location of manually identified objects in the rebinned image.
To address these changes in thought process, this PR principally relies on the user to input the
SPATcode of the identified object in each of the input frames via[coadd2d] user_obj_ids = NNN, NNN, NNN.... When theuser_obj_idsparameter is specified, the location of the object in the input image is transformed to the coordinate system of the rebinned frame, and a manual extraction of performed at the location of the object in the rebinned image for offset computation only. If one of the input frames has low S/N for the object of interest, a manual extraction will need to have been performed already on that frame, because the new code relies on theSpecObjdatamodel information to retrieve the fractional spatial pixel location of the trace.Once the offsets are computed, the coaddition proceeds as usual with no further changes.
New Workflow
For cases where the object moves spatially from frame to frame, does not have consistent S/N from frame to frame, and may have other, brighter objects along the slit, a slightly adjusted workflow will be required for 2D coaddition.
Science/spec1d*.txtfiles orpypeit_show_2dspec, identify the (integer)SPATcodes of the object in each of the input frames. Include these as a comma-separated list in the[coadd2d] user_obj_idsparamerter.[coadd2d] offsets = auto.snr_threshas needed to ensure the coadded object is found in the final image.What This PR Does NOT Do
It should be noted that this PR includes changes to the
MultiSlitversion ofCoAdd2DONLY. The echelle version has not been altered. This PR only affects the computation of spatial offsets of the input frames with respect to each other, and does not change the process of computing the final coaddition. The only way to get around the automated refinding of objects in each input frame is through the use of[coadd2d] user_obj_ids-- there is no mechanism in this PR to use the the previously generatedSpecObjdatamodel without the user input of specificSPATcodes.Main PR Content
The bulk of the substantive changes in this PR are in
pypeit.core.coadd,pypeit.coadd2d.In the former, the rebinning (and rectification) of individual frames onto the output CoAdd grid has been enhanced by also computing the rebinned location of pixels from the original image. In particular, this is needed for mapping where manually extracted objects lie in the rebinned frame for the purpose of computing offsets between frames for alignment ahead of the actual coaddition. The docstring for
rebin2dhas been expanded and typing has been introduced. A new function,_map_pixel_rebin(), has been added to compute where pixels locations in the original image end up in the rebinned image. This function uses a bilinear interpolation scheme to determine output fractional pixel location for input fractional positions.In the latter, the function change is the construction of a manual-extraction dictionary within the
compute_offsets()method based on input user-specific objects. If the user specifies[coadd2d] user_obj_ids = <int>, <int>, <int>,...objects (theSPATcode for objects on a given slit), the offset-computation (and offset-computation ONLY) will now assume the specified object is to be manually extracted and sends the appropriate information tofindobj_skymask.objs_in_slit().The functions
handle_reference_obj(),compute_offsets(), andcompute_weights()appear to have more changes than they actually do because I addedreturnstatements early in each method to avoid having most of the code being inside singleifstatements (unindention).Ancillary Changes / Collateral Damage
In a couple of places, dictionaries were replaced with either
@dataclassobjects or numpy arrays for clarity.In particular, the
load_coadd2d_stacks()method inpypeit.coadd2dnow returns a@dataclassobject rather than a dictionary, and downstream objects were renamed and changed to attribute-style references.In
pypeit.core.findobj_skymask, an INFO logging statement was changed from SPEC to SPAT value to indicate which object was being discussed.To make things clearer,
ParSetnow has a.copy()method that returns acopy.deepcopy()of itself. This helps to clean up other calling sites in the code (namely inpypeit.coadd2dandpypeit.flatfield).In
pypeit.manual_extract, an overwrought use of a dictionary plus lists was changed to use the desired outputnumpyarrays for internal clarity without changing anything up- or downstream.Has a companion DevSuite PR pypeit/PypeIt-development-suite#397 to test the changes made here using LDT/DeVeny data. The DV1 setup has been replaced with frames from this bug to test the updates to the 2D coaddition.
Testing
DevSuite testing started; job
tbowers-coadd2d-mar25b-565pt.