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):
82 if minOverlapFraction
is not None and tracts
is None:
83 raise RuntimeError(
"Must specify --tracts if --minOverlapFraction is set")
84 logger.info(
"Making butler for collections = {} in repo {}".format(collections, repo))
85 butler = dafButler.Butler(repo, collections=collections)
86 instrument = butler.find_dataset(
"camera").dataId[
"instrument"]
89 if skymapName
is None:
90 if instrument ==
"HSC":
91 skymapName =
"hsc_rings_v1"
92 detectorSkipList = [9]
93 elif instrument ==
"LSSTCam-imSim":
95 elif instrument ==
"LATISS":
96 skymapName =
"latiss_v1"
97 elif instrument ==
"DECam":
98 skymapName =
"decam_rings_v1"
100 logger.error(
"Unknown skymapName for instrument: %s. Must specify --skymapName on command line.",
102 logger.info(
"instrument = {} skymapName = {}".format(instrument, skymapName))
103 camera = butler.get(
"camera", instrument=instrument)
104 skymap = butler.get(
"skyMap", instrument=instrument, skymap=skymapName)
107 if tracts
is not None:
111 if physicalFilters
is not None:
112 physicalFilterStr =
makeWhereInStr(
"physical_filter", physicalFilters, str)
113 whereStr +=
" AND " + physicalFilterStr
if len(whereStr)
else " " + physicalFilterStr
115 if bands
is not None:
117 whereStr +=
" AND " + bandStr
if len(whereStr)
else " " + bandStr
119 if len(whereStr) > 1:
120 whereStr =
"instrument=\'" + instrument +
"\' AND skymap=\'" + skymapName +
"\' AND " + whereStr
122 whereStr =
"instrument=\'" + instrument +
"\' AND skymap=\'" + skymapName +
"\'"
123 logger.info(
"Applying the following where clause in dataId search: {} ".format(whereStr))
126 if visitVetoFile
is not None:
127 with open(visitVetoFile)
as f:
128 content = f.readlines()
129 visitVetoList = [
int(visit.strip())
for visit
in content]
132 dataRefs = list(butler.registry.queryDatasets(
"calexp", where=whereStr).expanded())
134 for dataRef
in dataRefs:
135 visit = dataRef.dataId.visit.id
136 if visit
not in visits
and visit
not in visitVetoList:
139 logger.info(
"List of visits (N={}) satisfying where and veto clauses: {}".format(len(visits),
142 if len(visitVetoList) > 1:
143 visitListTemp = visits.copy()
144 for visit
in visitListTemp:
145 if visit
in visitVetoList:
147 logger.info(
"List of visits (N={}) excluding veto list: {}".format(len(visits), visits))
152 if ((ccds
is None or ccdId
in ccds)
and ccd.getType() == cameraGeom.DetectorType.SCIENCE
153 and ccdId
not in detectorSkipList):
154 ccdIdList.append(ccdId)
156 nDetTot = len(ccdIdList)
158 visitIncludeList = []
162 if minOverlapFraction
is not None:
163 for i_v, visit
in enumerate(visits):
166 visitSummary = butler.get(
"visitSummary", visit=visit)
167 except LookupError
as e:
168 logger.warn(
"%s Will try to get wcs from calexp.", e)
170 if tracts
is not None:
172 tractInfo = skymap[tract]
173 sphCorners = tractInfo.wcs.pixelToSky(
geom.Box2D(tractInfo.bbox).getCorners())
174 tractConvexHull = sphgeom.ConvexPolygon.convexHull(
175 [coord.getVector()
for coord
in sphCorners])
176 for ccdId
in ccdIdList:
177 if ccdId
not in ccdOverlapList:
179 ccdKey, ccdId, visit, visitSummary=visitSummary, butler=butler,
181 if raCorners
is not None and decCorners
is not None:
183 for ra, dec
in zip(raCorners, decCorners):
186 detSphCorners.append(pt)
187 detConvexHull = sphgeom.ConvexPolygon.convexHull(
188 [coord.getVector()
for coord
in detSphCorners])
189 if tractConvexHull.contains(detConvexHull):
190 ccdOverlapList.append(ccdId)
192 if len(ccdOverlapList)/nDetTot >= minOverlapFraction:
194 if len(ccdOverlapList)/nDetTot < minOverlapFraction:
195 logger.info(
"Fraction of detectors overlaping any tract for visit %d (%.2f) < "
196 "minimum required (%.2f). Skipping visit...",
197 visit, len(ccdOverlapList)/nDetTot, minOverlapFraction)
199 if visit
not in visitIncludeList:
200 visitIncludeList.append(visit)
202 visitIncludeList = visits
207 cmap =
get_cmap(len(visitIncludeList))
209 maxVisitForLegend = 20
212 includedPhysicalFilters = []
213 for i_v, visit
in enumerate(visitIncludeList):
214 print(
"Working on visit %d [%d of %d]" % (visit, i_v + 1, len(visitIncludeList)), end=
"\r")
217 fillKwargs = {
"fill":
False,
"alpha": alphaEdge,
"facecolor":
None,
"edgecolor": color,
"lw": 0.6}
219 visitSummary = butler.get(
"visitSummary", visit=visit)
220 except LookupError
as e:
221 logger.warn(
"%s Will try to get wcs from calexp.", e)
224 band, physicalFilter =
getBand(visitSummary=visitSummary, butler=butler, visit=visit)
225 if band
not in includedBands:
226 includedBands.append(band)
227 if physicalFilter
not in includedPhysicalFilters:
228 includedPhysicalFilters.append(physicalFilter)
230 for ccdId
in ccdIdList:
232 ccdKey, ccdId, visit, visitSummary=visitSummary, butler=butler)
233 if raCorners
is not None and decCorners
is not None:
236 if not inLegend
and len(visitIncludeList) <= maxVisitForLegend:
237 plt.fill(raCorners, decCorners, label=str(visit), **fillKwargs)
240 plt.fill(raCorners, decCorners, **fillKwargs)
241 plt.fill(raCorners, decCorners, fill=
True, alpha=alphaEdge/4, color=color,
243 if visit
not in finalVisitList:
244 finalVisitList.append(visit)
248 deltaRa =
max(raCorners) -
min(raCorners)
249 deltaDec =
max(decCorners) -
min(decCorners)
251 min(decCorners) + overlapFrac*deltaDec)
253 max(decCorners) - overlapFrac*deltaDec)
256 overlaps = [
not bboxDouble.overlaps(otherBbox)
for otherBbox
in bboxesPlotted]
259 str(ccdId), fontsize=6, ha=
"center", va=
"center", color=
"darkblue")
260 bboxesPlotted.append(bboxDouble)
262 logger.info(
"Final list of visits (N={}) satisfying where and minOverlapFraction clauses: {}"
263 .format(len(finalVisitList), finalVisitList))
265 raToDecLimitRatio =
None
267 tractList = list(
set(skymap.findTractIdArray(ras, decs, degrees=
True)))
268 minVisitRa, maxVisitRa =
min(ras),
max(ras)
269 minVisitDec, maxVisitDec =
min(decs),
max(decs)
270 raVisitDiff = maxVisitRa - minVisitRa
271 decVisitDiff = maxVisitDec - minVisitDec
272 midVisitRa = minVisitRa + 0.5*raVisitDiff
273 midVisitDec = minVisitDec + 0.5*decVisitDiff
274 midRa = np.atleast_1d((midVisitRa*units.deg).to(units.radian).value).astype(np.float64)
275 midDec = np.atleast_1d((midVisitDec*units.deg).to(units.radian).value).astype(np.float64)
276 midSkyCoord = SkyCoord(midVisitRa*units.deg, midVisitDec*units.deg)
278 if tracts
is not None:
279 logger.info(
"No calexps were found, but --tracts list was provided, so will go ahead and "
280 "plot the empty tracts.")
283 raToDecLimitRatio = 1.0
285 raise RuntimeError(
"No data to plot (if you want to plot empty tracts, include them as "
286 "a blank-space separated list to the --tracts option.")
288 logger.info(
"List of tracts overlapping data: {}".format(tractList))
293 minDistToMidCood = 1e12
296 for i_v, visit
in enumerate(visits):
298 visitSummary = butler.get(
"visitSummary", visit=visit)
299 except LookupError
as e:
300 logger.warn(
"%s Will try to get wcs from calexp.", e)
302 for ccdId
in ccdIdList:
304 ccdKey, ccdId, visit, visitSummary=visitSummary, butler=butler, doLogWarn=
False)
305 if raCorners
is not None and decCorners
is not None:
307 for ra, dec
in zip(raCorners, decCorners):
310 detSphCorners.append(pt)
311 ptSkyCoord = SkyCoord(ra*units.deg, dec*units.deg)
312 separation = (midSkyCoord.separation(ptSkyCoord)).degree
313 if separation < minDistToMidCood:
316 minDistToMidCood = separation
317 detConvexHull = sphgeom.ConvexPolygon(
318 [coord.getVector()
for coord
in detSphCorners])
319 if detConvexHull.contains(midRa, midDec):
320 logger.info(
"visit/det overlapping plot coord mid point in RA/Dec: %d %d", visit, ccdId)
321 raToDecLimitRatio = (
max(raCorners) -
min(raCorners))/(
max(decCorners) -
min(decCorners))
323 width = det.getBBox().getWidth()
324 height = det.getBBox().getHeight()
325 if raToDecLimitRatio > 1.0:
326 raToDecLimitRatio /=
max(height/width, width/height)
328 if raToDecLimitRatio < 1.0:
329 raToDecLimitRatio *=
max(height/width, width/height)
331 if raToDecLimitRatio
is not None:
334 if raToDecLimitRatio
is None:
336 visitSummary = butler.get(
"visitSummary", visit=minSepVisit)
337 except LookupError
as e:
338 logger.warn(
"%s Will try to get wcs from calexp.", e)
341 ccdKey, minSepCcdId, minSepVisit, visitSummary=visitSummary, butler=butler, doLogWarn=
False)
342 for ra, dec
in zip(raCorners, decCorners):
345 detSphCorners.append(pt)
346 detConvexHull = sphgeom.ConvexPolygon([coord.getVector()
for coord
in detSphCorners])
347 logger.info(
"visit/det closest to plot coord mid point in RA/Dec (none actually overlat it): %d %d",
348 minSepVisit, minSepCcdId)
349 raToDecLimitRatio = (
max(raCorners) -
min(raCorners))/(
max(decCorners) -
min(decCorners))
350 det = camera[minSepCcdId]
351 width = det.getBBox().getWidth()
352 height = det.getBBox().getHeight()
353 if raToDecLimitRatio > 1.0:
354 raToDecLimitRatio /=
max(height/width, width/height)
356 if raToDecLimitRatio < 1.0:
357 raToDecLimitRatio *=
max(height/width, width/height)
359 if trimToTracts
is True:
360 xlim, ylim =
derivePlotLimits(tractLimitsDict, raToDecLimitRatio=raToDecLimitRatio, buffFrac=0.04)
362 visitLimitsDict = {
"allVisits": {
"ras": [minVisitRa, maxVisitRa],
"decs": [minVisitDec, maxVisitDec]}}
363 xlim, ylim =
derivePlotLimits(visitLimitsDict, raToDecLimitRatio=raToDecLimitRatio, buffFrac=0.04)
369 if tracts
is not None:
370 tractOutlineList = list(
set(tracts + tractList))
372 tractOutlineList = tractList
373 tractOutlineList.sort()
374 logger.info(
"List of tract outlines being plotted: {}".format(tractOutlineList))
375 for i_t, tract
in enumerate(tractOutlineList):
376 alpha =
max(0.1, alpha0 - i_t*1.0/len(tractOutlineList))
377 tractInfo = skymap[tract]
378 tCenter = tractInfo.ctr_coord
379 tCenterRa = tCenter.getRa().asDegrees()
380 tCenterDec = tCenter.getDec().asDegrees()
381 fracDeltaX = 0.02*abs((xlim[1] - xlim[0]))
382 fracDeltaY = 0.02*abs((ylim[1] - ylim[0]))
383 if (xlim[1] + fracDeltaX < tCenterRa < xlim[0] - fracDeltaX
384 and ylim[0] + fracDeltaY < tCenterDec < ylim[1] - fracDeltaY):
385 if len(tractOutlineList) > 1
or not showPatch:
386 plt.text(tCenterRa, tCenterDec, tract, fontsize=7, alpha=alpha, ha=
"center", va=
"center")
387 ra, dec =
bboxToRaDec(tractInfo.bbox, tractInfo.getWcs())
388 plt.fill(ra, dec, fill=
False, edgecolor=
"k", lw=1, linestyle=
"dashed", alpha=alpha)
389 tractArtist = mpatches.Patch(fill=
False, edgecolor=
"k", linestyle=
"dashed", alpha=alpha)
390 tractHandleList.append(tractArtist)
391 tractStrList.append(str(tract))
394 for patch
in tractInfo:
395 ra, dec =
bboxToRaDec(patch.getInnerBBox(), tractInfo.getWcs())
396 plt.fill(ra, dec, fill=
False, edgecolor=patchColor, lw=0.5, linestyle=(0, (5, 6)),
401 str(patch.sequential_index), fontsize=5, color=patchColor,
402 ha=
"center", va=
"center", alpha=alpha)
409 if abs(xlim[1] > 99.99):
410 ax.tick_params(
"x", labelrotation=45, pad=0, labelsize=8)
412 ax.tick_params(
"x", labelrotation=0, pad=0, labelsize=8)
413 ax.tick_params(
"y", labelsize=8)
414 ax.set_xlabel(
"RA (deg)", fontsize=9)
415 ax.set_ylabel(
"Dec (deg)", fontsize=9)
417 visitScaleOffset =
None
418 if len(visitIncludeList) > maxVisitForLegend:
419 nz = matplotlib.colors.Normalize()
420 colorBarScale = finalVisitList
421 if max(finalVisitList) > 9999999:
422 visitScaleOffset =
min(finalVisitList)
423 colorBarScale = [visit - visitScaleOffset
for visit
in finalVisitList]
424 nz.autoscale(colorBarScale)
425 cax, _ = matplotlib.colorbar.make_axes(plt.gca(), pad=0.03)
426 cax.tick_params(labelsize=7)
427 cb = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=nz, alpha=alphaEdge,
428 format=
lambda x, _: f
"{x:.0f}")
429 cb.ax.yaxis.get_offset_text().set_fontsize(7)
430 colorBarLabel =
"visit number"
431 if visitScaleOffset
is not None:
432 colorBarLabel +=
" - {:d}".format(visitScaleOffset)
433 cb.set_label(colorBarLabel, rotation=-90, labelpad=13, fontsize=9)
434 tractLegend = Legend(ax, tractHandleList, tractStrList, loc=
"upper right",
435 fancybox=
True, shadow=
True, fontsize=5, title_fontsize=6, title=
"tracts")
436 ax.add_artist(tractLegend)
438 if len(visitIncludeList) > 0:
439 ax.legend(loc=
"center left", bbox_to_anchor=(1.15, 0.5), fancybox=
True, shadow=
True,
440 fontsize=6, title_fontsize=6, title=
"visits")
442 tractLegend = Legend(ax, tractHandleList, tractStrList, loc=
"center left", bbox_to_anchor=(1.0, 0.5),
443 fancybox=
True, shadow=
True, fontsize=6, title_fontsize=6, title=
"tracts")
444 ax.add_artist(tractLegend)
446 titleStr = repo +
"\n" + collections[0]
447 if len(collections) > 1:
448 for collection
in collections[1:]:
449 titleStr +=
"\n" + collection
450 titleStr +=
"\nnVisit: {}".format(str(len(finalVisitList)))
451 if minOverlapFraction
is not None:
452 titleStr +=
" (minOvlpFrac = {:.2f})".format(minOverlapFraction)
453 if len(includedBands) > 0:
454 titleStr +=
" bands: {}".format(str(includedBands).translate({ord(i):
None for i
in "[]\'"}))
455 if len(includedPhysicalFilters) > 0:
456 if len(includedPhysicalFilters[0]) > 9:
458 titleStr +=
" physical filters: {}".format(str(includedPhysicalFilters).translate(
459 {ord(i):
None for i
in "[]\'"}))
460 ax.set_title(
"{}".format(titleStr), fontsize=8)
463 if saveFile
is not None:
464 logger.info(
"Saving file in: %s", saveFile)
465 fig.savefig(saveFile, bbox_inches=
"tight", dpi=150)
522 """Derive the axis limits to encompass all points in limitsDict.
526 limitsDict : `dict` [`dict`]
527 A dictionary keyed on any id. Each entry includes a `dict`
528 keyed on "ras" and "decs" including (at least the minimum
529 and maximum) RA and Dec values in units of degrees.
530 raToDecLimitRatio : `float`, optional
531 The aspect ratio between RA and Dec to set the plot limits to. This
532 is to namely to set this ratio to that of the focal plane (i.e. such
533 that a square detector appears as a square), but any aspect ratio can,
534 in principle, be requested.
538 xlim, ylim : `tuple` [`float`]
539 Two tuples containing the derived min and max values for the x and
540 y-axis limits (in degrees), respectively.
542 xLimMin, yLimMin = 1e12, 1e12
543 xLimMax, yLimMax = -1e12, -1e12
544 for limitId, limits
in limitsDict.items():
545 xLimMin =
min(xLimMin,
min(limits[
"ras"]))
546 xLimMax =
max(xLimMax,
max(limits[
"ras"]))
547 yLimMin =
min(yLimMin,
min(limits[
"decs"]))
548 yLimMax =
max(yLimMax,
max(limits[
"decs"]))
549 xDelta0 = xLimMax - xLimMin
550 yDelta0 = yLimMax - yLimMin
552 if raToDecLimitRatio == 1.0:
553 if xDelta0 > yDelta0:
554 xLimMin -= buffFrac*yDelta0
555 xLimMax += buffFrac*yDelta0
557 yLimMin -= buffFrac*yDelta0
558 yLimMax += buffFrac*yDelta0
561 xLimMin -= buffFrac*xDelta0
562 xLimMax += buffFrac*xDelta0
563 yLimMin -= buffFrac*yDelta0
564 yLimMax += buffFrac*yDelta0
566 xDelta = xLimMax - xLimMin
567 yDelta = yLimMax - yLimMin
568 if raToDecLimitRatio > 1.0:
570 xMid = xLimMin + 0.5*(xDelta)
571 xLimMin = xMid - 0.5*yDelta*raToDecLimitRatio
572 xLimMax = xMid + 0.5*yDelta*raToDecLimitRatio
574 yMid = yLimMin + 0.5*(yDelta)
575 yLimMin = yMid - 0.5*xDelta/raToDecLimitRatio
576 yLimMax = yMid + 0.5*xDelta/raToDecLimitRatio
578 if xDelta0 > yDelta0:
579 yMid = yLimMin + 0.5*(yDelta)
580 yLimMin = yMid - 0.5*xDelta/raToDecLimitRatio
581 yLimMax = yMid + 0.5*xDelta/raToDecLimitRatio
583 xMid = xLimMin + 0.5*(xDelta)
584 xLimMin = xMid - 0.5*yDelta*raToDecLimitRatio
585 xLimMax = xMid + 0.5*yDelta*raToDecLimitRatio
586 xlim = xLimMax, xLimMin
587 ylim = yLimMin, yLimMax