Basic Principles¶
Let's imagine you want to do something slightly more complicated. Maybe you want to make a movie, and include an linked time series plot next to it. Maybe you want to include several movie frames at different cadences. Maybe you want to create your own snazzy custom format. To do so, there are three core concepts you need to understand.
a Sequence
is a collection of data, with times¶
To make an animation, we need a data structure that has some kind of data that has a time axis associated with it. For example, here we make a Sequence
out of a small postage stamp of imaging data.
from illumination import *
from illumination.cartoons import create_test_stamp
stamp = create_test_stamp(N=10, seed=42)
The make_image_sequence
function is a general wrapper that does a decent job of figuring out what kind of sequence should be made out of the given inputs.
seq = make_image_sequence(stamp)
make_image_sequence
should be able to handle at least the following types of input:
- a single FITS filename, and an
ext_image=
keyword for the extension to use - a list of FITS filenames, and an
ext_image=
keyword - a glob pattern to search for FITS files, and an
ext_image=
keyword - a single FITS HDUList, and an
ext_image=
keyword - a list of loaded FITS HDULists, and an
ext_image=
keyword - a
Stamp
object from thecosmics
package.
Each sequence
has a .time
attribute. It is an astropy.time
object, and can be indexed as an array. If times are fake (for example, if they are simply image number or cadence number), then we pretend (artificially) that the times are gps
format with a cadence of 1s.
print(f"""
{seq}
has {len(seq.time)} times defined,
spanning {seq.time[0]}
to {seq.time[-1]}
with a cadence of {seq.cadence().to('s'):.2}""")
<stamp-sequence of 10 images of shape (5, 5)> has 10 times defined, spanning 2018-01-01 00:00:00.000 to 2018-01-01 00:00:18.000 with a cadence of 2.0 s
Each sequence can be indexed directly with [i]
to extract the i
th element of the dataset. Different types of sequences will store data in different ways. For a small stamp, it will generally load the entire 3D (x,y,t) dataset into memory. For a sequence of large FITS images, it will generally only load individual FITS images on the fly as needed, to preserve memory. These different background behaviors should hopefully remain hidden to the casual user.
fi, ax = plt.subplots(1,3, figsize=(10,3))
for i in range(3):
ax[i].imshow(seq[i])
ax[i].set_title(seq.time[i])
a Frame
is a plot panel, which can display a Sequence
¶
A frame is a container in which a sequence can be displayed. Different frames can display different views, even of the same dataset. For example, here we create two different frames that include our little sequence seq
as their data.
normal = imshowFrame(name='cecelia',
data=seq)
normal
<imshow Frame | data=<stamp-sequence of 10 images of shape (5, 5)> | name=cecelia>
Below, we can create a different frame using the same data, but by including the processingsteps
keyword, it will subtract the mean image from each individual image before displaying it.
subtracted = imshowFrame(name='henrietta',
data=seq,
title='(median subtracted)',
processingsteps=['subtractmean'])
subtracted
<imshow Frame | data=<stamp-sequence of 10 images of shape (5, 5)> | name=henrietta>
We will be able to visualize these frames once they are included in an illustration.
an Illustration
is a collection of Frames
, with its own layout¶
Once we have created our frames, we can include them in an illustration. The illustration handles not only the basic grid layout of the frames, but also synchronizing their frames when plotting an animation. When you call the illustration's .plot
method, it will make all the constituent frames plot
too. When you call the .animate
method, it will make an animation by looping through a grid of times and updating each frame whenever it needs updating.
i = GenericIllustration(imshows=[normal, subtracted])
i.plot()
i.animate('a-pair-of-frames.mp4');
[imshowframe] plotting <F"cecelia"> for the first time [imshowframe] {} [genericillustration] {} [genericillustration] included <F"cecelia"> in the shared color scheme [stamp-sequence] creating a mean image for <stamp-sequence of 10 images of shape (5, 5)> [stamp-sequence] included frame 1/10 in mean [stamp-sequence] included frame 2/10 in mean [stamp-sequence] included frame 3/10 in mean [stamp-sequence] included frame 4/10 in mean [stamp-sequence] included frame 5/10 in mean [stamp-sequence] included frame 6/10 in mean [stamp-sequence] included frame 7/10 in mean [stamp-sequence] included frame 8/10 in mean [stamp-sequence] included frame 9/10 in mean [stamp-sequence] included frame 10/10 in mean [genericillustration] included <F"henrietta"> in the shared color scheme [genericillustration] defined color scheme with cmap=RdBu norm=<matplotlib.colors.SymLogNorm object at 0x16bdb5af0> ticks=[-355.1031326981831, -46.61562494544138, 0, 46.61562494544138, 355.1031326981831] [imshowframe] added image of shape (5, 5) to <F"cecelia"> [imshowframe] making sure a shared colorbar is set up for <F"cecelia"> [imshowframe] added a shared colorbar for <I"Generic"> [genericillustration] adding a new colorbar for 2 frame(s) [imshowframe] defining 2458119.5 as the time offset [imshowframe] added time label of "t=2458119.50000+0.00000 d" on <F"cecelia"> [imshowframe] added arrows on <F"cecelia"> [imshowframe] added title of "TIC1234567890 (CAM1 | 913, 3900) SPM1 | 2s" to <F"cecelia"> [imshowframe] plotting <F"henrietta"> for the first time [imshowframe] {} [genericillustration] {} [imshowframe] added image of shape (5, 5) to <F"henrietta"> [imshowframe] making sure a shared colorbar is set up for <F"henrietta"> [imshowframe] defining 2458119.5 as the time offset [imshowframe] added time label of "t=2458119.50000+0.00000 d" on <F"henrietta"> [imshowframe] added arrows on <F"henrietta"> [imshowframe] added title of "(median subtracted)" to <F"henrietta"> [genericillustration] about to animate 20 times at 1.0s cadence for <I"Generic"> [genericillustration] 30 frames/second : [genericillustration] the animation will be saved to a-pair-of-frames.mp4 [genericillustration] 1/20 at 2024-09-12 20:24:58.781
[genericillustration] 2/20 at 2024-09-12 20:24:59.050 [genericillustration] 3/20 at 2024-09-12 20:24:59.080 [genericillustration] 4/20 at 2024-09-12 20:24:59.108 [genericillustration] 5/20 at 2024-09-12 20:24:59.133 [genericillustration] 6/20 at 2024-09-12 20:24:59.159 [genericillustration] 7/20 at 2024-09-12 20:24:59.184 [genericillustration] 8/20 at 2024-09-12 20:24:59.213 [genericillustration] 9/20 at 2024-09-12 20:24:59.239
[genericillustration] 10/20 at 2024-09-12 20:24:59.267 [genericillustration] 11/20 at 2024-09-12 20:24:59.292 [genericillustration] 12/20 at 2024-09-12 20:24:59.318 [genericillustration] 13/20 at 2024-09-12 20:24:59.342 [genericillustration] 14/20 at 2024-09-12 20:24:59.368 [genericillustration] 15/20 at 2024-09-12 20:24:59.392 [genericillustration] 16/20 at 2024-09-12 20:24:59.418 [genericillustration] 17/20 at 2024-09-12 20:24:59.451
[genericillustration] 18/20 at 2024-09-12 20:24:59.480 [genericillustration] 19/20 at 2024-09-12 20:24:59.506 [genericillustration] 20/20 at 2024-09-12 20:24:59.535 [genericillustration] [genericillustration] the animation is finished!
Looking back at the normal
and subtracted
frames that we defined, we can see a few features. The data and times for the two frames are identical, so the images update synchronously at each timestep of the animation. The subtracted
frame looks like noise scattered around 0, as we would expect for having subtracted off the mean image. We gave subtracted
a custom title, but normal
fell back on an initial best guess.
If you want, you can access the individual frames inside an illustration through the .frames
dictionary. This may come in handy if you want to modify the attributes of a frame once it's in an illustration, or change something in how it is plotted after calling i.plot()
. (See examples below.)
print(f'{i} contains {len(i.frames)} frames: {list(i.frames.keys())})')
<I"Generic"> contains 2 frames: ['cecelia', 'henrietta'])
By default, all image frames will share the same color mapping. We can let each frame have its own color mapping by setting the illustration's sharecolorbar=
keyword to False
.
i = GenericIllustration(imshows=[normal, subtracted], sharecolorbar=False)
i.plot()
i.animate('a-pair-of-frames-with-different-colorbars.mp4')
[imshowframe] plotting <F"cecelia"> for the first time [imshowframe] {} [imshowframe] added image of shape (5, 5) to <F"cecelia"> [imshowframe] making sure a unique colorbar is set up for <F"cecelia"> [imshowframe] added a unique colorbar for <F"cecelia"> [genericillustration] adding a new colorbar for 1 frame(s) [imshowframe] added time label of "t=2458119.50000+0.00000 d" on <F"cecelia"> [imshowframe] added arrows on <F"cecelia"> [imshowframe] added title of "TIC1234567890 (CAM1 | 913, 3900) SPM1 | 2s" to <F"cecelia"> [imshowframe] plotting <F"henrietta"> for the first time [imshowframe] {} [imshowframe] added image of shape (5, 5) to <F"henrietta"> [imshowframe] making sure a unique colorbar is set up for <F"henrietta"> [imshowframe] added a unique colorbar for <F"henrietta"> [genericillustration] adding a new colorbar for 1 frame(s) [imshowframe] added time label of "t=2458119.50000+0.00000 d" on <F"henrietta"> [imshowframe] added arrows on <F"henrietta"> [imshowframe] added title of "(median subtracted)" to <F"henrietta"> [genericillustration] about to animate 20 times at 1.0s cadence for <I"Generic"> [genericillustration] 30 frames/second : [genericillustration] the animation will be saved to a-pair-of-frames-with-different- colorbars.mp4 [genericillustration] 1/20 at 2024-09-12 20:24:59.868
[genericillustration] 2/20 at 2024-09-12 20:24:59.950 [genericillustration] 3/20 at 2024-09-12 20:25:00.025
[genericillustration] 4/20 at 2024-09-12 20:25:00.080 [genericillustration] 5/20 at 2024-09-12 20:25:00.122 [genericillustration] 6/20 at 2024-09-12 20:25:00.157
[genericillustration] 7/20 at 2024-09-12 20:25:00.186 [genericillustration] 8/20 at 2024-09-12 20:25:00.214 [genericillustration] 9/20 at 2024-09-12 20:25:00.241 [genericillustration] 10/20 at 2024-09-12 20:25:00.269
[genericillustration] 11/20 at 2024-09-12 20:25:00.297 [genericillustration] 12/20 at 2024-09-12 20:25:00.325 [genericillustration] 13/20 at 2024-09-12 20:25:00.353 [genericillustration] 14/20 at 2024-09-12 20:25:00.380
[genericillustration] 15/20 at 2024-09-12 20:25:00.408 [genericillustration] 16/20 at 2024-09-12 20:25:00.436 [genericillustration] 17/20 at 2024-09-12 20:25:00.467 [genericillustration] 18/20 at 2024-09-12 20:25:00.495
[genericillustration] 19/20 at 2024-09-12 20:25:00.524 [genericillustration] 20/20 at 2024-09-12 20:25:00.553 [genericillustration] [genericillustration] the animation is finished!
If you're trying to directly compare values across different frames, keep sharecolorbar=True
. If you're looking for qualitative features and want to highlight the details unique to each frame, let each have its own colorbar. In this example, where the data were simulated to represent photon counts, we can see that the noise in the difference images is highest where the flux in the normal image is too.
some example Frame
combinations¶
We can treat frames as somewhat modular containers, and build up illustrations through combinations of them. Here are a couple of examples of making custom illustrations by following the general process of
- make some sequences
- put them in frames
- fill an illustration
simple ZoomFrame
(recreating illustratefits
)¶
Let's start by recreating the zoom feature shown with illustratefits
in the quickstart.
# first create a sequence
big = make_image_sequence(create_test_stamp(N=10, xsize=80, ysize=100))
# then define some frames
image = imshowFrame(name='big-image', data=big, title='imshow')
zoom = ZoomFrame(name='zoom', source=image, position=(25, 50), size=(20,10), title='zoom')
# then populate an illustration with them
i = GenericIllustration(imshows=[image, zoom])
i.plot()
i.animate('an-example-of-a-zoom.mp4', dpi=75)
[imshowframe] plotting <F"big-image"> for the first time [imshowframe] {} [genericillustration] {} [genericillustration] included <F"big-image"> in the shared color scheme [genericillustration] included <F"zoom"> in the shared color scheme [genericillustration] defined color scheme with cmap=Blues norm=<matplotlib.colors.LogNorm object at 0x1736c5670> ticks=[21.972878571785586, 235.70697097470727, 2528.4705408334985] [imshowframe] added image of shape (100, 80) to <F"big-image"> [imshowframe] making sure a shared colorbar is set up for <F"big-image"> [imshowframe] added a shared colorbar for <I"Generic"> [genericillustration] adding a new colorbar for 2 frame(s) [imshowframe] defining 2458119.5 as the time offset [imshowframe] added time label of "t=2458119.50000+0.00000 d" on <F"big-image"> [imshowframe] added arrows on <F"big-image"> [imshowframe] added title of "imshow" to <F"big-image"> [zoomframe] plotting <F"zoom"> for the first time [zoomframe] {} [genericillustration] {} [zoomframe] added image of shape (10, 20) to <F"zoom"> [genericillustration] about to animate 20 times at 1.0s cadence for <I"Generic"> [genericillustration] 30 frames/second : [genericillustration] the animation will be saved to an-example-of-a-zoom.mp4 [genericillustration] 1/20 at 2024-09-12 20:25:00.889 [genericillustration] 2/20 at 2024-09-12 20:25:00.949 [genericillustration] 3/20 at 2024-09-12 20:25:00.999 [genericillustration] 4/20 at 2024-09-12 20:25:01.037
[genericillustration] 5/20 at 2024-09-12 20:25:01.080 [genericillustration] 6/20 at 2024-09-12 20:25:01.106 [genericillustration] 7/20 at 2024-09-12 20:25:01.158 [genericillustration] 8/20 at 2024-09-12 20:25:01.177 [genericillustration] 9/20 at 2024-09-12 20:25:01.194 [genericillustration] 10/20 at 2024-09-12 20:25:01.212 [genericillustration] 11/20 at 2024-09-12 20:25:01.230 [genericillustration] 12/20 at 2024-09-12 20:25:01.249 [genericillustration] 13/20 at 2024-09-12 20:25:01.266 [genericillustration] 14/20 at 2024-09-12 20:25:01.282
[genericillustration] 15/20 at 2024-09-12 20:25:01.299 [genericillustration] 16/20 at 2024-09-12 20:25:01.316 [genericillustration] 17/20 at 2024-09-12 20:25:01.332 [genericillustration] 18/20 at 2024-09-12 20:25:01.348 [genericillustration] 19/20 at 2024-09-12 20:25:01.364 [genericillustration] 20/20 at 2024-09-12 20:25:01.380 [genericillustration] [genericillustration] the animation is finished!
multiple ZoomFrames
on multiple images¶
Now, let's build a bit on that by including both the original images and some median-subtracted ones. In addition to the extra frames, we're also including some more complicated options in generating the illustration layout.
# first create a sequence
big = make_image_sequence(create_test_stamp(N=10, xsize=80, ysize=100))
# then define some frames
image = imshowFrame(name='big-image', data=big, title='imshow')
zoom = ZoomFrame(name='zoom', source=image, position=(25, 50), size=(20,10), title='zoom')
subtracted = imshowFrame(name='big-subtracted', data=big, title='subtracted', processingsteps=['subtractmedian'])
subtractedzoom = ZoomFrame(name='zoom-subtracted', source=subtracted, position=(25, 50), size=(20,10), title='zoom')
# then populate an illustration with them
i = GenericIllustration(imshows=[image, zoom, subtracted, subtractedzoom], imshowrows=2,
figsize=(7,6), hspace=0.2, wspace=0.02,
left=0.05, right=0.95, bottom=0.05, top=0.9)
i.plot()
i.animate('an-example-of-a-more-complicated-zoom.mp4', dpi=75)
[imshowframe] plotting <F"big-image"> for the first time [imshowframe] {} [genericillustration] {} [genericillustration] included <F"big-image"> in the shared color scheme [genericillustration] included <F"zoom"> in the shared color scheme [stamp-sequence] creating a median image for <stamp-sequence of 10 images of shape (100, 80)> [genericillustration] included <F"big-subtracted"> in the shared color scheme [genericillustration] included <F"zoom-subtracted"> in the shared color scheme [genericillustration] defined color scheme with cmap=RdBu norm=<matplotlib.colors.SymLogNorm object at 0x17ee92090> ticks=[-1606.8634285464254, -40.47184964782226, 0, 40.47184964782226, 1606.8634285464254] [imshowframe] added image of shape (100, 80) to <F"big-image"> [imshowframe] making sure a shared colorbar is set up for <F"big-image"> [imshowframe] added a shared colorbar for <I"Generic"> [genericillustration] adding a new colorbar for 4 frame(s) [imshowframe] defining 2458119.5 as the time offset [imshowframe] added time label of "t=2458119.50000+0.00000 d" on <F"big-image"> [imshowframe] added arrows on <F"big-image"> [imshowframe] added title of "imshow" to <F"big-image"> [zoomframe] plotting <F"zoom"> for the first time [zoomframe] {} [genericillustration] {} [zoomframe] added image of shape (10, 20) to <F"zoom"> [imshowframe] plotting <F"big-subtracted"> for the first time [imshowframe] {} [genericillustration] {} [imshowframe] added image of shape (100, 80) to <F"big-subtracted"> [imshowframe] making sure a shared colorbar is set up for <F"big-subtracted"> [imshowframe] defining 2458119.5 as the time offset [imshowframe] added time label of "t=2458119.50000+0.00000 d" on <F"big- subtracted"> [imshowframe] added arrows on <F"big-subtracted"> [imshowframe] added title of "subtracted" to <F"big-subtracted"> [zoomframe] plotting <F"zoom-subtracted"> for the first time [zoomframe] {} [genericillustration] {} [zoomframe] added image of shape (10, 20) to <F"zoom-subtracted"> [genericillustration] about to animate 20 times at 1.0s cadence for <I"Generic"> [genericillustration] 30 frames/second : [genericillustration] the animation will be saved to an-example-of-a-more-complicated- zoom.mp4 [genericillustration] 1/20 at 2024-09-12 20:25:01.707 [genericillustration] 2/20 at 2024-09-12 20:25:01.766 [genericillustration] 3/20 at 2024-09-12 20:25:01.806 [genericillustration] 4/20 at 2024-09-12 20:25:01.857
[genericillustration] 5/20 at 2024-09-12 20:25:01.907 [genericillustration] 6/20 at 2024-09-12 20:25:01.953 [genericillustration] 7/20 at 2024-09-12 20:25:01.992 [genericillustration] 8/20 at 2024-09-12 20:25:02.021 [genericillustration] 9/20 at 2024-09-12 20:25:02.050 [genericillustration] 10/20 at 2024-09-12 20:25:02.079 [genericillustration] 11/20 at 2024-09-12 20:25:02.108
[genericillustration] 12/20 at 2024-09-12 20:25:02.139 [genericillustration] 13/20 at 2024-09-12 20:25:02.167 [genericillustration] 14/20 at 2024-09-12 20:25:02.201 [genericillustration] 15/20 at 2024-09-12 20:25:02.233 [genericillustration] 16/20 at 2024-09-12 20:25:02.266 [genericillustration] 17/20 at 2024-09-12 20:25:02.298 [genericillustration] 18/20 at 2024-09-12 20:25:02.331
[genericillustration] 19/20 at 2024-09-12 20:25:02.363 [genericillustration] 20/20 at 2024-09-12 20:25:02.397 [genericillustration] [genericillustration] the animation is finished!
an EmptyTimeseriesFrame
linked to an imshowFrame
¶
It might be nice to see a light curve synced up to the data from which it is derived. The EmptyTimeseriesFrame
provides us with an empty frame, into which we can plot some time-series data. Here's a first step toward that capability; it still needs a bit of work, but hopefully this is enough to get started. The basic idea is to create an available plot location (an Axes
object, in matplotlib
-speak), where you can fill later with whatever plot elements you like. The below example demonstrates how to populate the time-series frame in two ways, both using its built-in .plot
function and simply by adding elements to i.frames['timeseries'].ax
.
# make a dataset
star = make_image_sequence(create_test_stamp(N=25, xsize=10, ysize=10, single=True))
# create an empty time-series frame into which we will plot a timeseries
lightcurve = EmptyTimeseriesFrame(name='timeseries')
someimage = imshowFrame(name='image', data=star, title='', plotingredients=['image', 'arrows', 'colorbar'])
# create an illustration that positions these frames next to each other
i = SideBySideIllustration(timeseries=[lightcurve], imshows=[someimage])
# plotting the illustration creates the basic structure...
#i.plot()
# ...but we can still modify it through the individual frames
f = i.frames['timeseries']
# the time-series f.plot plot will subtract a the illustration's shared time offset
time = star.time.jd
flux = star._gather_3d().sum(-1).sum(-1)
f.plot(time, flux, marker='o', color='black')
# the f.ax refers to the time-series axes, so this is how we set up to plot into it
plt.sca(f.ax)
plt.axhline(np.mean(flux), color='cornflowerblue', zorder=-1)
plt.ylabel('Some Flux')
i.plot()
#i.animate('an-example-of-a-timeseries.mp4')
[emptytimeseriesframe] defining 2458119.5 as the time offset [imshowframe] plotting <F"image"> for the first time [imshowframe] {} [sidebysideillustration] {} [sidebysideillustration] included <F"image"> in the shared color scheme [sidebysideillustration] found no color scheme data for <F"timeseries"> [sidebysideillustration] defined color scheme with cmap=Blues norm=<matplotlib.colors.LogNorm object at 0x17eb64ce0> ticks=[19.314367172532123, 144.9660254156713, 1088.0578347243859] [imshowframe] added image of shape (10, 10) to <F"image"> [imshowframe] making sure a shared colorbar is set up for <F"image"> [imshowframe] added a shared colorbar for <I"Timeseries"> [sidebysideillustration] adding a new colorbar for 1 frame(s) [imshowframe] added arrows on <F"image">
The interface for plotting timeseries is still a little buggy and may change soon. For now, hopefully you find this helpful!
Some more examples are included, in one way or another, in the tests/
directory of the playground
repository. Check those out, or please feel free to contribute new examples here! As always, please do not hesitate to contact Zach Berta-Thompson or leave an issue on the github repository.