Note
Go to the end to download the full example code.
Displaying Camera Images#
7 import astropy.coordinates as c
8 import astropy.units as u
9 import matplotlib.pyplot as plt
10 import numpy as np
11 from matplotlib.animation import FuncAnimation
12 from matplotlib.colors import PowerNorm
13
14 from ctapipe.coordinates import CameraFrame, EngineeringCameraFrame, TelescopeFrame
15 from ctapipe.image import hillas_parameters, tailcuts_clean, toymodel
16 from ctapipe.instrument import SubarrayDescription
17 from ctapipe.io import EventSource
18 from ctapipe.visualization import CameraDisplay
First, let’s create a fake Cherenkov image from a given
CameraGeometry
and fill it with some data that we can draw later.
25 # load an example camera geometry from a simulation file
26 subarray = SubarrayDescription.read("dataset://gamma_prod5.simtel.zst")
27 geom = subarray.tel[100].camera.geometry
28
29 # create a fake camera image to display:
30 model = toymodel.Gaussian(
31 x=0.2 * u.m,
32 y=0.0 * u.m,
33 width=0.05 * u.m,
34 length=0.15 * u.m,
35 psi="35d",
36 )
37
38 image, sig, bg = model.generate_image(geom, intensity=1500, nsb_level_pe=10)
39 mask = tailcuts_clean(geom, image, picture_thresh=15, boundary_thresh=5)
42 geom
CameraGeometry(name='NectarCam', pix_type=PixelShape.HEXAGON, npix=1855, cam_rot=0.000 deg, pix_rot=40.893 deg, frame=<CameraFrame Frame (focal_length=16.445049285888672 m, rotation=0.0 rad, telescope_pointing=None, obstime=None, location=None)>)
Displaying Images#
The simplest plot is just to generate a CameraDisplay with an image in its constructor. A figure and axis will be created automatically
53 CameraDisplay(geom)
<ctapipe.visualization.mpl_camera.CameraDisplay object at 0x7f746aaf8280>
You can also specify the initial image
, cmap
and norm
(colomap and normalization, see below), title
to use. You can
specify ax
if you want to draw the camera on an existing
matplotlib Axes
object (otherwise one is created).
To change other options, or to change options dynamically, you can call
the relevant functions of the CameraDisplay
object that is returned.
For example to add a color bar, call add_colorbar()
, or to change
the color scale, modify the cmap
or norm
properties directly.
Choosing a coordinate frame#
The CameraGeometry
object contains a ctapipe.coordinates.Frame
used by CameraDisplay
to draw the camera in the correct orientation
and distance units. The default frame is the CameraFrame
, which will
display the camera in units of meters and with an orientation that the
top of the camera (when parked) is aligned to the X-axis. To show the
camera in another orientation, it’s useful to apply a coordinate
transform to the CameraGeometry
before passing it to the
CameraDisplay
. The following Frames
are supported:
CameraFrame
: The frame used by SimTelArray, with the top of the camera on the x-axis
EngineeringCameraFrame
: similar to CameraFrame, but with the top of the camera aligned to the Y axis
TelescopeFrame
: In degrees (on the sky) coordinates relative to the telescope Alt/Az pointing position, with the Alt axis pointing upward.
Note the the name of the Frame appears in the lower-right corner
<ctapipe.visualization.mpl_camera.CameraDisplay object at 0x7f747a5acb80>
For the rest of this demo, let’s use the TelescopeFrame
103 geom_camframe = geom
104 geom = geom_camframe.transform_to(EngineeringCameraFrame())
Changing the color map and scale#
CameraDisplay supports any matplotlib color map It is highly recommended to use a perceptually uniform map, unless you have a good reason not to.
By default the minimum and maximum of the color bar are set automatically by the data in the image. To choose fixed limits, use:`
Or you can set the maximum limit by percentile of the charge distribution:
Using different normalizations#
You can choose from several preset normalizations (lin, log, symlog) and
also provide a custom normalization, for example a PowerNorm
:
163 fig, axes = plt.subplots(2, 2, figsize=(14, 10))
164 norms = ["lin", "log", "symlog", PowerNorm(0.5)]
165
166 for norm, ax in zip(norms, axes.flatten()):
167 disp = CameraDisplay(geom, image=image, ax=ax)
168 disp.norm = norm
169 disp.add_colorbar()
170 ax.set_title(str(norm))
171
172 axes[1, 1].set_title("PowerNorm(0.5)")
173 plt.show()
Overlays#
Marking pixels#
here we will mark pixels in the image mask. That will change their outline color
190 fig, ax = plt.subplots(1, 2, figsize=(10, 4))
191 disp = CameraDisplay(
192 geom, image=image, cmap="gray", ax=ax[0], title="Image mask in green"
193 )
194 disp.highlight_pixels(mask, alpha=0.8, linewidth=2, color="green")
195
196 disp = CameraDisplay(
197 geom, image=image, cmap="gray", ax=ax[1], title="Image mask in green (zoom)"
198 )
199 disp.highlight_pixels(mask, alpha=1, linewidth=3, color="green")
200
201 ax[1].set_ylim(-0.5, 0.5)
202 ax[1].set_xlim(-0.5, 0.5)
(-0.5, 0.5)
Drawing a Hillas-parameter ellipse#
For this, we will first compute some Hillas Parameters in the current frame:
216 clean_image = image.copy()
217 clean_image[~mask] = 0
218 hillas = hillas_parameters(geom, clean_image)
219
220 plt.figure(figsize=(6, 6))
221 disp = CameraDisplay(geom, image=image, cmap="gray_r")
222 disp.highlight_pixels(mask, alpha=0.5, color="dodgerblue")
223 disp.overlay_moments(hillas, color="red", linewidth=3, with_label=False)
Drawing a marker at a coordinate#
This depends on the coordinate frame of the CameraGeometry
. Here we
will specify the coordinate the EngineerngCameraFrame
, but if you
have enough information to do the coordinate transform, you could use
ICRS
coordinates and overlay star positions. CameraDisplay
will
convert the coordinate you pass in to the Frame
of the display
automatically (if sufficient frame attributes are set).
Note that the parameter keep_old
is False by default, meaning adding
a new point will clear the previous ones (useful for animations, but
perhaps unexpected for a static plot). Set it to True
to plot
multiple markers.
243 plt.figure(figsize=(6, 6))
244 disp = CameraDisplay(geom, image=image, cmap="gray_r")
245
246 coord = c.SkyCoord(x=0.5 * u.m, y=0.7 * u.m, frame=geom.frame)
247 coord_in_another_frame = c.SkyCoord(x=0.5 * u.m, y=0.7 * u.m, frame=CameraFrame())
248 disp.overlay_coordinate(coord, markersize=20, marker="*")
249 disp.overlay_coordinate(
250 coord_in_another_frame, markersize=20, marker="*", keep_old=True
251 )
Generating an animation#
Here we will make an animation of fake events by re-using a single display (much faster than generating a new one each time)
263 subarray = SubarrayDescription.read("dataset://gamma_prod5.simtel.zst")
264 geom = subarray.tel[1].camera.geometry
265
266 fov = 1.0
267 maxwid = 0.05
268 maxlen = 0.1
269
270 fig, ax = plt.subplots(1, 1, figsize=(8, 6))
271 disp = CameraDisplay(geom, ax=ax) # we only need one display (it can be re-used)
272 disp.cmap = "inferno"
273 disp.add_colorbar(ax=ax)
274
275
276 def update(frame):
277 """this function will be called for each frame of the animation"""
278 x, y = np.random.uniform(-fov, fov, size=2)
279 width = np.random.uniform(0.01, maxwid)
280 length = np.random.uniform(width, maxlen)
281 angle = np.random.uniform(0, 180)
282 intens = width * length * (5e4 + 1e5 * np.random.exponential(2))
283
284 model = toymodel.Gaussian(
285 x=x * u.m,
286 y=y * u.m,
287 width=width * u.m,
288 length=length * u.m,
289 psi=angle * u.deg,
290 )
291 image, _, _ = model.generate_image(
292 geom,
293 intensity=intens,
294 nsb_level_pe=5,
295 )
296 disp.image = image
297
298
299 # Create the animation and convert to a displayable video:
300 anim = FuncAnimation(fig, func=update, frames=10, interval=200)
301 plt.show()
Using CameraDisplays interactively#
CameraDisplays
can be used interactively when displayed in a window,
and also when using Jupyter notebooks/lab with appropriate backends.
When this is the case, the same CameraDisplay
object can be re-used.
We can’t show this here in the documentation, but creating an animation
when in a matplotlib window is quite easy! Try this in an interactive
ipython session:
Running interactive displays in a matplotlib window#
ipython -i –maplotlib=auto
That will open an ipython session with matplotlib graphics in a separate thread, meaning that you can type code and interact with plots simultaneneously.
In the ipython session try running the following code and you will see an animation (here in the documentation, it will of course be static)
First we load some real data so we have a nice image to view:
337 DATA = "dataset://gamma_20deg_0deg_run1___cta-prod5-lapalma_desert-2158m-LaPalma-dark_100evts.simtel.zst"
338
339 with EventSource(
340 DATA,
341 max_events=1,
342 focal_length_choice="EQUIVALENT",
343 ) as source:
344 event = next(iter(source))
345
346 tel_id = list(event.r0.tel.keys())[0]
347 geom = source.subarray.tel[tel_id].camera.geometry
348 waveform = event.r0.tel[tel_id].waveform
349 n_chan, n_pix, n_samp = waveform.shape
Running the following the will bring up a window and animate the shower image as a function of time.
The output will be similar to the static animation created as follows:
368 fig, ax = plt.subplots(1, 1)
369 disp = CameraDisplay(geom, ax=ax)
370 disp.add_colorbar()
371 disp.autoscale = False
372
373
374 def draw_sample(frame):
375 ax.set_title(f"sample: {frame}")
376 disp.set_limits_minmax(200, 400)
377 disp.image = waveform[0, :, frame]
378
379
380 anim = FuncAnimation(fig, func=draw_sample, frames=n_samp, interval=100)
381 plt.show()