Coverage for metadetect / metadetect.py: 6%
395 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-23 22:27 -0700
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-23 22:27 -0700
1import logging
2import time
4import numpy as np
5import ngmix
6from ngmix.gexceptions import BootPSFFailure
7import esutil as eu
9from . import fitting
10from . import procflags
11from . import shearpos
12from .util import Namer
13from .mfrac import measure_mfrac
14from .fitting import (
15 fit_mbobs_list_wavg,
16 combine_fit_res,
17 fit_mbobs_list_joint,
18 MAX_NUM_SHEAR_BANDS,
19)
21logger = logging.getLogger(__name__)
24def do_metadetect(
25 config, mbobs, rng, shear_band_combs=None,
26 color_key_func=None, color_dep_mbobs=None,
27 det_band_combs=None,
28):
29 """Run metadetect on the multi-band observations.
31 Parameters
32 ----------
33 config: dict
34 Configuration dictionary. Possible entries are
36 metacal
37 weight
38 model
40 mbobs: ngmix.MultiBandObsList
41 We will do detection and measurements on these images
42 rng: numpy.random.RandomState
43 Random number generator
44 shear_band_combs: list of list of int, optional
45 If given, each element of the outer list is a list of indices into mbobs to use
46 for shear measurement. Shear measurements will be made for each element of the
47 outer list. If None, then shear measurements will be made for all entries in
48 mbobs.
49 det_band_combs: list of list of int or str, optional
50 If given, the set of bands to use for detection. The default of None uses all
51 of the bands. If the string "shear_bands" is passed, the code uses the bands
52 used for shear.
53 color_key_func: function, optional
54 If given, a function that computes a color or tuple of colors to key the
55 `color_dep_mbobs` dictionary given an input set of fluxes from the mbobs.
56 color_dep_mbobs: dict of mbobs, optional
57 A dictionary of color-dependently rendered observations of the mbobs for use
58 in color-dependent metadetect.
60 Returns
61 -------
62 res: dict
63 The fitting data keyed on the shear component.
64 """
65 md = Metadetect(
66 config, mbobs, rng,
67 shear_band_combs=shear_band_combs,
68 color_key_func=color_key_func,
69 color_dep_mbobs=color_dep_mbobs,
70 det_band_combs=det_band_combs,
71 )
72 md.go()
73 return md.result
76class Metadetect(dict):
77 """Metadetect fitter for multi-band observations.
79 This class runs the metadetect shear fitting algorithm for observations
80 consisting of one image per band. It can use several different fitters for the
81 actual shape measurement.
83 wmom - Perform a post-PSF weighted moments measurement. The shear measurement
84 is a moment computed from the inverse variance weighted average across
85 the bands.
87 ksigma - Perform a pre-PSF weighted moments measurement. The shear measurement
88 is a moment computed from the inverse variance weighted average across
89 the bands.
91 pgauss - Perform a pre-PSF weighted moments measurement with a Gaussian filter.
92 The shear measurement is a moment computed from the inverse variance
93 weighted average across the bands.
95 am or admom - Use adaptive moments. The shear measurement is compute from fitting
96 adaptive moments on a coadd of the bands used for shear.
98 Parameters
99 ----------
100 config: dict
101 Configuration dictionary. Possible entries are
103 metacal
104 weight
105 model
107 mbobs: ngmix.MultiBandObsList
108 We will do detection and measurements on these images
109 rng: numpy.random.RandomState
110 Random number generator
111 show: bool, optional
112 If True, show the images using descwl_coadd.vis.
113 shear_band_combs: list of list of int, optional
114 If given, each element of the outer list is a list of indices into mbobs to use
115 for shear measurement. Shear measurements will be made for each element of the
116 outer list. If None, then shear measurements will be made for all entries in
117 mbobs.
118 det_band_combs: list of list of int or str, optional
119 If given, the set of bands to use for detection. The default of None uses all
120 of the bands. If the string "shear_bands" is passed, the code uses the bands
121 used for shear.
122 color_key_func: function, optional
123 If given, a function that computes a color or tuple of colors to key the
124 `color_dep_mbobs` dictionary given an input set of fluxes from the mbobs. If
125 this function returns None, the PSF from the original observation is used.
126 color_dep_mbobs: dict of mbobs, optional
127 A dictionary of color-dependently rendered observations of the mbobs for use
128 in color-dependent metadetect.
129 """
130 def __init__(
131 self, config, mbobs, rng, show=False,
132 shear_band_combs=None,
133 color_key_func=None,
134 color_dep_mbobs=None,
135 det_band_combs=None,
136 ):
137 self._show = show
139 self._set_config(config)
140 self.mbobs = mbobs
141 self.nband = len(mbobs)
142 self.rng = rng
144 self.color_key_func = color_key_func
145 self.color_dep_mbobs = color_dep_mbobs
146 if (
147 (color_dep_mbobs is not None and color_key_func is None)
148 or
149 (color_dep_mbobs is None and color_key_func is not None)
150 ):
151 raise RuntimeError(
152 "You must both `color_dep_mbobs` and `color_key_func`!"
153 )
155 self._set_fitter()
157 if shear_band_combs is None:
158 shear_band_combs = [
159 list(range(self.nband)),
160 ]
162 self._shear_band_combs = shear_band_combs
164 if det_band_combs is None:
165 det_band_combs = (
166 [list(range(self.nband))]
167 * len(self._shear_band_combs)
168 )
169 elif det_band_combs == "shear_bands":
170 det_band_combs = self._shear_band_combs
172 self._det_band_combs = det_band_combs
174 def _set_config(self, config):
175 """
176 set the config, dealing with defaults
177 """
179 self.update(config)
180 assert 'metacal' in self, \
181 'metacal setting must be present in config'
182 assert 'meds' in self, \
183 'meds setting must be present in config'
184 self['nodet_flags'] = self.get('nodet_flags', 0)
186 def _get_ormask_and_bmask(self, mbobs):
187 """
188 set the ormask and bmask, ored from all epochs
189 """
191 for band, obslist in enumerate(mbobs):
192 nepoch = len(obslist)
193 assert nepoch == 1, 'expected 1 epoch, got %d' % nepoch
195 obs = obslist[0]
197 if band == 0:
198 ormask = obs.ormask.copy()
199 bmask = obs.bmask.copy()
200 else:
201 ormask |= obs.ormask
202 bmask |= obs.bmask
204 return ormask, bmask
206 def _get_mfrac(self, mbobs):
207 """
208 get the masked fraction image, averaged over all bands
209 """
210 wgts = []
211 mfrac = np.zeros_like(mbobs[0][0].image)
212 for band, obslist in enumerate(mbobs):
213 nepoch = len(obslist)
214 assert nepoch == 1, 'expected 1 epoch, got %d' % nepoch
216 obs = obslist[0]
217 msk = obs.weight > 0
218 if not np.any(msk):
219 wgt = 0
220 else:
221 wgt = np.median(obs.weight[msk])
222 if hasattr(obs, "mfrac"):
223 mfrac += (obs.mfrac * wgt)
224 wgts.append(wgt)
226 if np.sum(wgts) > 0:
227 mfrac = mfrac / np.sum(wgts)
228 else:
229 mfrac[:, :] = 1.0
231 return mfrac
233 def _set_fitter(self):
234 """
235 set the fitter to be used
236 """
238 def _get_fitter(cfg):
239 model = cfg.get('model', 'wmom')
240 symmetrize = cfg.get("symmetrize", True)
242 if "fwhm_smooth" in cfg.get("weight", {}):
243 kwargs = {"fwhm_smooth": cfg["weight"]["fwhm_smooth"]}
244 else:
245 kwargs = {}
247 if model == 'wmom':
248 fitter = ngmix.gaussmom.GaussMom(fwhm=cfg["weight"]["fwhm"])
249 is_wavg = True
250 coadd = False
251 elif model == 'ksigma':
252 fitter = ngmix.prepsfmom.KSigmaMom(
253 fwhm=cfg["weight"]["fwhm"],
254 **kwargs,
255 )
256 is_wavg = True
257 coadd = False
258 elif model == "pgauss":
259 fitter = ngmix.prepsfmom.PGaussMom(
260 fwhm=cfg["weight"]["fwhm"],
261 **kwargs,
262 )
263 is_wavg = True
264 coadd = False
265 elif model in ["admom", "am", "gauss"]:
266 # we pass the name to our codes
267 fitter = model
268 is_wavg = False
270 # we set this defualt
271 # it may be used to set the masked fraction measurement
272 # aperture
273 if "weight" not in cfg:
274 cfg["weight"] = {}
275 if "fwhm" not in cfg["weight"]:
276 cfg["weight"]["fwhm"] = 1.2
278 coadd = cfg.get("coadd", False)
279 else:
280 raise ValueError("bad model: '%s'" % model)
282 if "fwhm_reg" in cfg.get("weight", {}):
283 fwhm_reg = cfg["weight"]["fwhm_reg"]
284 fitter.kind = fitter.kind + "_reg%0.2f" % cfg["weight"]["fwhm_reg"]
285 else:
286 fwhm_reg = 0
288 return (
289 model, fitter, cfg["weight"]["fwhm"], fwhm_reg,
290 is_wavg, symmetrize, coadd,
291 )
293 if "fitters" in self and (
294 "model" in self
295 or "weight" in self
296 or "symmetrize" in self
297 or "coadd" in self
298 ):
299 raise RuntimeError(
300 "You can only specify one of fitters or "
301 "model+weight+symmetrize+coadd!"
302 )
304 if "fitters" in self:
305 fitters = []
306 fwhms = []
307 fwhm_regs = []
308 fitter_is_wavg = []
309 fitter_symmetrize = []
310 fitter_coadd = []
311 for fitter_cfg in self["fitters"]:
312 _, fitter, fwhm, fwhm_reg, is_wavg, symmetrize, coadd = _get_fitter(
313 fitter_cfg
314 )
315 fitters.append(fitter)
316 fwhms.append(fwhm)
317 fwhm_regs.append(fwhm_reg)
318 fitter_is_wavg.append(is_wavg)
319 fitter_symmetrize.append(symmetrize)
320 fitter_coadd.append(coadd)
321 self._fitters = fitters
322 self._fwhms = fwhms
323 self._fwhm_regs = fwhm_regs
324 self._fitter_is_wavg = fitter_is_wavg
325 self._fitter_symmetrize = fitter_symmetrize
326 self._fitter_coadd = fitter_coadd
327 else:
328 _, fitter, fwhm, fwhm_reg, is_wavg, symmetrize, coadd = _get_fitter(self)
329 self._fitters = [fitter]
330 self._fwhms = [fwhm]
331 self._fwhm_regs = [fwhm_reg]
332 self._fitter_is_wavg = [is_wavg]
333 self._fitter_symmetrize = [symmetrize]
334 self._fitter_coadd = [coadd]
336 @property
337 def result(self):
338 """
339 get the result dict, keyed by the metacal type such
340 as 'noshear', '1p', '1m', '2p', '2m'
341 """
342 if not hasattr(self, '_result'):
343 raise RuntimeError('run go() first')
345 return self._result
347 def go(self):
348 """Run metadetect and set the result."""
350 mfrac = self._get_mfrac(self.mbobs)
351 any_all_zero_weight = False
352 any_all_masked = False
353 for obsl in self.mbobs:
354 for obs in obsl:
355 if np.all(obs.weight == 0):
356 any_all_zero_weight = True
358 if np.all((obs.bmask & self['nodet_flags']) != 0):
359 any_all_masked = True
361 # if the there are no pixels with mfrac < 1 or it is all zero weight
362 # or it is all masked, we cannot measure anything so set result to None
363 if (not np.any(mfrac < 1)) or any_all_zero_weight or any_all_masked:
364 self._result = None
365 return
367 # we do metacal on everything so we can get fluxes for non-shear bands later
368 mcal_res = self._get_mbobs_data(None, list(range(self.nband)))["mcal_res"]
370 if mcal_res is None:
371 self._result = None
372 return
374 # past this point, the code should always return a dictionary with the minimal
375 # metacal types
376 # this indicates that a measurement should have been possible
377 # we may find nothing, but that is a different thing
378 all_res = {}
379 for shear_bands, det_bands in zip(
380 self._shear_band_combs, self._det_band_combs
381 ):
382 if self.color_key_func is not None and self.color_dep_mbobs is not None:
383 res = self._go_bands_with_color(shear_bands, mcal_res, det_bands)
384 else:
385 res = self._go_bands(shear_bands, mcal_res, det_bands)
386 if res is not None:
387 for k, v in res.items():
388 if v is None:
389 continue
390 if k not in all_res:
391 all_res[k] = []
392 all_res[k].append(v)
394 for k in all_res:
395 all_res[k] = np.hstack(all_res[k])
397 for mcal_type in self['metacal'].get(
398 "types", ngmix.metacal.METACAL_MINIMAL_TYPES
399 ):
400 if mcal_type not in all_res:
401 # this can happen if we do not detect sources for one of the
402 # metacal images
403 all_res[mcal_type] = None
405 self._result = all_res
407 def _go_bands(self, shear_bands, mcal_res, det_bands):
408 kdata = self._get_mbobs_data(None, shear_bands)
410 _result = {}
411 for shear_str, shear_mbobs in mcal_res.items():
412 cat, mbobs_list = self._do_detect(
413 shear_mbobs,
414 det_bands,
415 )
416 _result[shear_str] = self._measure(
417 mbobs_list=mbobs_list,
418 shear_bands=shear_bands,
419 det_bands=det_bands,
420 cat=cat,
421 shear_str=shear_str,
422 mfrac=kdata["mfrac"],
423 bmask=kdata["bmask"],
424 ormask=kdata["ormask"],
425 psf_stats=kdata["psf_stats"],
426 )
428 return _result
430 def _go_bands_with_color(self, shear_bands, mcal_res, det_bands):
431 from . import detect
433 _result = {}
434 for shear_str, shear_mbobs in mcal_res.items():
435 if not self._fitter_is_wavg[0]:
436 raise RuntimeError(
437 "Color-dependent metadetect can only run if first"
438 " fitter is one of wmom, pgauss, or ksigma!"
439 )
441 # we first detect and get color of each detection
442 cat, mbobs_list = self._do_detect(shear_mbobs, det_bands)
443 nocolor_data = fit_mbobs_list_wavg(
444 mbobs_list=mbobs_list,
445 fitter=self._fitters[0],
446 shear_bands=shear_bands,
447 bmask_flags=self.get("bmask_flags", 0),
448 )
449 if nocolor_data is None:
450 _result[shear_str] = None
451 continue
453 # now we map color to the mbobs for that color
454 n = Namer(self._fitters[0].kind)
455 col = n("band_flux")
456 color_keys = [
457 self.color_key_func(nocolor_data[col][i])
458 for i in range(nocolor_data.shape[0])
459 ]
461 # now we remeasure the object at that mbobs
462 color_data = []
463 for i, color_key in enumerate(color_keys):
464 kdata = self._get_mbobs_data(color_key, shear_bands)
465 if kdata["mcal_res"] is None or kdata["mcal_res"][shear_str] is None:
466 continue
468 _medsifier = detect.CatalogMEDSifier(
469 kdata["mcal_res"][shear_str],
470 cat['x'][i:i+1],
471 cat['y'][i:i+1],
472 cat['box_size'][i:i+1],
473 )
474 mbm = _medsifier.get_multiband_meds()
475 mbobs_list = mbm.get_mbobs_list(
476 weight_type=self["meds"].get("weight_type", "weight"),
477 )
479 _data = self._measure(
480 mbobs_list=mbobs_list,
481 shear_bands=shear_bands,
482 det_bands=det_bands,
483 cat=cat[i:i+1],
484 shear_str=shear_str,
485 mfrac=kdata["mfrac"],
486 bmask=kdata["bmask"],
487 ormask=kdata["ormask"],
488 psf_stats=kdata["psf_stats"],
489 )
490 if _data is not None:
491 color_data.append(_data)
493 if len(color_data) > 0:
494 _result[shear_str] = np.hstack(color_data)
495 else:
496 _result[shear_str] = None
498 return _result
500 def _get_mbobs_data(self, key, shear_bands):
501 logger.info("computing mbobs data: %s %s", key, shear_bands)
503 if key is None:
504 mbobs = self.mbobs
505 else:
506 mbobs = self.color_dep_mbobs[key]
507 # the caching will miss cases where the input mbobs is passed as one
508 # of the color dependent ones as well
509 # thus we test if they are the same object and set the key to None if
510 # this happens
511 if mbobs is self.mbobs:
512 key = None
514 if not hasattr(self, "_mbobs_data_cache"):
515 logger.info("set mbobs data caches")
516 self._mbobs_data_cache = {}
517 self._mcalpsf_data_cache = {}
519 if key not in self._mbobs_data_cache:
520 logger.info("key %s not in mbobs data cache", key)
521 self._mbobs_data_cache[key] = {}
522 self._mcalpsf_data_cache[key] = {}
524 t0 = time.time()
525 try:
526 fitting.fit_all_psfs(mbobs, self.rng)
527 _psf_fit_flags = 0
528 except BootPSFFailure:
529 _psf_fit_flags = procflags.PSF_FAILURE
530 self._mcalpsf_data_cache[key]["psf_fit_flags"] = _psf_fit_flags
531 logger.info("PSF fits took %s seconds", time.time() - t0)
533 mcal_res = self._get_all_metacal(mbobs)
534 self._mcalpsf_data_cache[key]["mcal_res"] = mcal_res
536 sbkey = tuple(sorted(shear_bands))
537 if sbkey not in self._mbobs_data_cache[key]:
538 logger.info("shear_bands key %s not in mbobs data cache", sbkey)
539 self._mbobs_data_cache[key][sbkey] = {}
540 self._mbobs_data_cache[key][sbkey].update(
541 self._mcalpsf_data_cache[key]
542 )
544 _mbobs = ngmix.MultiBandObsList()
545 for band in shear_bands:
546 _mbobs.append(mbobs[band])
547 mfrac = self._get_mfrac(_mbobs)
548 ormask, bmask = self._get_ormask_and_bmask(_mbobs)
549 psf_stats = _get_psf_stats(
550 _mbobs,
551 self._mbobs_data_cache[key][sbkey]["psf_fit_flags"],
552 )
553 self._mbobs_data_cache[key][sbkey]["mfrac"] = mfrac
554 self._mbobs_data_cache[key][sbkey]["bmask"] = bmask
555 self._mbobs_data_cache[key][sbkey]["ormask"] = ormask
556 self._mbobs_data_cache[key][sbkey]["psf_stats"] = psf_stats
558 return self._mbobs_data_cache[key][sbkey]
560 def _measure(
561 self, *, mbobs_list, shear_bands, cat, shear_str, mfrac, bmask,
562 ormask, psf_stats, det_bands,
563 ):
565 t0 = time.time()
566 all_res = []
567 for fitter, fwhm_reg, is_wavg, symm, coadd in zip(
568 self._fitters, self._fwhm_regs,
569 self._fitter_is_wavg, self._fitter_symmetrize,
570 self._fitter_coadd,
571 ):
572 ft0 = time.time()
573 if is_wavg:
574 res = fit_mbobs_list_wavg(
575 mbobs_list=mbobs_list,
576 fitter=fitter,
577 shear_bands=shear_bands,
578 bmask_flags=self.get("bmask_flags", 0),
579 fwhm_reg=fwhm_reg,
580 symmetrize=symm,
581 )
582 else:
583 res = fit_mbobs_list_joint(
584 mbobs_list=mbobs_list,
585 fitter_name=fitter,
586 shear_bands=shear_bands,
587 bmask_flags=self.get("bmask_flags", 0),
588 rng=self.rng,
589 symmetrize=symm,
590 coadd=coadd,
591 )
592 ft0 = time.time() - ft0
593 logger.info(
594 "fitter %s took %s seconds",
595 fitter.kind
596 if hasattr(fitter, "kind")
597 else fitter,
598 ft0,
599 )
600 all_res.append(res)
602 res = combine_fit_res(all_res)
604 if res is not None:
605 res = self._add_positions_and_psf(
606 cat=cat,
607 res=res,
608 shear_str=shear_str,
609 mfrac=mfrac,
610 bmask=bmask,
611 ormask=ormask,
612 psf_stats=psf_stats,
613 det_bands=det_bands,
614 )
615 logger.info("src measurements took %s seconds", time.time() - t0)
617 return res
619 def _add_positions_and_psf(
620 self, *, cat, res, shear_str, mfrac, bmask, ormask, psf_stats, det_bands,
621 ):
622 """
623 add catalog positions to the result
624 """
626 new_dt = [
627 ('sx_row', 'f4'),
628 ('sx_col', 'f4'),
629 ('sx_row_noshear', 'f4'),
630 ('sx_col_noshear', 'f4'),
631 ('ormask', 'i4'),
632 ('mfrac', 'f4'),
633 ('bmask', 'i4'),
634 ('mfrac_img', 'f4'),
635 ('ormask_noshear', 'i4'),
636 ('mfrac_noshear', 'f4'),
637 ('bmask_noshear', 'i4'),
638 ("det_bands", "U%d" % MAX_NUM_SHEAR_BANDS),
639 ]
640 if 'psfrec_flags' not in res.dtype.names:
641 new_dt += [
642 ('psfrec_flags', 'i4'), # psfrec is the original psf
643 ('psfrec_g', 'f8', 2),
644 ('psfrec_T', 'f8'),
645 ]
646 newres = eu.numpy_util.add_fields(
647 res,
648 new_dt,
649 )
651 assert len(det_bands) <= MAX_NUM_SHEAR_BANDS
652 newres["det_bands"] = "".join("%s" % b for b in sorted(det_bands))
654 if 'psfrec_flags' not in res.dtype.names:
655 newres['psfrec_flags'] = procflags.NO_ATTEMPT
657 newres['psfrec_flags'][:] = psf_stats['flags']
658 newres['psfrec_g'][:, 0] = psf_stats['g1']
659 newres['psfrec_g'][:, 1] = psf_stats['g2']
660 newres['psfrec_T'][:] = psf_stats['T']
661 newres['mfrac_img'][:] = np.mean(mfrac)
663 if cat.size > 0:
664 obs = self.mbobs[0][0]
666 newres['sx_col'] = cat['x']
667 newres['sx_row'] = cat['y']
669 rows_noshear, cols_noshear = shearpos.unshear_positions_obs(
670 newres['sx_row'],
671 newres['sx_col'],
672 shear_str,
673 obs, # an example for jacobian and image shape
674 # default is 0.01 but make sure to use the passed in default
675 # if needed
676 step=self['metacal'].get("step", shearpos.DEFAULT_STEP),
677 )
679 newres['sx_row_noshear'] = rows_noshear
680 newres['sx_col_noshear'] = cols_noshear
682 if 'ormask_region' in self and self['ormask_region'] > 1:
683 ormask_region = self['ormask_region']
684 elif 'mask_region' in self and self['mask_region'] > 1:
685 ormask_region = self['mask_region']
686 else:
687 ormask_region = 1
689 if 'mask_region' in self and self['mask_region'] > 1:
690 bmask_region = self['mask_region']
691 else:
692 bmask_region = 1
694 logger.debug(
695 'ormask|bmask region: %s|%s',
696 ormask_region,
697 bmask_region,
698 )
700 newres["ormask"] = _fill_in_mask_col(
701 mask_region=ormask_region,
702 rows=newres['sx_row'],
703 cols=newres['sx_col'],
704 mask=ormask,
705 )
706 newres["ormask_noshear"] = _fill_in_mask_col(
707 mask_region=ormask_region,
708 rows=newres['sx_row_noshear'],
709 cols=newres['sx_col_noshear'],
710 mask=ormask,
711 )
713 newres["bmask"] = _fill_in_mask_col(
714 mask_region=bmask_region,
715 rows=newres['sx_row'],
716 cols=newres['sx_col'],
717 mask=bmask,
718 )
719 newres["bmask_noshear"] = _fill_in_mask_col(
720 mask_region=bmask_region,
721 rows=newres['sx_row_noshear'],
722 cols=newres['sx_col_noshear'],
723 mask=bmask,
724 )
726 if np.any(mfrac > 0):
727 newres["mfrac"] = measure_mfrac(
728 mfrac=mfrac,
729 x=newres["sx_col"],
730 y=newres["sx_row"],
731 box_sizes=cat["box_size"],
732 obs=obs,
733 fwhm=self.get("mfrac_fwhm", self._fwhms[0]),
734 )
736 newres["mfrac_noshear"] = measure_mfrac(
737 mfrac=mfrac,
738 x=newres["sx_col_noshear"],
739 y=newres["sx_row_noshear"],
740 box_sizes=cat["box_size"],
741 obs=obs,
742 fwhm=self.get("mfrac_fwhm", self._fwhms[0]),
743 )
744 else:
745 newres["mfrac"] = 0
746 newres["mfrac_noshear"] = 0
748 return newres
750 def _do_detect(self, mbobs, det_bands):
751 """
752 use a MEDSifier to run detection
753 """
754 from . import detect
756 t0 = time.time()
757 det_mbobs = ngmix.MultiBandObsList()
758 for band in det_bands:
759 det_mbobs.append(mbobs[band])
761 medsifier = detect.MEDSifier(
762 mbobs=det_mbobs,
763 sx_config=self.get('sx', None),
764 meds_config=self['meds'],
765 nodet_flags=self['nodet_flags'],
766 )
768 if self._show:
769 import descwl_coadd.vis
770 descwl_coadd.vis.show_image(medsifier.seg)
772 all_medsifier = detect.CatalogMEDSifier(
773 mbobs,
774 medsifier.cat['x'],
775 medsifier.cat['y'],
776 medsifier.cat['box_size'],
777 seg=medsifier.seg,
778 number=medsifier.cat['number'],
779 )
780 mbm = all_medsifier.get_multiband_meds()
781 mbobs_list = mbm.get_mbobs_list(
782 weight_type=self["meds"].get("weight_type", "weight"),
783 )
784 logger.info("detect took %s seconds", time.time() - t0)
786 return medsifier.cat, mbobs_list
788 def _get_all_metacal(self, mbobs):
789 """
790 get the sheared versions of the observations
791 """
792 t0 = time.time()
793 try:
794 odict = ngmix.metacal.get_all_metacal(
795 mbobs,
796 rng=self.rng,
797 **self['metacal']
798 )
799 except BootPSFFailure:
800 odict = None
801 logger.info("metacal took %s seconds", time.time() - t0)
803 if self._show and odict is not None:
804 import descwl_coadd.vis
806 orig_mbobs = mbobs
808 for mtype, mbobs in odict.items():
809 for band in range(len(mbobs)):
811 obslist = mbobs[band]
812 orig_obslist = orig_mbobs[band]
814 for iobs in range(len(obslist)):
815 obs = obslist[iobs]
816 orig_obs = orig_obslist[iobs]
818 descwl_coadd.vis.show_images(
819 [
820 obs.image,
821 obs.bmask,
822 obs.weight,
823 orig_obs.noise,
824 ],
825 title=mtype,
826 )
828 return odict
831def _get_psf_stats(mbobs, global_flags):
832 if global_flags != 0:
833 flags = procflags.PSF_FAILURE | global_flags
834 g1 = np.nan
835 g2 = np.nan
836 T = np.nan
837 else:
838 try:
839 g1sum = 0.0
840 g2sum = 0.0
841 Tsum = 0.0
842 wsum = 0.0
844 for obslist in mbobs:
845 for obs in obslist:
846 wt = obs.weight.max()
847 res = obs.psf.meta['result']
848 T = res['T']
849 if 'e' in res:
850 g1, g2 = res['e']
851 else:
852 g1, g2 = res['g']
854 g1sum += g1*wt
855 g2sum += g2*wt
856 Tsum += T*wt
857 wsum += wt
859 if wsum <= 0.0:
860 raise BootPSFFailure(
861 'zero weights, could not get mean psf properties'
862 )
863 g1 = g1sum/wsum
864 g2 = g2sum/wsum
865 T = Tsum/wsum
867 flags = 0
869 except BootPSFFailure:
870 flags = procflags.PSF_FAILURE
871 g1 = np.nan
872 g2 = np.nan
873 T = np.nan
875 return {
876 'flags': flags,
877 'g1': g1,
878 'g2': g2,
879 'T': T,
880 }
883def _clip_and_round(vals_in, dim):
884 """
885 clip values and round to nearest integer
886 """
888 vals = vals_in.copy()
890 np.rint(vals, out=vals)
891 vals.clip(min=0, max=dim-1, out=vals)
893 return vals.astype('i4')
896def _fill_in_mask_col(*, mask_region, rows, cols, mask):
897 dims = mask.shape
898 rclip = _clip_and_round(rows, dims[0])
899 cclip = _clip_and_round(cols, dims[1])
901 if mask_region > 1:
902 res = np.zeros_like(rows, dtype=np.int32)
903 for ind in range(rows.size):
904 lr = int(min(
905 dims[0]-1,
906 max(0, rclip[ind] - mask_region)))
907 ur = int(min(
908 dims[0]-1,
909 max(0, rclip[ind] + mask_region)))
911 lc = int(min(
912 dims[1]-1,
913 max(0, cclip[ind] - mask_region)))
914 uc = int(min(
915 dims[1]-1,
916 max(0, cclip[ind] + mask_region)))
918 res[ind] = np.bitwise_or.reduce(
919 mask[lr:ur+1, lc:uc+1],
920 axis=None,
921 )
922 else:
923 res = mask[rclip, cclip].astype(np.int32)
925 return res