Loading [MathJax]/extensions/tex2jax.js
LSST Applications g0d33ba9806+3d1aa5cd78,g0fba68d861+6c07529581,g1e78f5e6d3+aa4d1c21f1,g1ec0fe41b4+f536777771,g1fd858c14a+c6963eae98,g35bb328faa+fcb1d3bbc8,g4af146b050+58cb980876,g4d2262a081+bfae794ebc,g53246c7159+fcb1d3bbc8,g5a012ec0e7+b20b785ecb,g60b5630c4e+3d1aa5cd78,g67b6fd64d1+4086c0989b,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g87b7deb4dc+a8b896a16a,g8852436030+75f93ca278,g89139ef638+4086c0989b,g9125e01d80+fcb1d3bbc8,g94187f82dc+3d1aa5cd78,g989de1cb63+4086c0989b,g9f33ca652e+94dd9a85be,g9f7030ddb1+3f50642ad9,ga2b97cdc51+3d1aa5cd78,ga44b1db4f6+7d2d5e68ea,gabe3b4be73+1e0a283bba,gabf8522325+fa80ff7197,gb1101e3267+96cb2ddcf2,gb58c049af0+f03b321e39,gb89ab40317+4086c0989b,gcf25f946ba+75f93ca278,gd6cbbdb0b4+af3c3595f5,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+ed90e8109d,ge278dab8ac+d65b3c2b70,ge410e46f29+4086c0989b,gf67bdafdda+4086c0989b,gfe06eef73a+e2ab3e8e4f,v29.0.0.rc5
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
firefly.py
Go to the documentation of this file.
2# LSST Data Management System
3# Copyright 2008, 2009, 2010, 2015 LSST Corporation.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <http://www.lsstcorp.org/LegalNotices/>.
21#
22
23import logging
24from io import BytesIO
25from socket import gaierror
26
27import lsst.afw.display.interface as interface
28import lsst.afw.display.virtualDevice as virtualDevice
29import lsst.afw.display.ds9Regions as ds9Regions
30import lsst.afw.display as afwDisplay
31import lsst.afw.math as afwMath
32
33from .footprints import createFootprintsTable
34
35try:
36 import firefly_client
37 _fireflyClient = None
38except ImportError as e:
39 raise RuntimeError(f"Cannot import firefly_client: {e}")
40from ws4py.client import HandshakeError
41
42_LOG = logging.getLogger(__name__)
43
44
45class FireflyError(Exception):
46
47 def __init__(self, str):
48 Exception.__init__(self, str)
49
50
52 """Return the version of firefly_client in use, as a string"""
53 return firefly_client.__version__
54
55
57 """Device to talk to a firefly display"""
58
59 @staticmethod
60 def __handleCallbacks(event):
61 if 'type' in event['data']:
62 if event['data']['type'] == 'AREA_SELECT':
63 _LOG.debug('*************area select')
64 pParams = {'URL': 'http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits',
65 'ColorTable': '9'}
66 plot_id = 3
67 global _fireflyClient
68 _fireflyClient.show_fits(fileOnServer=None, plot_id=plot_id, additionalParams=pParams)
69
70 _LOG.debug("Callback event info: %s", event)
71 return
72 data = dict(_.split('=') for _ in event.get('data', {}).split('&'))
73 if data.get('type') == "POINT":
74 _LOG.debug("Event Received: %s", data.get('id'))
75
76 def __init__(self, display, verbose=False, url=None,
77 name=None, *args, **kwargs):
78 virtualDevice.DisplayImpl.__init__(self, display, verbose)
79
80 if self.verbose:
81 print("Opening firefly device %s" % (self.display.frame if self.display else "[None]"))
82
83 global _fireflyClient
84 if not _fireflyClient:
85 import os
86 start_tab = None
87 html_file = kwargs.get('html_file',
88 os.environ.get('FIREFLY_HTML', ''))
89 if url is None:
90 if (('fireflyLabExtension' in os.environ) and
91 ('fireflyURLLab' in os.environ)):
92 url = os.environ['fireflyURLLab']
93 start_tab = kwargs.get('start_tab', True)
94 start_browser_tab = kwargs.get('start_browser_tab', False)
95 if (name is None) and ('fireflyChannelLab' in os.environ):
96 name = os.environ['fireflyChannelLab']
97 elif 'FIREFLY_URL' in os.environ:
98 url = os.environ['FIREFLY_URL']
99 else:
100 raise RuntimeError('Cannot determine url from environment; you must pass url')
101
102 token = kwargs.get('token',
103 os.environ.get('ACCESS_TOKEN', None))
104
105 try:
106 if start_tab:
107 if verbose:
108 print('Starting Jupyterlab client')
109 _fireflyClient = firefly_client.FireflyClient.make_lab_client(
110 start_tab=True, start_browser_tab=start_browser_tab,
111 html_file=html_file, verbose=verbose,
112 token=token)
113
114 else:
115 if verbose:
116 print('Starting vanilla client')
117 _fireflyClient = firefly_client.FireflyClient.make_client(
118 url=url, html_file=html_file, launch_browser=True,
119 channel_override=name, verbose=verbose,
120 token=token)
121
122 except (HandshakeError, gaierror) as e:
123 raise RuntimeError(f"Unable to connect to {url or ''}: {e}")
124
125 try:
126 _fireflyClient.add_listener(self.__handleCallbacks)
127 except Exception as e:
128 raise RuntimeError("Cannot add listener. Browser must be connected"
129 f"to {_fireflyClient.get_firefly_url()}: {e}")
130
131 self._isBuffered = False
132 self._regions = []
134 self._fireflyFitsID = None
136 self._client = _fireflyClient
137 self._channel = _fireflyClient.channel
138 self._url = _fireflyClient.get_firefly_url()
139 self._maskIds = []
140 self._maskDict = {}
143 self._lastZoom = None
144 self._lastPan = None
145 self._lastStretch = None
146
148 return f"lsstRegions{self.display.frame}" if self.display else "None"
149
150 def _clearImage(self):
151 """Delete the current image in the Firefly viewer
152 """
153 self._client.dispatch(action_type='ImagePlotCntlr.deletePlotView',
154 payload=dict(plotId=str(self.display.frame)))
155
156 def _mtv(self, image, mask=None, wcs=None, title="", metadata=None):
157 """Display an Image and/or Mask on a Firefly display
158 """
159 if title == "":
160 title = str(self.display.frame)
161 if image:
162 if self.verbose:
163 print('displaying image')
164 self._erase()
165
166 with BytesIO() as fd:
167 afwDisplay.writeFitsImage(fd, image, wcs, title, metadata=metadata)
168 fd.seek(0, 0)
169 self._fireflyFitsID = _fireflyClient.upload_fits_data(fd)
170
171 try:
172 viewer_id = f'image-{_fireflyClient.render_tree_id}-{self.frame}'
173 except AttributeError:
174 viewer_id = f'image-{self.frame}'
175 extraParams = dict(Title=title,
176 MultiImageIdx=0,
177 PredefinedOverlayIds=' ',
178 viewer_id=viewer_id)
179 # Firefly's Javascript API requires a space for parameters;
180 # otherwise the parameter will be ignored
181
182 if self._lastZoom:
183 extraParams['InitZoomLevel'] = self._lastZoom
184 extraParams['ZoomType'] = 'LEVEL'
185 if self._lastPan:
186 extraParams['InitialCenterPosition'] = f'{self._lastPan[0]:.3f};{self._lastPan[1]:.3f};PIXEL'
187 if self._lastStretch:
188 extraParams['RangeValues'] = self._lastStretch
189
190 ret = _fireflyClient.show_fits(self._fireflyFitsID, plot_id=str(self.display.frame),
191 **extraParams)
192
193 if not ret["success"]:
194 raise RuntimeError("Display of image failed")
195
196 if mask:
197 if self.verbose:
198 print('displaying mask')
199 with BytesIO() as fdm:
200 afwDisplay.writeFitsImage(fdm, mask, wcs, title, metadata=metadata)
201 fdm.seek(0, 0)
202 self._fireflyMaskOnServer = _fireflyClient.upload_fits_data(fdm)
203
204 maskPlaneDict = mask.getMaskPlaneDict()
205 for k, v in maskPlaneDict.items():
206 self._maskDict[k] = v
207 self._maskPlaneColors[k] = self.display.getMaskPlaneColor(k)
208 usedPlanes = int(afwMath.makeStatistics(mask, afwMath.SUM).getValue())
209 for k in self._maskDict:
210 if (((1 << self._maskDict[k]) & usedPlanes) and
211 (k in self._maskPlaneColors) and
212 (self._maskPlaneColors[k] is not None) and
213 (self._maskPlaneColors[k].lower() != 'ignore')):
214 _fireflyClient.add_mask(bit_number=self._maskDict[k],
215 image_number=0,
216 plot_id=str(self.display.frame),
217 mask_id=k,
218 title=k + ' - bit %d'%self._maskDict[k],
219 color=self._maskPlaneColors[k],
220 file_on_server=self._fireflyMaskOnServer)
221 if k in self._maskTransparencies:
223 self._maskIds.append(k)
224
225 def _remove_masks(self):
226 """Remove mask layers"""
227 for k in self._maskIds:
228 _fireflyClient.remove_mask(plot_id=str(self.display.frame), mask_id=k)
229 self._maskIds = []
230
231 def _buffer(self, enable=True):
232 """!Enable or disable buffering of writes to the display
233 param enable True or False, as appropriate
234 """
235 self._isBuffered = enable
236
237 def _flush(self):
238 """!Flush any I/O buffers
239 """
240 if not self._regions:
241 return
242
243 if self.verbose:
244 print("Flushing %d regions" % len(self._regions))
245 print(self._regions)
246
248 _fireflyClient.add_region_data(region_data=self._regions, plot_id=str(self.display.frame),
249 region_layer_id=self._regionLayerId)
250 self._regions = []
251
252 def _uploadTextData(self, regions):
253 self._regions += regions
254
255 if not self._isBuffered:
256 self._flush()
257
258 def _close(self):
259 """Called when the device is closed"""
260 if self.verbose:
261 print("Closing firefly device %s" % (self.display.frame if self.display else "[None]"))
262 if _fireflyClient is not None:
263 _fireflyClient.disconnect()
264 _fireflyClient.session.close()
265
266 def _dot(self, symb, c, r, size, ctype, fontFamily="helvetica", textAngle=None):
267 """Draw a symbol onto the specified DS9 frame at (col,row) = (c,r) [0-based coordinates]
268 Possible values are:
269 + Draw a +
270 x Draw an x
271 * Draw a *
272 o Draw a circle
273 @:Mxx,Mxy,Myy Draw an ellipse with moments (Mxx, Mxy, Myy) (argument size is ignored)
274 An object derived from afwGeom.ellipses.BaseCore Draw the ellipse (argument size is ignored)
275 Any other value is interpreted as a string to be drawn. Strings obey the fontFamily (which may be extended
276 with other characteristics, e.g. "times bold italic". Text will be drawn rotated by textAngle (textAngle
277 is ignored otherwise).
278
279 N.b. objects derived from BaseCore include Axes and Quadrupole.
280 """
281 self._uploadTextData(ds9Regions.dot(symb, c, r, size, ctype, fontFamily, textAngle))
282
283 def _drawLines(self, points, ctype):
284 """Connect the points, a list of (col,row)
285 Ctype is the name of a colour (e.g. 'red')"""
286
287 self._uploadTextData(ds9Regions.drawLines(points, ctype))
288
289 def _erase(self):
290 """Erase all overlays on the image"""
291 if self.verbose:
292 print(f'region layer id is {self._regionLayerId}')
293 if self._regionLayerId:
294 _fireflyClient.delete_region_layer(self._regionLayerId, plot_id=str(self.display.frame))
295
296 def _setCallback(self, what, func):
297 if func != interface.noop_callback:
298 try:
299 status = _fireflyClient.add_extension('POINT' if False else 'AREA_SELECT', title=what,
300 plot_id=str(self.display.frame),
301 extension_id=what)
302 if not status['success']:
303 pass
304 except Exception as e:
305 raise RuntimeError("Cannot set callback. Browser must be (re)opened "
306 f"to {_fireflyClient.url_bw}{_fireflyClient.channel} : {e}")
307
308 def _getEvent(self):
309 """Return an event generated by a keypress or mouse click
310 """
311 ev = interface.Event("q")
312
313 if self.verbose:
314 print(f"virtual[{self.display.frame}]._getEvent() -> {ev}")
315
316 return ev
317 #
318 # Set gray scale
319 #
320
321 def _scale(self, algorithm, min, max, unit=None, *args, **kwargs):
322 """Scale the image stretch and limits
323
324 Parameters:
325 -----------
326 algorithm : `str`
327 stretch algorithm, e.g. 'linear', 'log', 'loglog', 'equal', 'squared',
328 'sqrt', 'asinh', powerlaw_gamma'
329 min : `float` or `str`
330 lower limit, or 'minmax' for full range, or 'zscale'
331 max : `float` or `str`
332 upper limit; overrriden if min is 'minmax' or 'zscale'
333 unit : `str`
334 unit for min and max. 'percent', 'absolute', 'sigma'.
335 if not specified, min and max are presumed to be in 'absolute' units.
336
337 *args, **kwargs : additional position and keyword arguments.
338 The options are shown below:
339
340 **Q** : `float`, optional
341 The asinh softening parameter for asinh stretch.
342 Use Q=0 for linear stretch, increase Q to make brighter features visible.
343 When not specified or None, Q is calculated by Firefly to use full color range.
344 **gamma**
345 The gamma value for power law gamma stretch (default 2.0)
346 **zscale_contrast** : `int`, optional
347 Contrast parameter in percent for zscale algorithm (default 25)
348 **zscale_samples** : `int`, optional
349 Number of samples for zscale algorithm (default 600)
350 **zscale_samples_perline** : `int`, optional
351 Number of samples per line for zscale algorithm (default 120)
352 """
353 stretch_algorithms = ('linear', 'log', 'loglog', 'equal', 'squared', 'sqrt',
354 'asinh', 'powerlaw_gamma')
355 interval_methods = ('percent', 'maxmin', 'absolute', 'zscale', 'sigma')
356 #
357 #
358 # Normalise algorithm's case
359 #
360 if algorithm:
361 algorithm = dict((a.lower(), a) for a in stretch_algorithms).get(algorithm.lower(), algorithm)
362
363 if algorithm not in stretch_algorithms:
364 raise FireflyError(
365 'Algorithm {} is invalid; please choose one of "{}"'.format(
366 algorithm, '", "'.join(stretch_algorithms)
367 )
368 )
369 self._stretchAlgorithm = algorithm
370 else:
371 algorithm = 'linear'
372
373 # Translate parameters for asinh and powerlaw_gamma stretches
374 if 'Q' in kwargs:
375 kwargs['asinh_q_value'] = kwargs['Q']
376 del kwargs['Q']
377
378 if 'gamma' in kwargs:
379 kwargs['gamma_value'] = kwargs['gamma']
380 del kwargs['gamma']
381
382 if min == 'minmax':
383 interval_type = 'percent'
384 unit = 'percent'
385 min, max = 0, 100
386 elif min == 'zscale':
387 interval_type = 'zscale'
388 else:
389 interval_type = None
390
391 if not unit:
392 unit = 'absolute'
393
394 units = ('percent', 'absolute', 'sigma')
395 if unit not in units:
396 raise FireflyError(
397 'Unit {} is invalid; please choose one of "{}"'.format(unit, '", "'.join(units))
398 )
399
400 if unit == 'sigma':
401 interval_type = 'sigma'
402 elif unit == 'absolute' and interval_type is None:
403 interval_type = 'absolute'
404 elif unit == 'percent':
405 interval_type = 'percent'
406
407 self._stretchMin = min
408 self._stretchMax = max
409 self._stretchUnit = unit
410
411 if interval_type not in interval_methods:
412 raise FireflyError(f'Interval method {interval_type} is invalid')
413
414 rval = {}
415 if interval_type != 'zscale':
416 rval = _fireflyClient.set_stretch(str(self.display.frame), stype=interval_type,
417 algorithm=algorithm, lower_value=min,
418 upper_value=max, **kwargs)
419 else:
420 if 'zscale_contrast' not in kwargs:
421 kwargs['zscale_contrast'] = 25
422 if 'zscale_samples' not in kwargs:
423 kwargs['zscale_samples'] = 600
424 if 'zscale_samples_perline' not in kwargs:
425 kwargs['zscale_samples_perline'] = 120
426 rval = _fireflyClient.set_stretch(str(self.display.frame), stype='zscale',
427 algorithm=algorithm, **kwargs)
428
429 if 'rv_string' in rval:
430 self._lastStretch = rval['rv_string']
431
432 def _setMaskTransparency(self, transparency, maskName):
433 """Specify mask transparency (percent); or None to not set it when loading masks"""
434 if maskName is not None:
435 masklist = [maskName]
436 else:
437 masklist = set(self._maskIds + list(self.display._defaultMaskPlaneColor.keys()))
438 for k in masklist:
439 self._maskTransparencies[k] = transparency
440 _fireflyClient.dispatch(action_type='ImagePlotCntlr.overlayPlotChangeAttributes',
441 payload={'plotId': str(self.display.frame),
442 'imageOverlayId': k,
443 'attributes': {'opacity': 1.0 - transparency/100.},
444 'doReplot': False})
445
446 def _getMaskTransparency(self, maskName):
447 """Return the current mask's transparency"""
448 transparency = None
449 if maskName in self._maskTransparencies:
450 transparency = self._maskTransparencies[maskName]
451 return transparency
452
453 def _setMaskPlaneColor(self, maskName, color):
454 """Specify mask color """
455 _fireflyClient.remove_mask(plot_id=str(self.display.frame),
456 mask_id=maskName)
457 self._maskPlaneColors[maskName] = color
458 if (color.lower() != 'ignore'):
459 _fireflyClient.add_mask(bit_number=self._maskDict[maskName],
460 image_number=1,
461 plot_id=str(self.display.frame),
462 mask_id=maskName,
463 color=self.display.getMaskPlaneColor(maskName),
464 file_on_server=self._fireflyFitsID)
465
466 def _show(self):
467 """Show the requested window"""
468 if self._client.render_tree_id is not None:
469 # we are using Jupyterlab
470 self._client.dispatch(self._client.ACTION_DICT['StartLabWindow'],
471 {})
472 else:
473 localbrowser, url = _fireflyClient.launch_browser(verbose=self.verbose)
474 if not localbrowser and not self.verbose:
475 _fireflyClient.display_url()
476
477 #
478 # Zoom and Pan
479 #
480
481 def _zoom(self, zoomfac):
482 """Zoom display by specified amount
483
484 Parameters:
485 -----------
486 zoomfac: `float`
487 zoom level in screen pixels per image pixel
488 """
489 self._lastZoom = zoomfac
490 _fireflyClient.set_zoom(plot_id=str(self.display.frame), factor=zoomfac)
491
492 def _pan(self, colc, rowc):
493 """Pan to specified pixel coordinates
494
495 Parameters:
496 -----------
497 colc, rowc : `float`
498 column and row in units of pixels (zero-based convention,
499 with the xy0 already subtracted off)
500 """
501 self._lastPan = [colc+0.5, rowc+0.5] # saved for future use in _mtv
502 # Firefly's internal convention is first pixel is (0.5, 0.5)
503 _fireflyClient.set_pan(plot_id=str(self.display.frame), x=colc, y=rowc)
504
505 # Extensions to the API that are specific to using the Firefly backend
506
507 def getClient(self):
508 """Get the instance of FireflyClient for this display
509
510 Returns:
511 --------
512 `firefly_client.FireflyClient`
513 Instance of FireflyClient used by this display
514 """
515 return self._client
516
517 def clearViewer(self):
518 """Reinitialize the viewer
519 """
520 self._client.reinit_viewer()
521
522 def resetLayout(self):
523 """Reset the layout of the Firefly Slate browser
524
525 Clears the display and adds Slate cells to display image in upper left,
526 plot area in upper right, and plots stretch across the bottom
527 """
528 self.clearViewer()
529 try:
530 tables_cell_id = 'tables-' + str(_fireflyClient.render_tree_id)
531 except AttributeError:
532 tables_cell_id = 'tables'
533 self._client.add_cell(row=2, col=0, width=4, height=2, element_type='tables',
534 cell_id=tables_cell_id)
535 try:
536 image_cell_id = ('image-' + str(_fireflyClient.render_tree_id) + '-' +
537 str(self.frame))
538 except AttributeError:
539 image_cell_id = 'image-' + str(self.frame)
540 self._client.add_cell(row=0, col=0, width=2, height=3, element_type='images',
541 cell_id=image_cell_id)
542 try:
543 plots_cell_id = 'plots-' + str(_fireflyClient.render_tree_id)
544 except AttributeError:
545 plots_cell_id = 'plots'
546 self._client.add_cell(row=0, col=2, width=2, height=3, element_type='xyPlots',
547 cell_id=plots_cell_id)
548
549 def overlayFootprints(self, catalog, color='rgba(74,144,226,0.60)',
550 highlightColor='cyan', selectColor='orange',
551 style='fill', layerString='detection footprints ',
552 titleString='catalog footprints '):
553 """Overlay outlines of footprints from a catalog
554
555 Overlay outlines of LSST footprints from the input catalog. The colors
556 and style can be specified as parameters, and the base color and style
557 can be changed in the Firefly browser user interface.
558
559 Parameters:
560 -----------
561 catalog : `lsst.afw.table.SourceCatalog`
562 Source catalog from which to display footprints.
563 color : `str`
564 Color for footprints overlay. Colors can be specified as a name
565 like 'cyan' or afwDisplay.RED; as an rgb value such as
566 'rgb(80,100,220)'; or as rgb plus alpha (transparency) such
567 as 'rgba('74,144,226,0.60)'.
568 highlightColor : `str`
569 Color for highlighted footprints
570 selectColor : `str`
571 Color for selected footprints
572 style : {'fill', 'outline'}
573 Style of footprints display, filled or outline
574 insertColumn : `int`
575 Column at which to insert the "family_id" and "category" columns
576 layerString: `str`
577 Name of footprints layer string, to concatenate with the frame
578 Re-using the layer_string will overwrite the previous table and
579 footprints
580 titleString: `str`
581 Title of catalog, to concatenate with the frame
582 """
583 footprintTable = createFootprintsTable(catalog)
584 with BytesIO() as fd:
585 footprintTable.to_xml(fd)
586 tableval = self._client.upload_data(fd, 'UNKNOWN')
587 self._client.overlay_footprints(footprint_file=tableval,
588 title=titleString + str(self.display.frame),
589 footprint_layer_id=layerString + str(self.display.frame),
590 plot_id=str(self.display.frame),
591 color=color,
592 highlightColor=highlightColor,
593 selectColor=selectColor,
594 style=style)
_setMaskTransparency(self, transparency, maskplane)
_dot(self, symb, c, r, size, ctype, fontFamily="helvetica", textAngle=None)
Definition firefly.py:266
_setMaskTransparency(self, transparency, maskName)
Definition firefly.py:432
__init__(self, display, verbose=False, url=None, name=None, *args, **kwargs)
Definition firefly.py:77
_flush(self)
Flush any I/O buffers.
Definition firefly.py:237
overlayFootprints(self, catalog, color='rgba(74, 144, 226, 0.60)', highlightColor='cyan', selectColor='orange', style='fill', layerString='detection footprints ', titleString='catalog footprints ')
Definition firefly.py:552
_buffer(self, enable=True)
Enable or disable buffering of writes to the display param enable True or False, as appropriate.
Definition firefly.py:231
_scale(self, algorithm, min, max, unit=None, *args, **kwargs)
Definition firefly.py:321
_mtv(self, image, mask=None, wcs=None, title="", metadata=None)
Definition firefly.py:156
_setMaskPlaneColor(self, maskName, color)
Definition firefly.py:453
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
Definition Statistics.h:361