77def main(repo, collections, skymapName=None, tracts=None, visits=None, physicalFilters=None, bands=None,
78 ccds=None, ccdKey="detector", showPatch=False, saveFile=None, showCcds=False, visitVetoFile=None,
79 minOverlapFraction=None, trimToTracts=False):
80 if minOverlapFraction
is not None and tracts
is None:
81 raise RuntimeError(
"Must specify --tracts if --minOverlapFraction is set")
82 logger.info(
"Making butler for collections = {} in repo {}".format(collections, repo))
83 butler = dafButler.Butler(repo, collections=collections)
84 instrument = butler.registry.findDataset(
"camera").dataId[
"instrument"]
87 if skymapName
is None:
88 if instrument ==
"HSC":
89 skymapName =
"hsc_rings_v1"
90 detectorSkipList = [9]
91 elif instrument ==
"LSSTCam-imSim":
93 elif instrument ==
"LATISS":
94 skymapName =
"latiss_v1"
95 elif instrument ==
"DECam":
96 skymapName =
"decam_rings_v1"
98 logger.error(
"Unknown skymapName for instrument: %s. Must specify --skymapName on command line.",
100 logger.info(
"instrument = {} skymapName = {}".format(instrument, skymapName))
101 camera = butler.get(
"camera", instrument=instrument)
102 skymap = butler.get(
"skyMap", instrument=instrument, skymap=skymapName)
105 if tracts
is not None:
109 if physicalFilters
is not None:
110 physicalFilterStr =
makeWhereInStr(
"physical_filter", physicalFilters, str)
111 whereStr +=
" AND " + physicalFilterStr
if len(whereStr)
else " " + physicalFilterStr
113 if bands
is not None:
115 whereStr +=
" AND " + bandStr
if len(whereStr)
else " " + bandStr
117 if len(whereStr) > 1:
118 whereStr =
"instrument=\'" + instrument +
"\' AND skymap=\'" + skymapName +
"\' AND " + whereStr
120 whereStr =
"instrument=\'" + instrument +
"\' AND skymap=\'" + skymapName +
"\'"
121 logger.info(
"Applying the following where clause in dataId search: {} ".format(whereStr))
124 if visitVetoFile
is not None:
125 with open(visitVetoFile)
as f:
126 content = f.readlines()
127 visitVetoList = [
int(visit.strip())
for visit
in content]
130 dataRefs = list(butler.registry.queryDatasets(
"calexp", where=whereStr).expanded())
132 for dataRef
in dataRefs:
133 visit = dataRef.dataId.visit.id
134 if visit
not in visits
and visit
not in visitVetoList:
137 logger.info(
"List of visits (N={}) satisfying where and veto clauses: {}".format(len(visits),
140 if len(visitVetoList) > 1:
141 visitListTemp = visits.copy()
142 for visit
in visitListTemp:
143 if visit
in visitVetoList:
145 logger.info(
"List of visits (N={}) excluding veto list: {}".format(len(visits), visits))
150 if ((ccds
is None or ccdId
in ccds)
and ccd.getType() == cameraGeom.DetectorType.SCIENCE
151 and ccdId
not in detectorSkipList):
152 ccdIdList.append(ccdId)
154 nDetTot = len(ccdIdList)
156 visitIncludeList = []
160 if minOverlapFraction
is not None:
161 for i_v, visit
in enumerate(visits):
164 visitSummary = butler.get(
"visitSummary", visit=visit)
165 except LookupError
as e:
166 logger.warn(
"%s Will try to get wcs from calexp.", e)
168 if tracts
is not None:
170 tractInfo = skymap[tract]
171 sphCorners = tractInfo.wcs.pixelToSky(geom.Box2D(tractInfo.bbox).getCorners())
172 tractConvexHull = sphgeom.ConvexPolygon.convexHull(
173 [coord.getVector()
for coord
in sphCorners])
174 for ccdId
in ccdIdList:
175 if ccdId
not in ccdOverlapList:
177 ccdKey, ccdId, visit, visitSummary=visitSummary, butler=butler,
179 if raCorners
is not None and decCorners
is not None:
181 for ra, dec
in zip(raCorners, decCorners):
182 pt = geom.SpherePoint(geom.Angle(ra, geom.degrees),
183 geom.Angle(dec, geom.degrees))
184 detSphCorners.append(pt)
185 detConvexHull = sphgeom.ConvexPolygon.convexHull(
186 [coord.getVector()
for coord
in detSphCorners])
187 if tractConvexHull.contains(detConvexHull):
188 ccdOverlapList.append(ccdId)
190 if len(ccdOverlapList)/nDetTot >= minOverlapFraction:
192 if len(ccdOverlapList)/nDetTot < minOverlapFraction:
193 logger.info(
"Fraction of detectors overlaping any tract for visit %d (%.2f) < "
194 "minimum required (%.2f). Skipping visit...",
195 visit, len(ccdOverlapList)/nDetTot, minOverlapFraction)
197 if visit
not in visitIncludeList:
198 visitIncludeList.append(visit)
200 visitIncludeList = visits
205 cmap =
get_cmap(len(visitIncludeList))
207 maxVisitForLegend = 20
210 includedPhysicalFilters = []
211 for i_v, visit
in enumerate(visitIncludeList):
212 print(
"Working on visit %d [%d of %d]" % (visit, i_v + 1, len(visitIncludeList)), end=
"\r")
215 fillKwargs = {
"fill":
False,
"alpha": alphaEdge,
"facecolor":
None,
"edgecolor": color,
"lw": 0.6}
217 visitSummary = butler.get(
"visitSummary", visit=visit)
218 except LookupError
as e:
219 logger.warn(
"%s Will try to get wcs from calexp.", e)
222 band, physicalFilter =
getBand(visitSummary=visitSummary, butler=butler, visit=visit)
223 if band
not in includedBands:
224 includedBands.append(band)
225 if physicalFilter
not in includedPhysicalFilters:
226 includedPhysicalFilters.append(physicalFilter)
228 for ccdId
in ccdIdList:
230 ccdKey, ccdId, visit, visitSummary=visitSummary, butler=butler)
231 if raCorners
is not None and decCorners
is not None:
234 if not inLegend
and len(visitIncludeList) <= maxVisitForLegend:
235 plt.fill(raCorners, decCorners, label=
str(visit), **fillKwargs)
238 plt.fill(raCorners, decCorners, **fillKwargs)
239 plt.fill(raCorners, decCorners, fill=
True, alpha=alphaEdge/4, color=color,
241 if visit
not in finalVisitList:
242 finalVisitList.append(visit)
246 deltaRa = max(raCorners) - min(raCorners)
247 deltaDec = max(decCorners) - min(decCorners)
248 minPoint = geom.Point2D(min(raCorners) + overlapFrac*deltaRa,
249 min(decCorners) + overlapFrac*deltaDec)
250 maxPoint = geom.Point2D(max(raCorners) - overlapFrac*deltaRa,
251 max(decCorners) - overlapFrac*deltaDec)
253 bboxDouble = geom.Box2D(minPoint, maxPoint)
254 overlaps = [
not bboxDouble.overlaps(otherBbox)
for otherBbox
in bboxesPlotted]
257 str(ccdId), fontsize=6, ha=
"center", va=
"center", color=
"darkblue")
258 bboxesPlotted.append(bboxDouble)
260 logger.info(
"Final list of visits (N={}) satisfying where and minOverlapFraction clauses: {}"
261 .format(len(finalVisitList), finalVisitList))
263 raToDecLimitRatio =
None
265 tractList = list(set(skymap.findTractIdArray(ras, decs, degrees=
True)))
266 minVisitRa, maxVisitRa = min(ras), max(ras)
267 minVisitDec, maxVisitDec = min(decs), max(decs)
268 raVisitDiff = maxVisitRa - minVisitRa
269 decVisitDiff = maxVisitDec - minVisitDec
270 midVisitRa = minVisitRa + 0.5*raVisitDiff
271 midVisitDec = minVisitDec + 0.5*decVisitDiff
272 midRa = np.atleast_1d((midVisitRa*units.deg).to(units.radian).value).astype(np.float64)
273 midDec = np.atleast_1d((midVisitDec*units.deg).to(units.radian).value).astype(np.float64)
274 midSkyCoord = SkyCoord(midVisitRa*units.deg, midVisitDec*units.deg)
276 if tracts
is not None:
277 logger.info(
"No calexps were found, but --tracts list was provided, so will go ahead and "
278 "plot the empty tracts.")
281 raToDecLimitRatio = 1.0
283 raise RuntimeError(
"No data to plot (if you want to plot empty tracts, include them as "
284 "a blank-space separated list to the --tracts option.")
286 logger.info(
"List of tracts overlapping data: {}".format(tractList))
291 minDistToMidCood = 1e12
294 for i_v, visit
in enumerate(visits):
296 visitSummary = butler.get(
"visitSummary", visit=visit)
297 except LookupError
as e:
298 logger.warn(
"%s Will try to get wcs from calexp.", e)
300 for ccdId
in ccdIdList:
302 ccdKey, ccdId, visit, visitSummary=visitSummary, butler=butler, doLogWarn=
False)
303 if raCorners
is not None and decCorners
is not None:
305 for ra, dec
in zip(raCorners, decCorners):
306 pt = geom.SpherePoint(geom.Angle(ra, geom.degrees),
307 geom.Angle(dec, geom.degrees))
308 detSphCorners.append(pt)
309 ptSkyCoord = SkyCoord(ra*units.deg, dec*units.deg)
310 separation = (midSkyCoord.separation(ptSkyCoord)).degree
311 if separation < minDistToMidCood:
314 minDistToMidCood = separation
315 detConvexHull = sphgeom.ConvexPolygon(
316 [coord.getVector()
for coord
in detSphCorners])
317 if detConvexHull.contains(midRa, midDec):
318 logger.info(
"visit/det overlapping plot coord mid point in RA/Dec: %d %d", visit, ccdId)
319 raToDecLimitRatio = (max(raCorners) - min(raCorners))/(max(decCorners) - min(decCorners))
321 width = det.getBBox().getWidth()
322 height = det.getBBox().getHeight()
323 if raToDecLimitRatio > 1.0:
324 raToDecLimitRatio /= max(height/width, width/height)
326 if raToDecLimitRatio < 1.0:
327 raToDecLimitRatio *= max(height/width, width/height)
329 if raToDecLimitRatio
is not None:
332 if raToDecLimitRatio
is None:
334 visitSummary = butler.get(
"visitSummary", visit=minSepVisit)
335 except LookupError
as e:
336 logger.warn(
"%s Will try to get wcs from calexp.", e)
339 ccdKey, minSepCcdId, minSepVisit, visitSummary=visitSummary, butler=butler, doLogWarn=
False)
340 for ra, dec
in zip(raCorners, decCorners):
341 pt = geom.SpherePoint(geom.Angle(ra, geom.degrees),
342 geom.Angle(dec, geom.degrees))
343 detSphCorners.append(pt)
344 detConvexHull = sphgeom.ConvexPolygon([coord.getVector()
for coord
in detSphCorners])
345 logger.info(
"visit/det closest to plot coord mid point in RA/Dec (none actually overlat it): %d %d",
346 minSepVisit, minSepCcdId)
347 raToDecLimitRatio = (max(raCorners) - min(raCorners))/(max(decCorners) - min(decCorners))
348 det = camera[minSepCcdId]
349 width = det.getBBox().getWidth()
350 height = det.getBBox().getHeight()
351 if raToDecLimitRatio > 1.0:
352 raToDecLimitRatio /= max(height/width, width/height)
354 if raToDecLimitRatio < 1.0:
355 raToDecLimitRatio *= max(height/width, width/height)
357 if trimToTracts
is True:
358 xlim, ylim =
derivePlotLimits(tractLimitsDict, raToDecLimitRatio=raToDecLimitRatio, buffFrac=0.04)
360 visitLimitsDict = {
"allVisits": {
"ras": [minVisitRa, maxVisitRa],
"decs": [minVisitDec, maxVisitDec]}}
361 xlim, ylim =
derivePlotLimits(visitLimitsDict, raToDecLimitRatio=raToDecLimitRatio, buffFrac=0.04)
367 if tracts
is not None:
368 tractOutlineList = list(set(tracts + tractList))
370 tractOutlineList = tractList
371 tractOutlineList.sort()
372 logger.info(
"List of tract outlines being plotted: {}".format(tractOutlineList))
373 for i_t, tract
in enumerate(tractOutlineList):
374 alpha = max(0.1, alpha0 - i_t*1.0/len(tractOutlineList))
375 tractInfo = skymap[tract]
376 tCenter = tractInfo.ctr_coord
377 tCenterRa = tCenter.getRa().asDegrees()
378 tCenterDec = tCenter.getDec().asDegrees()
379 fracDeltaX = 0.02*abs((xlim[1] - xlim[0]))
380 fracDeltaY = 0.02*abs((ylim[1] - ylim[0]))
381 if (xlim[1] + fracDeltaX < tCenterRa < xlim[0] - fracDeltaX
382 and ylim[0] + fracDeltaY < tCenterDec < ylim[1] - fracDeltaY):
383 if len(tractOutlineList) > 1
or not showPatch:
384 plt.text(tCenterRa, tCenterDec, tract, fontsize=7, alpha=alpha, ha=
"center", va=
"center")
385 ra, dec =
bboxToRaDec(tractInfo.bbox, tractInfo.getWcs())
386 plt.fill(ra, dec, fill=
False, edgecolor=
"k", lw=1, linestyle=
"dashed", alpha=alpha)
387 tractArtist = mpatches.Patch(fill=
False, edgecolor=
"k", linestyle=
"dashed", alpha=alpha)
388 tractHandleList.append(tractArtist)
389 tractStrList.append(
str(tract))
392 for patch
in tractInfo:
393 ra, dec =
bboxToRaDec(patch.getInnerBBox(), tractInfo.getWcs())
394 plt.fill(ra, dec, fill=
False, edgecolor=patchColor, lw=0.5, linestyle=(0, (5, 6)),
399 str(patch.sequential_index), fontsize=5, color=patchColor,
400 ha=
"center", va=
"center", alpha=alpha)
407 if abs(xlim[1] > 99.99):
408 ax.tick_params(
"x", labelrotation=45, pad=0, labelsize=8)
410 ax.tick_params(
"x", labelrotation=0, pad=0, labelsize=8)
411 ax.tick_params(
"y", labelsize=8)
412 ax.set_xlabel(
"RA (deg)", fontsize=9)
413 ax.set_ylabel(
"Dec (deg)", fontsize=9)
415 visitScaleOffset =
None
416 if len(visitIncludeList) > maxVisitForLegend:
417 nz = matplotlib.colors.Normalize()
418 colorBarScale = finalVisitList
419 if max(finalVisitList) > 9999999:
420 visitScaleOffset = min(finalVisitList)
421 colorBarScale = [visit - visitScaleOffset
for visit
in finalVisitList]
422 nz.autoscale(colorBarScale)
423 cax, _ = matplotlib.colorbar.make_axes(plt.gca(), pad=0.03)
424 cax.tick_params(labelsize=7)
425 cb = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=nz, alpha=alphaEdge,
426 format=
lambda x, _: f
"{x:.0f}")
427 cb.ax.yaxis.get_offset_text().set_fontsize(7)
428 colorBarLabel =
"visit number"
429 if visitScaleOffset
is not None:
430 colorBarLabel +=
" - {:d}".format(visitScaleOffset)
431 cb.set_label(colorBarLabel, rotation=-90, labelpad=13, fontsize=9)
432 tractLegend = Legend(ax, tractHandleList, tractStrList, loc=
"upper right",
433 fancybox=
True, shadow=
True, fontsize=5, title_fontsize=6, title=
"tracts")
434 ax.add_artist(tractLegend)
436 if len(visitIncludeList) > 0:
437 ax.legend(loc=
"center left", bbox_to_anchor=(1.15, 0.5), fancybox=
True, shadow=
True,
438 fontsize=6, title_fontsize=6, title=
"visits")
440 tractLegend = Legend(ax, tractHandleList, tractStrList, loc=
"center left", bbox_to_anchor=(1.0, 0.5),
441 fancybox=
True, shadow=
True, fontsize=6, title_fontsize=6, title=
"tracts")
442 ax.add_artist(tractLegend)
444 titleStr = repo +
"\n" + collections[0]
445 if len(collections) > 1:
446 for collection
in collections[1:]:
447 titleStr +=
"\n" + collection
448 titleStr +=
"\nnVisit: {}".format(
str(len(finalVisitList)))
449 if minOverlapFraction
is not None:
450 titleStr +=
" (minOvlpFrac = {:.2f})".format(minOverlapFraction)
451 if len(includedBands) > 0:
452 titleStr +=
" bands: {}".format(
str(includedBands).translate({ord(i):
None for i
in "[]\'"}))
453 if len(includedPhysicalFilters) > 0:
454 if len(includedPhysicalFilters[0]) > 9:
456 titleStr +=
" physical filters: {}".format(
str(includedPhysicalFilters).translate(
457 {ord(i):
None for i
in "[]\'"}))
458 ax.set_title(
"{}".format(titleStr), fontsize=8)
461 if saveFile
is not None:
462 logger.info(
"Saving file in: %s", saveFile)
463 fig.savefig(saveFile, bbox_inches=
"tight", dpi=150)
520 """Derive the axis limits to encompass all points in limitsDict.
524 limitsDict : `dict` [`dict`]
525 A dictionary keyed on any id. Each entry includes a `dict`
526 keyed on "ras" and "decs" including (at least the minimum
527 and maximum) RA and Dec values in units of degrees.
528 raToDecLimitRatio : `float`, optional
529 The aspect ratio between RA and Dec to set the plot limits to. This
530 is to namely to set this ratio to that of the focal plane (i.e. such
531 that a square detector appears as a square), but any aspect ratio can,
532 in principle, be requested.
536 xlim, ylim : `tuple` [`float`]
537 Two tuples containing the derived min and max values for the x and
538 y-axis limits (in degrees), respectively.
540 xLimMin, yLimMin = 1e12, 1e12
541 xLimMax, yLimMax = -1e12, -1e12
542 for limitId, limits
in limitsDict.items():
543 xLimMin = min(xLimMin, min(limits[
"ras"]))
544 xLimMax = max(xLimMax, max(limits[
"ras"]))
545 yLimMin = min(yLimMin, min(limits[
"decs"]))
546 yLimMax = max(yLimMax, max(limits[
"decs"]))
547 xDelta0 = xLimMax - xLimMin
548 yDelta0 = yLimMax - yLimMin
550 if raToDecLimitRatio == 1.0:
551 if xDelta0 > yDelta0:
552 xLimMin -= buffFrac*yDelta0
553 xLimMax += buffFrac*yDelta0
555 yLimMin -= buffFrac*yDelta0
556 yLimMax += buffFrac*yDelta0
559 xLimMin -= buffFrac*xDelta0
560 xLimMax += buffFrac*xDelta0
561 yLimMin -= buffFrac*yDelta0
562 yLimMax += buffFrac*yDelta0
564 xDelta = xLimMax - xLimMin
565 yDelta = yLimMax - yLimMin
566 if raToDecLimitRatio > 1.0:
568 xMid = xLimMin + 0.5*(xDelta)
569 xLimMin = xMid - 0.5*yDelta*raToDecLimitRatio
570 xLimMax = xMid + 0.5*yDelta*raToDecLimitRatio
572 yMid = yLimMin + 0.5*(yDelta)
573 yLimMin = yMid - 0.5*xDelta/raToDecLimitRatio
574 yLimMax = yMid + 0.5*xDelta/raToDecLimitRatio
576 if xDelta0 > yDelta0:
577 yMid = yLimMin + 0.5*(yDelta)
578 yLimMin = yMid - 0.5*xDelta/raToDecLimitRatio
579 yLimMax = yMid + 0.5*xDelta/raToDecLimitRatio
581 xMid = xLimMin + 0.5*(xDelta)
582 xLimMin = xMid - 0.5*yDelta*raToDecLimitRatio
583 xLimMax = xMid + 0.5*yDelta*raToDecLimitRatio
584 xlim = xLimMax, xLimMin
585 ylim = yLimMin, yLimMax