79def main(repo, collections, skymapName=None, tracts=None, visits=None, physicalFilters=None, bands=None,
80 ccds=None, ccdKey="detector", showPatch=False, saveFile=None, showCcds=False, visitVetoFile=None,
81 minOverlapFraction=None, trimToTracts=False, doUnscaledLimitRatio=False,
82 forceScaledLimitRatio=False):
83 if minOverlapFraction
is not None and tracts
is None:
84 raise RuntimeError(
"Must specify --tracts if --minOverlapFraction is set")
85 logger.info(
"Making butler for collections = {} in repo {}".format(collections, repo))
86 butler = dafButler.Butler(repo, collections=collections)
87 instrument = butler.find_dataset(
"camera").dataId[
"instrument"]
90 if skymapName
is None:
91 if instrument ==
"HSC":
92 skymapName =
"hsc_rings_v1"
93 detectorSkipList = [9]
94 elif instrument ==
"LSSTCam-imSim":
96 elif instrument ==
"LSSTComCamSim":
97 skymapName =
"ops_rehersal_prep_2k_v1"
98 elif instrument ==
"LATISS":
99 skymapName =
"latiss_v1"
100 elif instrument ==
"DECam":
101 skymapName =
"decam_rings_v1"
103 logger.error(
"Unknown skymapName for instrument: %s. Must specify --skymapName on command line.",
105 logger.info(
"instrument = {} skymapName = {}".format(instrument, skymapName))
106 camera = butler.get(
"camera", instrument=instrument)
107 skymap = butler.get(
"skyMap", instrument=instrument, skymap=skymapName)
110 if tracts
is not None:
114 if physicalFilters
is not None:
115 physicalFilterStr =
makeWhereInStr(
"physical_filter", physicalFilters, str)
116 whereStr +=
" AND " + physicalFilterStr
if len(whereStr)
else " " + physicalFilterStr
118 if bands
is not None:
120 whereStr +=
" AND " + bandStr
if len(whereStr)
else " " + bandStr
122 if len(whereStr) > 1:
123 whereStr =
"instrument=\'" + instrument +
"\' AND skymap=\'" + skymapName +
"\' AND " + whereStr
125 whereStr =
"instrument=\'" + instrument +
"\' AND skymap=\'" + skymapName +
"\'"
126 logger.info(
"Applying the following where clause in dataId search: {} ".format(whereStr))
129 if visitVetoFile
is not None:
130 with open(visitVetoFile)
as f:
131 content = f.readlines()
132 visitVetoList = [
int(visit.strip())
for visit
in content]
135 dataRefs = list(butler.registry.queryDatasets(
"calexp", where=whereStr).expanded())
137 for dataRef
in dataRefs:
138 visit = dataRef.dataId.visit.id
139 if visit
not in visits
and visit
not in visitVetoList:
142 logger.info(
"List of visits (N={}) satisfying where and veto clauses: {}".format(len(visits),
145 if len(visitVetoList) > 1:
146 visitListTemp = visits.copy()
147 for visit
in visitListTemp:
148 if visit
in visitVetoList:
150 logger.info(
"List of visits (N={}) excluding veto list: {}".format(len(visits), visits))
151 logger.info(
"List of visits (N={}): {}".format(len(visits), visits))
156 if ((ccds
is None or ccdId
in ccds)
and ccd.getType() == cameraGeom.DetectorType.SCIENCE
157 and ccdId
not in detectorSkipList):
158 ccdIdList.append(ccdId)
160 nDetTot = len(ccdIdList)
162 visitIncludeList = []
166 if minOverlapFraction
is not None:
167 for i_v, visit
in enumerate(visits):
170 visitSummary = butler.get(
"visitSummary", visit=visit)
171 except LookupError
as e:
172 logger.warn(
"%s Will try to get wcs from calexp.", e)
174 if tracts
is not None:
176 tractInfo = skymap[tract]
177 sphCorners = tractInfo.wcs.pixelToSky(
geom.Box2D(tractInfo.bbox).getCorners())
178 tractConvexHull = sphgeom.ConvexPolygon.convexHull(
179 [coord.getVector()
for coord
in sphCorners])
180 for ccdId
in ccdIdList:
181 if ccdId
not in ccdOverlapList:
183 ccdKey, ccdId, visit, visitSummary=visitSummary, butler=butler,
185 if raCorners
is not None and decCorners
is not None:
187 for ra, dec
in zip(raCorners, decCorners):
190 detSphCorners.append(pt)
191 detConvexHull = sphgeom.ConvexPolygon.convexHull(
192 [coord.getVector()
for coord
in detSphCorners])
193 if tractConvexHull.contains(detConvexHull):
194 ccdOverlapList.append(ccdId)
196 if len(ccdOverlapList)/nDetTot >= minOverlapFraction:
198 if len(ccdOverlapList)/nDetTot < minOverlapFraction:
199 logger.info(
"Fraction of detectors overlaping any tract for visit %d (%.2f) < "
200 "minimum required (%.2f). Skipping visit...",
201 visit, len(ccdOverlapList)/nDetTot, minOverlapFraction)
203 if visit
not in visitIncludeList:
204 visitIncludeList.append(visit)
206 visitIncludeList = visits
211 cmap =
get_cmap(len(visitIncludeList))
213 maxVisitForLegend = 20
216 includedPhysicalFilters = []
217 for i_v, visit
in enumerate(visitIncludeList):
218 print(
"Working on visit %d [%d of %d]" % (visit, i_v + 1, len(visitIncludeList)), end=
"\r")
221 fillKwargs = {
"fill":
False,
"alpha": alphaEdge,
"facecolor":
None,
"edgecolor": color,
"lw": 0.6}
223 visitSummary = butler.get(
"visitSummary", visit=visit)
224 except Exception
as e:
225 logger.warn(
"%s Will try to get wcs from calexp.", e)
228 band, physicalFilter =
getBand(visitSummary=visitSummary, butler=butler, visit=visit)
229 if band
not in includedBands:
230 includedBands.append(band)
231 if physicalFilter
not in includedPhysicalFilters:
232 includedPhysicalFilters.append(physicalFilter)
234 for ccdId
in ccdIdList:
236 ccdKey, ccdId, visit, visitSummary=visitSummary, butler=butler)
237 if raCorners
is not None and decCorners
is not None:
240 if not inLegend
and len(visitIncludeList) <= maxVisitForLegend:
241 plt.fill(raCorners, decCorners, label=str(visit), **fillKwargs)
244 plt.fill(raCorners, decCorners, **fillKwargs)
245 plt.fill(raCorners, decCorners, fill=
True, alpha=alphaEdge/4, color=color,
247 if visit
not in finalVisitList:
248 finalVisitList.append(visit)
252 deltaRa =
max(raCorners) -
min(raCorners)
253 deltaDec =
max(decCorners) -
min(decCorners)
255 min(decCorners) + overlapFrac*deltaDec)
257 max(decCorners) - overlapFrac*deltaDec)
260 overlaps = [
not bboxDouble.overlaps(otherBbox)
for otherBbox
in bboxesPlotted]
263 str(ccdId), fontsize=6, ha=
"center", va=
"center", color=
"darkblue")
264 bboxesPlotted.append(bboxDouble)
266 logger.info(
"Final list of visits (N={}) satisfying where and minOverlapFraction clauses: {}"
267 .format(len(finalVisitList), finalVisitList))
269 raToDecLimitRatio =
None
271 tractList = list(
set(skymap.findTractIdArray(ras, decs, degrees=
True)))
272 minVisitRa, maxVisitRa =
min(ras),
max(ras)
273 minVisitDec, maxVisitDec =
min(decs),
max(decs)
274 raVisitDiff = maxVisitRa - minVisitRa
275 decVisitDiff = maxVisitDec - minVisitDec
276 midVisitRa = minVisitRa + 0.5*raVisitDiff
277 midVisitDec = minVisitDec + 0.5*decVisitDiff
278 midRa = np.atleast_1d((midVisitRa*units.deg).to(units.radian).value).astype(np.float64)
279 midDec = np.atleast_1d((midVisitDec*units.deg).to(units.radian).value).astype(np.float64)
280 midSkyCoord = SkyCoord(midVisitRa*units.deg, midVisitDec*units.deg)
282 if tracts
is not None:
283 logger.info(
"No calexps were found, but --tracts list was provided, so will go ahead and "
284 "plot the empty tracts.")
288 raise RuntimeError(
"No data to plot (if you want to plot empty tracts, include them as "
289 "a blank-space separated list to the --tracts option.")
291 logger.info(
"List of tracts overlapping data: {}".format(tractList))
294 if forceScaledLimitRatio:
295 doUnscaledLimitRatio =
False
301 radiusMm = camera.computeMaxFocalPlaneRadius()
303 focalPlaneToFieldAngle = camera.getTransformMap().getTransform(
304 cameraGeom.FOCAL_PLANE, cameraGeom.FIELD_ANGLE
306 fpRadiusDeg = np.rad2deg(focalPlaneToFieldAngle.applyForward(fpRadiusPt))[0]
307 detectorRadiusDeg = fpRadiusDeg/np.sqrt(len(camera))
311 xDelta0 = xLimMax - xLimMin
312 yDelta0 = yLimMax - yLimMin
314 xDelta0 = raVisitDiff
315 yDelta0 = decVisitDiff
316 yLimMin = minVisitDec
317 yLimMax = maxVisitDec
318 raDecScaleThresh = 1.5
320 (xDelta0/yDelta0 > raDecScaleThresh
or yDelta0/xDelta0 > raDecScaleThresh)
321 and max(xDelta0, yDelta0) > 70*detectorRadiusDeg
322 and yLimMin < 75.0
and yLimMax > -75.0
325 "Sky coverage is large (and not too close to a pole), so not scaling to detector coords."
327 doUnscaledLimitRatio =
True
329 if not doUnscaledLimitRatio:
332 minDistToMidCoord = 1e12
335 for i_v, visit
in enumerate(visits):
337 visitSummary = butler.get(
"visitSummary", visit=visit)
338 except Exception
as e:
339 logger.warn(
"%s Will try to get wcs from calexp.", e)
341 for ccdId
in ccdIdList:
343 ccdKey, ccdId, visit, visitSummary=visitSummary, butler=butler, doLogWarn=
False)
344 if raCorners
is not None and decCorners
is not None:
346 for ra, dec
in zip(raCorners, decCorners):
349 detSphCorners.append(pt)
350 ptSkyCoord = SkyCoord(ra*units.deg, dec*units.deg)
351 separation = (midSkyCoord.separation(ptSkyCoord)).degree
352 if separation < minDistToMidCoord:
355 minDistToMidCoord = separation
356 detConvexHull = sphgeom.ConvexPolygon(
357 [coord.getVector()
for coord
in detSphCorners])
358 if detConvexHull.contains(midRa, midDec)
and raToDecLimitRatio
is None:
360 "visit/det overlapping plot coord mid point in RA/Dec: %d %d", visit, ccdId
362 raToDecLimitRatio = (
363 (
max(raCorners) -
min(raCorners))/(
max(decCorners) -
min(decCorners))
366 width = det.getBBox().getWidth()
367 height = det.getBBox().getHeight()
368 if raToDecLimitRatio > 1.0:
369 raToDecLimitRatio /=
max(height/width, width/height)
371 if raToDecLimitRatio < 1.0:
372 raToDecLimitRatio *=
max(height/width, width/height)
374 if raToDecLimitRatio
is not None:
377 if raToDecLimitRatio
is None and minSepVisit
is not None:
379 visitSummary = butler.get(
"visitSummary", visit=minSepVisit)
380 except Exception
as e:
381 logger.warn(
"%s Will try to get wcs from calexp.", e)
384 ccdKey, minSepCcdId, minSepVisit, visitSummary=visitSummary, butler=butler, doLogWarn=
False)
385 for ra, dec
in zip(raCorners, decCorners):
388 detSphCorners.append(pt)
389 detConvexHull = sphgeom.ConvexPolygon([coord.getVector()
for coord
in detSphCorners])
391 "visit/det closest to plot coord mid point in RA/Dec (none actually overlap it): %d %d",
392 minSepVisit, minSepCcdId
394 raToDecLimitRatio = (
max(raCorners) -
min(raCorners))/(
max(decCorners) -
min(decCorners))
395 det = camera[minSepCcdId]
396 width = det.getBBox().getWidth()
397 height = det.getBBox().getHeight()
398 if raToDecLimitRatio > 1.0:
399 raToDecLimitRatio /=
max(height/width, width/height)
401 if raToDecLimitRatio < 1.0:
402 raToDecLimitRatio *=
max(height/width, width/height)
404 if trimToTracts
is True:
405 xlim, ylim =
derivePlotLimits(tractLimitsDict, raToDecLimitRatio=raToDecLimitRatio, buffFrac=0.04)
407 visitLimitsDict = {
"allVisits": {
"ras": [minVisitRa, maxVisitRa],
"decs": [minVisitDec, maxVisitDec]}}
408 xlim, ylim =
derivePlotLimits(visitLimitsDict, raToDecLimitRatio=raToDecLimitRatio, buffFrac=0.04)
410 if doUnscaledLimitRatio:
411 boxAspectRatio = abs((ylim[1] - ylim[0])/(xlim[1] - xlim[0]))
419 if tracts
is not None:
420 tractOutlineList = list(
set(tracts + tractList))
422 tractOutlineList = tractList
423 tractOutlineList.sort()
424 logger.info(
"List of tract outlines being plotted: {}".format(tractOutlineList))
425 for i_t, tract
in enumerate(tractOutlineList):
426 alpha =
max(0.1, alpha0 - i_t*1.0/len(tractOutlineList))
427 tractInfo = skymap[tract]
428 tCenter = tractInfo.ctr_coord
429 tCenterRa = tCenter.getRa().asDegrees()
430 tCenterDec = tCenter.getDec().asDegrees()
431 fracDeltaX = 0.02*abs((xlim[1] - xlim[0]))
432 fracDeltaY = 0.02*abs((ylim[1] - ylim[0]))
433 if (xlim[1] + fracDeltaX < tCenterRa < xlim[0] - fracDeltaX
434 and ylim[0] + fracDeltaY < tCenterDec < ylim[1] - fracDeltaY):
435 if len(tractOutlineList) > 1
or not showPatch:
437 plt.text(tCenterRa, tCenterDec, tract, fontsize=7, alpha=alpha, ha=
"center", va=
"center")
439 plt.text(tCenterRa, tCenterDec, tract, fontsize=7, alpha=1, color=
"white",
440 path_effects=[pathEffects.withStroke(linewidth=3, foreground=
"black")],
441 fontweight=500, ha=
"center", va=
"center", zorder=5)
442 ra, dec =
bboxToRaDec(tractInfo.bbox, tractInfo.getWcs())
443 plt.fill(ra, dec, fill=
False, edgecolor=
"k", lw=1, linestyle=
"dashed", alpha=alpha)
444 tractArtist = matplotlib.patches.Patch(fill=
False, edgecolor=
"k", linestyle=
"dashed", alpha=alpha)
445 tractHandleList.append(tractArtist)
446 tractStrList.append(str(tract))
449 for patch
in tractInfo:
450 ra, dec =
bboxToRaDec(patch.getInnerBBox(), tractInfo.getWcs())
451 plt.fill(ra, dec, fill=
False, edgecolor=patchColor, lw=0.5, linestyle=(0, (5, 6)),
456 str(patch.sequential_index), fontsize=5, color=patchColor,
457 ha=
"center", va=
"center", alpha=alpha)
463 ax.set_box_aspect(boxAspectRatio)
464 if abs(xlim[1] > 99.99):
465 ax.tick_params(
"x", labelrotation=45, pad=0, labelsize=8)
467 ax.tick_params(
"x", labelrotation=0, pad=0, labelsize=8)
468 ax.tick_params(
"y", labelsize=8)
469 ax.set_xlabel(
"RA (deg)", fontsize=9)
470 ax.set_ylabel(
"Dec (deg)", fontsize=9)
472 visitScaleOffset =
None
473 if len(visitIncludeList) > maxVisitForLegend:
474 nz = matplotlib.colors.Normalize()
475 colorBarScale = finalVisitList
476 if max(finalVisitList) > 9999999:
477 visitScaleOffset =
min(finalVisitList)
478 colorBarScale = [visit - visitScaleOffset
for visit
in finalVisitList]
479 nz.autoscale(colorBarScale)
480 cax, _ = matplotlib.colorbar.make_axes(plt.gca(), pad=0.03)
481 cax.tick_params(labelsize=7)
482 cb = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=nz, alpha=alphaEdge,
483 format=
lambda x, _: f
"{x:.0f}")
484 cb.ax.yaxis.get_offset_text().set_fontsize(7)
485 colorBarLabel =
"visit number"
486 if visitScaleOffset
is not None:
487 colorBarLabel +=
" - {:d}".format(visitScaleOffset)
488 cb.set_label(colorBarLabel, rotation=-90, labelpad=13, fontsize=9)
489 tractLegend = Legend(ax, tractHandleList, tractStrList, loc=
"upper right", fancybox=
True,
490 shadow=
True, fontsize=5, title_fontsize=6, title=
"tracts")
491 ax.add_artist(tractLegend)
493 if len(visitIncludeList) > 0:
494 xBboxAnchor =
min(1.25,
max(1.03, boxAspectRatio*1.15))
495 ax.legend(loc=
"center left", bbox_to_anchor=(xBboxAnchor, 0.5), fancybox=
True,
496 shadow=
True, fontsize=6, title_fontsize=6, title=
"visits")
498 tractLegend = Legend(ax, tractHandleList, tractStrList, loc=
"center left", bbox_to_anchor=(1.0, 0.5),
499 fancybox=
True, shadow=
True, fontsize=6, title_fontsize=6, title=
"tracts")
500 ax.add_artist(tractLegend)
502 titleStr = repo +
"\n" + collections[0]
503 if len(collections) > 1:
504 for collection
in collections[1:]:
505 titleStr +=
"\n" + collection
506 titleStr +=
"\nnVisit: {}".format(str(len(finalVisitList)))
507 if minOverlapFraction
is not None:
508 titleStr +=
" (minOvlpFrac = {:.2f})".format(minOverlapFraction)
509 if len(includedBands) > 0:
510 titleStr +=
" bands: {}".format(str(includedBands).translate({ord(i):
None for i
in "[]\'"}))
511 if len(includedPhysicalFilters) > 0:
512 if len(includedPhysicalFilters[0]) > 9:
514 titleStr +=
" physical filters: {}".format(str(includedPhysicalFilters).translate(
515 {ord(i):
None for i
in "[]\'"}))
516 ax.set_title(
"{}".format(titleStr), fontsize=8)
519 if boxAspectRatio > 1.0:
520 minInches =
max(4.0, 0.3*abs(xlim[1] - xlim[0]))
522 yInches =
min(120.0, boxAspectRatio*minInches)
523 fig.set_size_inches(xInches, yInches)
524 if boxAspectRatio < 1.0:
525 minInches =
max(4.0, 0.3*abs(ylim[1] - ylim[0]))
526 xInches =
min(120.0, minInches/boxAspectRatio)
528 fig.set_size_inches(xInches, yInches)
529 if saveFile
is not None:
530 logger.info(
"Saving file in: %s", saveFile)
531 fig.savefig(saveFile, bbox_inches=
"tight", dpi=150)