198 input_ref_dict = butlerQC.get(inputRefs)
200 isolated_star_cat_handles = input_ref_dict[
"isolated_star_cats"]
201 isolated_star_source_handles = input_ref_dict[
"isolated_star_sources"]
203 isolated_star_cat_handle_dict = {
204 handle.dataId[
"tract"]: handle
for handle
in isolated_star_cat_handles
206 isolated_star_source_handle_dict = {
207 handle.dataId[
"tract"]: handle
for handle
in isolated_star_source_handles
210 if len(isolated_star_cat_handle_dict) != len(isolated_star_source_handle_dict):
211 raise RuntimeError(
"isolated_star_cats and isolate_star_sources must have same length.")
213 for tract
in isolated_star_cat_handle_dict:
214 if tract
not in isolated_star_source_handle_dict:
215 raise RuntimeError(f
"tract {tract} in isolated_star_cats but not isolated_star_sources")
217 if self.config.doReferenceMatches:
220 ref_config = LoadReferenceObjectsConfig()
221 ref_config.filterMap = self.config.fgcmLoadReferenceCatalog.filterMap
222 ref_obj_loader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
223 for ref
in inputRefs.ref_cat],
224 refCats=butlerQC.get(inputRefs.ref_cat),
225 name=self.config.connections.ref_cat,
228 self.makeSubtask(
'fgcmLoadReferenceCatalog',
229 refObjLoader=ref_obj_loader,
230 refCatName=self.config.connections.ref_cat)
236 visit_summary_handle_dict = {handle.dataId[
'visit']: [handle,
None]
for
237 handle
in input_ref_dict[
'visit_summaries']}
239 camera = input_ref_dict[
"camera"]
241 lookup_table_handle = input_ref_dict[
"fgcm_lookup_table"]
245 visit_summary_handle_dict=visit_summary_handle_dict,
246 isolated_star_cat_handle_dict=isolated_star_cat_handle_dict,
247 isolated_star_source_handle_dict=isolated_star_source_handle_dict,
248 lookup_table_handle=lookup_table_handle,
251 butlerQC.put(struct.fgcm_visit_catalog, outputRefs.fgcm_visit_catalog)
252 butlerQC.put(struct.fgcm_star_observations, outputRefs.fgcm_star_observations)
253 butlerQC.put(struct.fgcm_star_ids, outputRefs.fgcm_star_ids)
254 if self.config.doReferenceMatches:
255 butlerQC.put(struct.fgcm_reference_stars, outputRefs.fgcm_reference_stars)
257 def run(self, *, camera, visit_summary_handle_dict, isolated_star_cat_handle_dict,
258 isolated_star_source_handle_dict, lookup_table_handle):
259 """Run the fgcmBuildFromIsolatedStarsTask.
263 camera : `lsst.afw.cameraGeom.Camera`
265 visit_summary_handle_dict : `dict` [`int`, [`lsst.daf.butler.DeferredDatasetHandle`]]
266 Visit summary dataset handles, with the visit as key.
267 isolated_star_cat_handle_dict : `dict` [`int`, `lsst.daf.butler.DeferredDatasetHandle`]
268 Isolated star catalog dataset handles, with the tract as key.
269 isolated_star_source_handle_dict : `dict` [`int`, `lsst.daf.butler.DeferredDatasetHandle`]
270 Isolated star source dataset handles, with the tract as key.
271 lookup_table_handle : `lsst.daf.butler.DeferredDatasetHandle`
272 Data reference to fgcm look-up table.
276 struct : `lsst.pipe.base.struct`
277 Catalogs for persistence, with attributes:
279 ``fgcm_visit_catalog``
280 Catalog of per-visit data (`lsst.afw.table.ExposureCatalog`).
281 ``fgcm_star_observations``
282 Catalog of individual star observations (`astropy.table.Table`).
284 Catalog of per-star ids and positions (`astropy.table.Table`).
285 ``fgcm_reference_stars``
286 Catalog of reference stars matched to fgcm stars (`astropy.table.Table`).
290 calib_flux_aperture_radius =
None
291 if self.config.doSubtractLocalBackground:
293 calib_flux_aperture_radius = computeApertureRadiusFromName(self.config.instFluxField)
294 except RuntimeError
as e:
295 raise RuntimeError(
"Could not determine aperture radius from %s. "
296 "Cannot use doSubtractLocalBackground." %
297 (self.config.instFluxField))
from e
300 if self.config.doReferenceMatches:
301 if lookup_table_handle
is None:
302 raise RuntimeError(
"Must supply lookup_table_handle if config.doReferenceMatches is True.")
304 lut_cat = lookup_table_handle.get()
305 if len(camera) == lut_cat[0][
"nCcd"]:
306 use_science_detectors =
False
313 use_science_detectors =
True
318 visit_summary_handle_dict,
319 useScienceDetectors=use_science_detectors,
323 isolated_star_cat_handle_dict,
324 isolated_star_source_handle_dict,
327 calib_flux_aperture_radius=calib_flux_aperture_radius,
328 use_science_detectors=use_science_detectors,
335 isolated_star_cat_handle_dict,
336 isolated_star_source_handle_dict,
339 calib_flux_aperture_radius=calib_flux_aperture_radius,
340 use_science_detectors=use_science_detectors,
341 input_star_ids=star_ids,
342 return_observations=
True,
354 if self.config.doReferenceMatches:
359 return pipeBase.Struct(
360 fgcm_visit_catalog=visit_cat,
361 fgcm_star_observations=star_obs,
362 fgcm_star_ids=fgcm_obj,
363 fgcm_reference_stars=fgcm_ref,
368 isolated_star_cat_handle_dict,
369 isolated_star_source_handle_dict,
372 calib_flux_aperture_radius=None,
373 use_science_detectors=False,
375 return_observations=False,
378 Make all the star observations from isolated star catalogs.
380 This is intended to be run in two passes. In the first pass, a limited
381 set of columns is read in (only what is required to select potential
382 "good stars") and the stars are returned. In the second pass, the full
383 set of columns is read and only stars that are in input_star_ids
384 are put into the aggregated tables.
388 isolated_star_cat_handle_dict : `dict` [`int`, `lsst.daf.butler.DeferredDatasetHandle`]
389 Isolated star catalog dataset handles, with the tract as key.
390 isolated_star_source_handle_dict : `dict` [`int`, `lsst.daf.butler.DeferredDatasetHandle`]
391 Isolated star source dataset handles, with the tract as key.
392 visit_cat : `lsst.afw.table.ExposureCatalog`
393 Catalog of per-visit data.
394 camera : `lsst.afw.cameraGeom.Camera`
396 calib_flux_aperture_radius : `float`, optional
397 Radius for the calibration flux aperture.
398 use_science_detectors : `bool`, optional
399 Only use science detectors?
400 input_star_ids : `np.ndarray`, optional
401 Array of star IDs to use. Used for second pass and
403 return_observations : `bool`, optional
404 Return the observations? Should be False for first pass.
408 fgcm_obj : `astropy.table.Table`
409 Catalog of ids and positions for each star.
410 star_obs : `astropy.table.Table`
411 Catalog of individual star observations if
412 return_observations is True.
415 if input_star_ids
is not None:
416 input_star_ids = np.sort(input_star_ids)
425 self.config.instFluxField,
426 self.config.instFluxField +
"Err",
429 if return_observations:
432 source_columns.extend(
438 self.config.apertureInnerInstFluxField,
439 self.config.apertureInnerInstFluxField +
"Err",
440 self.config.apertureOuterInstFluxField,
441 self.config.apertureOuterInstFluxField +
"Err",
445 if self.config.doSubtractLocalBackground:
446 source_columns.append(self.config.localBackgroundFluxField)
447 local_background_flag_name = self.config.localBackgroundFluxField[0: -len(
'instFlux')] +
'flag'
448 source_columns.append(local_background_flag_name)
450 if self.sourceSelector.config.doFlags:
451 source_columns.extend(self.sourceSelector.config.flags.bad)
453 if self.config.doSubtractLocalBackground:
454 local_background_area = np.pi*calib_flux_aperture_radius**2.
458 approx_pixel_area_fields = computeApproxPixelAreaFields(camera)
461 detector_mapping = {}
462 for detector_index, detector
in enumerate(camera):
463 detector_mapping[detector.getId()] = detector_index
467 merge_source_counter = 0
471 visit_cat_table = visit_cat.asAstropy()
473 n_tracts = len(isolated_star_cat_handle_dict)
475 for index, tract
in enumerate(sorted(isolated_star_cat_handle_dict)):
476 if (index % 50) == 0:
477 self.log.info(getMemoryString(f
"Loading tract index {index}"))
479 stars = isolated_star_cat_handle_dict[tract].get()
480 sources = isolated_star_source_handle_dict[tract].get(parameters={
"columns": source_columns})
483 good_sources = self.sourceSelector.selectSources(sources).selected
484 if self.config.doSubtractLocalBackground:
485 good_sources &= (~sources[local_background_flag_name])
486 local_background = local_background_area*sources[self.config.localBackgroundFluxField]
487 good_sources &= ((sources[self.config.instFluxField] - local_background) > 0)
491 if good_sources.sum() == 0:
492 self.log.info(
"No good sources found in tract %d", tract)
499 for detector
in camera:
500 if use_science_detectors:
501 if not detector.getType() == afwCameraGeom.DetectorType.SCIENCE:
503 detector_ids.append(detector.getId())
505 missing_sources1 = np.ones(len(sources), dtype=np.bool_)
506 _, sources_match = esutil.numpy_util.match(visit_cat_table[
"visit"], sources[
"visit"])
507 missing_sources1[sources_match] =
False
508 good_sources &= (~missing_sources1)
510 missing_sources2 = np.ones(len(sources), dtype=np.bool_)
511 _, sources_match = esutil.numpy_util.match(detector_ids, sources[
"detector"])
512 missing_sources2[sources_match] =
False
513 good_sources &= (~missing_sources2)
516 if good_sources.sum() == 0:
517 self.log.info(
"No good sources found in tract %d", tract)
521 "Tract %d (%d/%d) contains %d good sources.",
533 if len(self.config.requiredBands) > 0:
534 loop_bands = self.config.requiredBands
536 loop_bands = np.unique(sources[
"band"])
538 n_req = np.zeros((len(loop_bands), len(stars)), dtype=np.int32)
539 for i, band
in enumerate(loop_bands):
540 (band_use,) = (sources[good_sources][
"band"] == band).nonzero()
543 (i, sources[good_sources][
"obj_index"][band_use]),
547 if len(self.config.requiredBands) > 0:
550 (good_stars,) = (n_req.min(axis=0) >= self.config.minPerBand).nonzero()
554 (good_stars,) = (n_req.max(axis=0) >= self.config.minPerBand).nonzero()
556 if len(good_stars) == 0:
557 self.log.info(
"No good stars found in tract %d", tract)
560 if not return_observations:
562 fgcm_obj[
"isolated_star_id"] = stars[
"isolated_star_id"][good_stars]
563 fgcm_obj[
"ra"] = stars[
"ra"][good_stars]
564 fgcm_obj[
"dec"] = stars[
"dec"][good_stars]
565 fgcm_obj[
"fgcm_id"] = np.zeros(len(fgcm_obj), dtype=np.int64)
567 fgcm_objs.append(fgcm_obj)
570 elif input_star_ids
is not None:
573 np.searchsorted(input_star_ids, stars[
"isolated_star_id"][good_stars]),
575 len(input_star_ids) - 1,
577 matched = (input_star_ids[sub1] == stars[
"isolated_star_id"][good_stars])
578 good_stars = good_stars[matched]
580 if len(good_stars) == 0:
581 self.log.info(
"No good stars found after down-selection in tract %d", tract)
588 obj_index = sources[
"obj_index"][good_sources]
589 a, b = esutil.numpy_util.match(good_stars, obj_index)
592 _, index_new = np.unique(a, return_index=
True)
593 stars[
"source_cat_index"][good_stars] = index_new
594 sources = sources[good_sources][b]
595 sources[
"obj_index"][:] = a
596 stars = stars[good_stars]
598 nsource = np.zeros(len(stars), dtype=np.int32)
601 sources[
"obj_index"],
604 stars[
"nsource"][:] = nsource
618 star_obs[
"ra"] = sources[
"ra"].astype(np.float64)
619 star_obs[
"dec"] = sources[
"dec"].astype(np.float64)
620 star_obs[
"x"] = sources[
"x"].astype(np.float64)
621 star_obs[
"y"] = sources[
"y"].astype(np.float64)
622 star_obs[
"visit"] = sources[
"visit"].astype(np.int64)
623 star_obs[
"detector"] = sources[
"detector"].astype(np.int32)
624 star_obs[
"inst_mag"] = np.zeros(len(sources), dtype=
"f4")
625 star_obs[
"inst_mag_err"] = np.zeros(len(sources), dtype=
"f4")
626 star_obs[
"jacobian"] = np.zeros(len(sources), dtype=
"f4")
627 star_obs[
"delta_mag_bkg"] = np.zeros(len(sources), dtype=
"f4")
628 star_obs[
"delta_mag_aper"] = np.zeros(len(sources), dtype=
"f4")
629 star_obs[
"delta_mag_err_aper"] = np.zeros(len(sources), dtype=
"f4")
631 visit_match, obs_match = esutil.numpy_util.match(visit_cat_table[
"visit"], sources[
"visit"])
633 exp_time = np.zeros(len(star_obs))
634 exp_time[obs_match] = visit_cat_table[
"exptime"][visit_match]
636 with warnings.catch_warnings():
638 warnings.simplefilter(
"ignore")
640 inst_mag_inner = -2.5*np.log10(sources[self.config.apertureInnerInstFluxField])
641 inst_mag_err_inner = k*(sources[self.config.apertureInnerInstFluxField +
"Err"]
642 / sources[self.config.apertureInnerInstFluxField])
643 inst_mag_outer = -2.5*np.log10(sources[self.config.apertureOuterInstFluxField])
644 inst_mag_err_outer = k*(sources[self.config.apertureOuterInstFluxField +
"Err"]
645 / sources[self.config.apertureOuterInstFluxField])
646 star_obs[
"delta_mag_aper"][:] = inst_mag_inner - inst_mag_outer
647 star_obs[
"delta_mag_err_aper"][:] = np.sqrt(inst_mag_err_inner**2. + inst_mag_err_outer**2.)
649 bad = ~np.isfinite(star_obs[
"delta_mag_aper"])
650 star_obs[
"delta_mag_aper"][bad] = 99.0
651 star_obs[
"delta_mag_err_aper"][bad] = 99.0
653 if self.config.doSubtractLocalBackground:
667 local_background = local_background_area*sources[self.config.localBackgroundFluxField]
668 star_obs[
"delta_mag_bkg"][:] = (
669 -2.5*np.log10(sources[self.config.instFluxField]
670 - local_background) -
671 -2.5*np.log10(sources[self.config.instFluxField])
675 for detector
in camera:
676 if use_science_detectors:
677 if not detector.getType() == afwCameraGeom.DetectorType.SCIENCE:
680 detector_id = detector.getId()
682 (use,) = (star_obs[
"detector"][obs_match] == detector_id).nonzero()
690 jac = approx_pixel_area_fields[detector_id].evaluate(
691 star_obs[
"x"][obs_match][use],
692 star_obs[
"y"][obs_match][use],
694 star_obs[
"jacobian"][obs_match[use]] = jac
695 scaled_inst_flux = (sources[self.config.instFluxField][obs_match[use]]
696 * visit_cat_table[
"scaling"][visit_match[use],
697 detector_mapping[detector_id]])
698 star_obs[
"inst_mag"][obs_match[use]] = (-2.5 * np.log10(scaled_inst_flux
704 star_obs[
"inst_mag_err"][:] = k*(sources[self.config.instFluxField +
"Err"]
705 / sources[self.config.instFluxField])
708 if self.config.doApplyWcsJacobian:
709 star_obs[
"inst_mag"][:] -= 2.5*np.log10(star_obs[
"jacobian"])
717 fgcm_obj[
"fgcm_id"] = np.zeros(len(stars), dtype=np.int64)
718 fgcm_obj[
"isolated_star_id"] = stars[
"isolated_star_id"].astype(np.int64)
719 fgcm_obj[
"ra"] = stars[
"ra"].astype(np.float64)
720 fgcm_obj[
"dec"] = stars[
"dec"].astype(np.float64)
721 fgcm_obj[
"obs_arr_index"] = stars[
"source_cat_index"].astype(np.int64)
722 fgcm_obj[
"n_obs"] = stars[
"nsource"].astype(np.int32)
723 fgcm_obj[
"obj_flag"] = np.zeros(len(stars), dtype=np.int32)
726 fgcm_obj[
"obs_arr_index"][:] += merge_source_counter
728 fgcm_objs.append(fgcm_obj)
729 star_obs_cats.append(star_obs)
731 merge_source_counter += len(star_obs)
733 fgcm_obj = vstack(fgcm_objs)
736 fgcm_obj[
"fgcm_id"][:] = np.arange(len(fgcm_obj)) + 1
738 if return_observations:
739 return fgcm_obj, vstack(star_obs_cats)
801 """Associate fgcm isolated stars with reference stars.
805 lookup_table_handle : `lsst.daf.butler.DeferredDatasetHandle`, optional
806 Data reference to fgcm look-up table (used if matching reference stars).
807 visit_cat : `lsst.afw.table.ExposureCatalog`
808 Catalog of per-visit data.
809 fgcm_obj : `astropy.table.Table`
810 Catalog of per-star ids and positions
814 ref_cat : `astropy.table.Table`
815 Catalog of reference stars matched to fgcm stars.
818 lut_cat = lookup_table_handle.get()
820 std_filter_dict = {filter_name: std_filter
for (filter_name, std_filter)
in
821 zip(lut_cat[0][
"physicalFilters"].split(
","),
822 lut_cat[0][
"stdPhysicalFilters"].split(
","))}
823 std_lambda_dict = {std_filter: std_lambda
for (std_filter, std_lambda)
in
824 zip(lut_cat[0][
"stdPhysicalFilters"].split(
","),
825 lut_cat[0][
"lambdaStdFilter"])}
833 self.log.info(
"Using the following reference filters: %s", (
", ".join(reference_filter_names)))
836 ipnest = hpg.angle_to_pixel(self.config.coarseNside, fgcm_obj[
"ra"], fgcm_obj[
"dec"])
837 hpix, revpix = histogram_rev_sorted(ipnest)
842 dtype = [(
"fgcm_id",
"i8"),
843 (
"refMag",
"f4", (len(reference_filter_names), )),
844 (
"refMagErr",
"f4", (len(reference_filter_names), ))]
846 (gdpix,) = (hpix > 0).nonzero()
847 for ii, gpix
in enumerate(gdpix):
848 p1a = revpix[revpix[gpix]: revpix[gpix + 1]]
851 ra_wrap = fgcm_obj[
"ra"][p1a]
852 if (ra_wrap.min() < 10.0)
and (ra_wrap.max() > 350.0):
853 ra_wrap[ra_wrap > 180.0] -= 360.0
854 mean_ra = np.mean(ra_wrap) % 360.0
856 mean_ra = np.mean(ra_wrap)
857 mean_dec = np.mean(fgcm_obj[
"dec"][p1a])
859 dist = esutil.coords.sphdist(mean_ra, mean_dec, fgcm_obj[
"ra"][p1a], fgcm_obj[
"dec"][p1a])
862 if rad < hpg.nside_to_resolution(self.config.coarseNside)/2.:
868 reference_filter_names,
873 self.config.coarseNside,
874 hpg.nest_to_ring(self.config.coarseNside, ipnest[p1a[0]]),
875 reference_filter_names,
877 if ref_cat.size == 0:
881 with Matcher(fgcm_obj[
"ra"][p1a], fgcm_obj[
"dec"][p1a])
as matcher:
882 idx, i1, i2, d = matcher.query_radius(
885 self.config.matchRadius/3600.,
893 pixel_cat = Table(data=np.zeros(i1.size, dtype=dtype))
894 pixel_cat[
"fgcm_id"][:] = fgcm_obj[
"fgcm_id"][p1a[i1]]
895 pixel_cat[
"refMag"][:, :] = ref_cat[
"refMag"][i2, :]
896 pixel_cat[
"refMagErr"][:, :] = ref_cat[
"refMagErr"][i2, :]
898 pixel_cats.append(pixel_cat)
901 "Found %d reference matches in pixel %d (%d of %d).",
908 ref_cat_full = vstack(pixel_cats)
909 ref_cat_full.meta.update({
'FILTERNAMES': reference_filter_names})