diff --git a/v/pdev/.buildinfo b/v/pdev/.buildinfo
new file mode 100644
index 00000000..a65b4b08
--- /dev/null
+++ b/v/pdev/.buildinfo
@@ -0,0 +1,4 @@
+# Sphinx build info version 1
+# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
+config: 8b4d12baca50d6e4cfc56a5a419baf15
+tags: 645f666f9bcd5a90fca523b33c5a78b7
diff --git a/v/pdev/_downloads/59793ee2752570cbec02c0d2fd15151e/convenience_functions.py b/v/pdev/_downloads/59793ee2752570cbec02c0d2fd15151e/convenience_functions.py
new file mode 100644
index 00000000..29b0bbda
--- /dev/null
+++ b/v/pdev/_downloads/59793ee2752570cbec02c0d2fd15151e/convenience_functions.py
@@ -0,0 +1,236 @@
+from astropy import visualization as aviz
+from astropy.nddata.blocks import block_reduce
+from astropy.nddata.utils import Cutout2D
+from matplotlib import pyplot as plt
+
+
+def show_image(image,
+ percl=99, percu=None, is_mask=False,
+ figsize=(10, 10),
+ cmap='viridis', log=False, clip=True,
+ show_colorbar=True, show_ticks=True,
+ fig=None, ax=None, input_ratio=None):
+ """
+ Show an image in matplotlib with some basic astronomically-appropriat stretching.
+
+ Parameters
+ ----------
+ image
+ The image to show
+ percl : number
+ The percentile for the lower edge of the stretch (or both edges if ``percu`` is None)
+ percu : number or None
+ The percentile for the upper edge of the stretch (or None to use ``percl`` for both)
+ figsize : 2-tuple
+ The size of the matplotlib figure in inches
+ """
+ if percu is None:
+ percu = percl
+ percl = 100 - percl
+
+ if (fig is None and ax is not None) or (fig is not None and ax is None):
+ raise ValueError('Must provide both "fig" and "ax" '
+ 'if you provide one of them')
+ elif fig is None and ax is None:
+ if figsize is not None:
+ # Rescale the fig size to match the image dimensions, roughly
+ image_aspect_ratio = image.shape[0] / image.shape[1]
+ figsize = (max(figsize) * image_aspect_ratio, max(figsize))
+
+ fig, ax = plt.subplots(1, 1, figsize=figsize)
+
+
+ # To preserve details we should *really* downsample correctly and
+ # not rely on matplotlib to do it correctly for us (it won't).
+
+ # So, calculate the size of the figure in pixels, block_reduce to
+ # roughly that,and display the block reduced image.
+
+ # Thanks, https://stackoverflow.com/questions/29702424/how-to-get-matplotlib-figure-size
+ fig_size_pix = fig.get_size_inches() * fig.dpi
+
+ ratio = (image.shape // fig_size_pix).max()
+
+ if ratio < 1:
+ ratio = 1
+
+ ratio = input_ratio or ratio
+
+ reduced_data = block_reduce(image, ratio)
+
+ if not is_mask:
+ # Divide by the square of the ratio to keep the flux the same in the
+ # reduced image. We do *not* want to do this for images which are
+ # masks, since their values should be zero or one.
+ reduced_data = reduced_data / ratio**2
+
+ # Of course, now that we have downsampled, the axis limits are changed to
+ # match the smaller image size. Setting the extent will do the trick to
+ # change the axis display back to showing the actual extent of the image.
+ extent = [0, image.shape[1], 0, image.shape[0]]
+
+ if log:
+ stretch = aviz.LogStretch()
+ else:
+ stretch = aviz.LinearStretch()
+
+ norm = aviz.ImageNormalize(reduced_data,
+ interval=aviz.AsymmetricPercentileInterval(percl, percu),
+ stretch=stretch, clip=clip)
+
+ if is_mask:
+ # The image is a mask in which pixels should be zero or one.
+ # block_reduce may have changed some of the values, so reset here.
+ reduced_data = reduced_data > 0
+ # Set the image scale limits appropriately.
+ scale_args = dict(vmin=0, vmax=1)
+ else:
+ scale_args = dict(norm=norm)
+
+ im = ax.imshow(reduced_data, origin='lower',
+ cmap=cmap, extent=extent, aspect='equal', **scale_args)
+
+ if show_colorbar:
+ # I haven't a clue why the fraction and pad arguments below work to make
+ # the colorbar the same height as the image, but they do....unless the image
+ # is wider than it is tall. Sticking with this for now anyway...
+ # Thanks: https://stackoverflow.com/a/26720422/3486425
+ fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
+ # In case someone in the future wants to improve this:
+ # https://joseph-long.com/writing/colorbars/
+ # https://stackoverflow.com/a/33505522/3486425
+ # https://matplotlib.org/mpl_toolkits/axes_grid/users/overview.html#colorbar-whose-height-or-width-in-sync-with-the-master-axes
+
+ if not show_ticks:
+ ax.tick_params(labelbottom=False, labelleft=False, labelright=False, labeltop=False)
+
+
+def image_snippet(image, center, width=50, axis=None, fig=None,
+ is_mask=False, pad_black=False, **kwargs):
+ """
+ Display a subsection of an image about a center.
+
+ Parameters
+ ----------
+
+ image : numpy array
+ The full image from which a section is to be taken.
+
+ center : list-like
+ The location of the center of the cutout.
+
+ width : int, optional
+ Width of the cutout, in pixels.
+
+ axis : matplotlib.Axes instance, optional
+ Axis on which the image should be displayed.
+
+ fig : matplotlib.Figure, optional
+ Figure on which the image should be displayed.
+
+ is_mask : bool, optional
+ Set to ``True`` if the image is a mask, i.e. all values are
+ either zero or one.
+
+ pad_black : bool, optional
+ If ``True``, pad edges of the image with zeros to fill out width
+ if the slice is near the edge.
+ """
+ if pad_black:
+ sub_image = Cutout2D(image, center, width, mode='partial', fill_value=0)
+ else:
+ # Return a smaller subimage if extent goes out side image
+ sub_image = Cutout2D(image, center, width, mode='trim')
+ show_image(sub_image.data, cmap='gray', ax=axis, fig=fig,
+ show_colorbar=False, show_ticks=False, is_mask=is_mask,
+ **kwargs)
+
+
+def _mid(sl):
+ return (sl.start + sl.stop) // 2
+
+
+def display_cosmic_rays(cosmic_rays, images, titles=None,
+ only_display_rays=None):
+ """
+ Display cutouts of the region around each cosmic ray and the other images
+ passed in.
+
+ Parameters
+ ----------
+
+ cosmic_rays : photutils.segmentation.SegmentationImage
+ The segmented cosmic ray image returned by ``photuils.detect_source``.
+
+ images : list of images
+ The list of images to be displayed. Each image becomes a column in
+ the generated plot. The first image must be the cosmic ray mask.
+
+ titles : list of str
+ Titles to be put above the first row of images.
+
+ only_display_rays : list of int, optional
+ The number of the cosmic ray(s) to display. The default value,
+ ``None``, means display them all. The number of the cosmic ray is
+ its index in ``cosmic_rays``, which is also the number displayed
+ on the mask.
+ """
+ # Check whether the first image is actually a mask.
+
+ if not ((images[0] == 0) | (images[0] == 1)).all():
+ raise ValueError('The first image must be a mask with '
+ 'values of zero or one')
+
+ if only_display_rays is None:
+ n_rows = len(cosmic_rays.slices)
+ else:
+ n_rows = len(only_display_rays)
+
+ n_columns = len(images)
+
+ width = 12
+
+ # The height below is *CRITICAL*. If the aspect ratio of the figure as
+ # a whole does not allow for square plots then one ends up with a bunch
+ # of whitespace. The plots here are square by design.
+ height = width / n_columns * n_rows
+ fig, axes = plt.subplots(n_rows, n_columns, sharex=False, sharey='row',
+ figsize=(width, height))
+
+ # Generate empty titles if none were provided.
+ if titles is None:
+ titles = [''] * n_columns
+
+ display_row = 0
+
+ for row, s in enumerate(cosmic_rays.slices):
+ if only_display_rays is not None:
+ if row not in only_display_rays:
+ # We are not supposed to display this one, so skip it.
+ continue
+
+ x = _mid(s[1])
+ y = _mid(s[0])
+
+ for column, plot_info in enumerate(zip(images, titles)):
+ image = plot_info[0]
+ title = plot_info[1]
+ is_mask = column == 0
+ ax = axes[display_row, column]
+ image_snippet(image, (x, y), width=80, axis=ax, fig=fig,
+ is_mask=is_mask)
+ if is_mask:
+ ax.annotate('Cosmic ray {}'.format(row), (0.1, 0.9),
+ xycoords='axes fraction',
+ color='cyan', fontsize=20)
+
+ if display_row == 0:
+ # Only set the title if it isn't empty.
+ if title:
+ ax.set_title(title)
+
+ display_row = display_row + 1
+
+ # This choice results in the images close to each other but with
+ # a small gap.
+ plt.subplots_adjust(wspace=0.1, hspace=0.05)
diff --git a/v/pdev/_downloads/76047cce970667de1321ff175a84750a/image_sim.py b/v/pdev/_downloads/76047cce970667de1321ff175a84750a/image_sim.py
new file mode 100644
index 00000000..b8bb7bdf
--- /dev/null
+++ b/v/pdev/_downloads/76047cce970667de1321ff175a84750a/image_sim.py
@@ -0,0 +1,340 @@
+import os
+
+import numpy as np
+
+from astropy.modeling.models import Gaussian2D, RickerWavelet2D, Const2D
+from photutils.datasets import (make_random_gaussians_table,
+ make_gaussian_sources_image)
+from photutils.aperture import EllipticalAperture
+
+# To use a seed, set it in the environment. Useful for minimizing changes when
+# publishing the book.
+seed = os.getenv('GUIDE_RANDOM_SEED', None)
+
+if seed is not None:
+ seed = int(seed)
+
+default_rng = np.random.default_rng(seed)
+
+
+def read_noise(image, amount, gain=1):
+ """
+ Generate simulated read noise.
+
+ Parameters
+ ----------
+
+ image: numpy array
+ Image whose shape the noise array should match.
+ amount : float
+ Amount of read noise, in electrons.
+ gain : float, optional
+ Gain of the camera, in units of electrons/ADU.
+ """
+ shape = image.shape
+
+ noise = default_rng.normal(scale=amount / gain, size=shape)
+
+ return noise
+
+
+def bias(image, value, realistic=False):
+ """
+ Generate simulated bias image.
+
+ Parameters
+ ----------
+
+ image: numpy array
+ Image whose shape the bias array should match.
+ value: float
+ Bias level to add.
+ realistic : bool, optional
+ If ``True``, add some clomuns with somewhat higher bias value
+ (a not uncommon thing)
+ """
+ # This is the whole thing: the bias is really suppose to be a constant
+ # offset!
+ bias_im = np.zeros_like(image) + value
+
+ # If we want a more realistic bias we need to do a little more work.
+ if realistic:
+ shape = image.shape
+ number_of_colums = 5
+
+ # We want a random-looking variation in the bias, but unlike the
+ # readnoise the bias should *not* change from image to image, so we
+ # make sure to always generate the same "random" numbers.
+ rng = np.random.RandomState(seed=8392) # 20180520
+ columns = rng.randint(0, shape[1], size=number_of_colums)
+ # This adds a little random-looking noise into the data.
+ col_pattern = rng.randint(0, int(0.1 * value), size=shape[0])
+
+ # Make the chosen columns a little brighter than the rest...
+ for c in columns:
+ bias_im[:, c] = value + col_pattern
+
+ return bias_im
+
+
+def dark_current(image, current, exposure_time, gain=1.0, hot_pixels=False):
+ """
+ Simulate dark current in a CCD, optionally including hot pixels.
+
+ Parameters
+ ----------
+
+ image : numpy array
+ Image whose shape the cosmic array should match.
+ current : float
+ Dark current, in electrons/pixel/second, which is the way
+ manufacturers typically report it.
+ exposure_time : float
+ Length of the simulated exposure, in seconds.
+ gain : float, optional
+ Gain of the camera, in units of electrons/ADU.
+ hot_pixels : bool, optional
+ If ``True``, add hot pixels to the image.
+
+ Returns
+ -------
+
+ numpy array
+ An array the same shape and dtype as the input containing dark counts
+ in units of ADU.
+ """
+
+ # dark current for every pixel; we'll modify the current for some pixels if
+ # the user wants hot pixels.
+ base_current = current * exposure_time / gain
+
+ # This random number generation should change on each call.
+ dark_im = default_rng.poisson(base_current, size=image.shape)
+
+ if hot_pixels:
+ # We'll set 0.01% of the pixels to be hot; that is probably too high
+ # but should ensure they are visible.
+ y_max, x_max = dark_im.shape
+
+ n_hot = int(0.0001 * x_max * y_max)
+
+ # Like with the bias image, we want the hot pixels to always be in the
+ # same places (at least for the same image size) but also want them to
+ # appear to be randomly distributed. So we set a random number seed to
+ # ensure we always get the same thing.
+ rng = np.random.RandomState(16201649)
+ hot_x = rng.randint(0, x_max, size=n_hot)
+ hot_y = rng.randint(0, y_max, size=n_hot)
+
+ hot_current = 10000 * current
+
+ dark_im[(hot_y, hot_x)] = hot_current * exposure_time / gain
+
+ return dark_im
+
+
+def sky_background(image, sky_counts, gain=1):
+ """
+ Generate sky background, optionally including a gradient across the
+ image (because some times Moons happen).
+
+ Parameters
+ ----------
+
+ image : numpy array
+ Image whose shape the cosmic array should match.
+ sky_counts : float
+ The target value for the number of counts (as opposed to electrons or
+ photons) from the sky.
+ gain : float, optional
+ Gain of the camera, in units of electrons/ADU.
+ """
+ sky_im = default_rng.poisson(sky_counts * gain, size=image.shape) / gain
+
+ return sky_im
+
+
+def stars(image, number, max_counts=10000, gain=1, fwhm=4):
+ """
+ Add some stars to the image.
+ """
+ # Most of the code below is a direct copy/paste from
+ # https://photutils.readthedocs.io/en/stable/_modules/photutils/datasets/make.html#make_100gaussians_image
+
+ flux_range = [max_counts / 10, max_counts]
+
+ y_max, x_max = image.shape
+ xmean_range = [0.1 * x_max, 0.9 * x_max]
+ ymean_range = [0.1 * y_max, 0.9 * y_max]
+ xstddev_range = [fwhm, fwhm]
+ ystddev_range = [fwhm, fwhm]
+ params = dict([('amplitude', flux_range),
+ ('x_mean', xmean_range),
+ ('y_mean', ymean_range),
+ ('x_stddev', xstddev_range),
+ ('y_stddev', ystddev_range),
+ ('theta', [0, 2 * np.pi])])
+
+ sources = make_random_gaussians_table(number, params,
+ seed=12345)
+
+ star_im = make_gaussian_sources_image(image.shape, sources)
+
+ return star_im
+
+
+def make_cosmic_rays(image, number, strength=10000):
+ """
+ Generate an image with a few cosmic rays.
+
+ Parameters
+ ----------
+
+ image numpy array
+ Image whose shape the cosmic array should match.
+ number: float
+ Number of cosmic rays to add to the image.
+ strength : float, optional
+ Pixel count in the cosmic rays.
+ """
+
+ cr_image = np.zeros_like(image)
+
+ # Yes, the order below is correct. The x axis is the column, which
+ # is the second index.
+ max_y, max_x = cr_image.shape
+
+ # Get the smallest dimension to ensure the cosmic rays are within the image
+ maximum_pos = np.min(cr_image.shape)
+ # These will be center points of the cosmic rays, which we place away from
+ # the edges to ensure they are visible.
+ xy_cr = default_rng.integers(0.1 * maximum_pos, 0.9 * maximum_pos,
+ size=[number, 2])
+
+ cr_length = 5 # pixels, a little big
+ cr_width = 2
+ theta_cr = 2 * np.pi * default_rng.uniform()
+ apertures = EllipticalAperture(xy_cr, cr_length, cr_width, theta_cr)
+ masks = apertures.to_mask(method='center')
+ for mask in masks:
+ cr_image += strength * mask.to_image(shape=cr_image.shape)
+
+ return cr_image
+
+
+# Functions related to simulated flat images
+
+def make_one_donut(center, diameter=10, amplitude=0.25):
+ sigma = diameter / 2
+ mh = RickerWavelet2D(amplitude=amplitude,
+ x_0=center[0], y_0=center[1],
+ sigma=sigma)
+ gauss = Gaussian2D(amplitude=amplitude,
+ x_mean=center[0], y_mean=center[1],
+ x_stddev=sigma, y_stddev=sigma)
+ return Const2D(amplitude=1) + (mh - gauss)
+
+
+def add_donuts(image, number=20):
+ """
+ Create a transfer function, i.e. matrix by which you multiply
+ input counts to obtain actual counts.
+
+ Parameters
+ ----------
+
+
+ image : numpy array
+ Image whose shape the cosmic array should match.
+
+ number : int, optional
+ Number of dust donuts to add.
+ """
+
+ y, x = np.indices(image.shape)
+
+ # The dust donuts should always be in the same place...
+ rng = np.random.RandomState(43901)
+ shape = np.array(image.shape)
+ border_padding = 50
+
+ # We'll make the dust specks range from 1% to 5% of the image size, but
+ # only in a couple of sizes. The dust grains themselves are fairly uniform
+ # in size (I think), and there are only a fwe elements on which dust can
+ # settle. Size on the image is determined by size of the dust and how far
+ # it is from the CCD chip.
+
+ min_diam = int(0.02 * shape.max())
+ max_diam = int(0.05 * shape.max())
+
+ # Weight towards the smaller donuts because it looks more like real flats..
+ diameters = rng.choice([min_diam, min_diam, min_diam, max_diam],
+ size=number)
+
+ # Add a little variation in amplitude
+ amplitudes = rng.normal(0.25, 0.05, size=number)
+ center_x = rng.randint(border_padding,
+ high=shape[1] - border_padding, size=number)
+ center_y = rng.randint(border_padding,
+ high=shape[0] - border_padding, size=number)
+ centers = [[x, y] for x, y in zip(center_x, center_y)]
+
+ donut_model = make_one_donut(centers[0], diameter=diameters[0],
+ amplitude=amplitudes[0])
+ donut_im = donut_model(x, y)
+ idx = 1
+ for center, diam, amplitude in zip(centers[1:],
+ diameters[1:],
+ amplitudes[1:]):
+ idx += 1
+ donut_model = make_one_donut(center, diameter=diam,
+ amplitude=amplitude)
+ donut_im += donut_model(x, y)
+
+ donut_im /= number
+
+ return donut_im
+
+
+def sensitivity_variations(image, vignetting=True, dust=True):
+ """
+ Create a transfer function, i.e. matrix by which you multiply input
+ counts to obtain actual counts.
+
+ Parameters
+ ----------
+
+
+ image : numpy array
+ Image whose shape the cosmic array should match.
+
+ vignetting : bool, optional
+ If ``True``, darken image near corners.
+
+ dust : bool, optional
+ If ``True``, add some plausible-looking dust.
+ """
+
+ sensitivity = np.zeros_like(image) + 1.0
+ shape = np.array(sensitivity.shape)
+
+ if dust or vignetting:
+ # Yes, this should be y, x.
+ y, x = np.indices(sensitivity.shape)
+
+ if vignetting:
+ # Generate very wide gaussian centered on the center of the image,
+ # multiply the sensitivity by it.
+ vign_model = Gaussian2D(amplitude=1,
+ x_mean=shape[0] / 2, y_mean=shape[1] / 2,
+ x_stddev=2 * shape.max(),
+ y_stddev=2 * shape.max())
+ vign_im = vign_model(x, y)
+ sensitivity *= vign_im
+
+ if dust:
+ dust_im = add_donuts(image, number=40)
+ dust_im = dust_im / dust_im.max()
+ sensitivity *= dust_im
+
+ return sensitivity
diff --git a/v/pdev/_downloads/88873bf83a31f4f7389e718868c0ff1d/photutils_notebook_style.mplstyle b/v/pdev/_downloads/88873bf83a31f4f7389e718868c0ff1d/photutils_notebook_style.mplstyle
new file mode 100644
index 00000000..7b9b10d6
--- /dev/null
+++ b/v/pdev/_downloads/88873bf83a31f4f7389e718868c0ff1d/photutils_notebook_style.mplstyle
@@ -0,0 +1,20 @@
+# ---- Matplotlib formatting ----
+
+font.family : serif
+font.weight : light
+mathtext.bf : serif:normal
+
+font.size : 12
+axes.titlesize : 20
+axes.titlepad : 12
+axes.labelsize : 18
+xtick.labelsize : 16
+ytick.labelsize : 16
+
+
+
+figure.subplot.bottom : 0.15
+figure.dpi : 200
+
+savefig.dpi : 300
+savefig.transparent : True
diff --git a/v/pdev/_images/02fc8f890fd313c7f9c6773d3977a31a7fd8d7d4e9d6a7a8243e9dee1e4f607b.png b/v/pdev/_images/02fc8f890fd313c7f9c6773d3977a31a7fd8d7d4e9d6a7a8243e9dee1e4f607b.png
new file mode 100644
index 00000000..bbad99d7
Binary files /dev/null and b/v/pdev/_images/02fc8f890fd313c7f9c6773d3977a31a7fd8d7d4e9d6a7a8243e9dee1e4f607b.png differ
diff --git a/v/pdev/_images/20f808a0b0791a70a216895bb435f3c1a6eef263ee93ff2e6cfb0e826a898278.png b/v/pdev/_images/20f808a0b0791a70a216895bb435f3c1a6eef263ee93ff2e6cfb0e826a898278.png
new file mode 100644
index 00000000..d7dac916
Binary files /dev/null and b/v/pdev/_images/20f808a0b0791a70a216895bb435f3c1a6eef263ee93ff2e6cfb0e826a898278.png differ
diff --git a/v/pdev/_images/3716b2b55d05cb5023b46c6369869bd66d31dd87199eaacb2764fa93a92093ba.png b/v/pdev/_images/3716b2b55d05cb5023b46c6369869bd66d31dd87199eaacb2764fa93a92093ba.png
new file mode 100644
index 00000000..f6b6eaeb
Binary files /dev/null and b/v/pdev/_images/3716b2b55d05cb5023b46c6369869bd66d31dd87199eaacb2764fa93a92093ba.png differ
diff --git a/v/pdev/_images/39276fb27d2da687a87c52a9b27b6050ae29d51d4c5e04b0efb3a12944411289.png b/v/pdev/_images/39276fb27d2da687a87c52a9b27b6050ae29d51d4c5e04b0efb3a12944411289.png
new file mode 100644
index 00000000..a43d4703
Binary files /dev/null and b/v/pdev/_images/39276fb27d2da687a87c52a9b27b6050ae29d51d4c5e04b0efb3a12944411289.png differ
diff --git a/v/pdev/_images/4bf025b836ce7a0889d8d71d5c873da4cc3a6811d9ee95978e3fbe5a1455a120.png b/v/pdev/_images/4bf025b836ce7a0889d8d71d5c873da4cc3a6811d9ee95978e3fbe5a1455a120.png
new file mode 100644
index 00000000..be99de45
Binary files /dev/null and b/v/pdev/_images/4bf025b836ce7a0889d8d71d5c873da4cc3a6811d9ee95978e3fbe5a1455a120.png differ
diff --git a/v/pdev/_images/4e19bb0ccd841c4688a79f8c163d162d931432bb1ac0b4e8d7a1c0821a4fc1dc.png b/v/pdev/_images/4e19bb0ccd841c4688a79f8c163d162d931432bb1ac0b4e8d7a1c0821a4fc1dc.png
new file mode 100644
index 00000000..ca9e8e85
Binary files /dev/null and b/v/pdev/_images/4e19bb0ccd841c4688a79f8c163d162d931432bb1ac0b4e8d7a1c0821a4fc1dc.png differ
diff --git a/v/pdev/_images/691717e93644692808a62deae37b56ba8406f753565db873bd0f3d93ef48a242.png b/v/pdev/_images/691717e93644692808a62deae37b56ba8406f753565db873bd0f3d93ef48a242.png
new file mode 100644
index 00000000..1faa5738
Binary files /dev/null and b/v/pdev/_images/691717e93644692808a62deae37b56ba8406f753565db873bd0f3d93ef48a242.png differ
diff --git a/v/pdev/_images/6d3b7a910df2dca79592aa548438d072e5c0bf8c83adac094c3ca9f4e6677672.png b/v/pdev/_images/6d3b7a910df2dca79592aa548438d072e5c0bf8c83adac094c3ca9f4e6677672.png
new file mode 100644
index 00000000..13411b7b
Binary files /dev/null and b/v/pdev/_images/6d3b7a910df2dca79592aa548438d072e5c0bf8c83adac094c3ca9f4e6677672.png differ
diff --git a/v/pdev/_images/6febc0bd6a2f8a79b8c5e95f65d12460435a08d173d787bae9ed28751d8b2cc5.png b/v/pdev/_images/6febc0bd6a2f8a79b8c5e95f65d12460435a08d173d787bae9ed28751d8b2cc5.png
new file mode 100644
index 00000000..8d0072b9
Binary files /dev/null and b/v/pdev/_images/6febc0bd6a2f8a79b8c5e95f65d12460435a08d173d787bae9ed28751d8b2cc5.png differ
diff --git a/v/pdev/_images/94dd5aec16b28f420bd4bc16ce0a8b219be2e780d2cad74f71502c636fdef1bc.png b/v/pdev/_images/94dd5aec16b28f420bd4bc16ce0a8b219be2e780d2cad74f71502c636fdef1bc.png
new file mode 100644
index 00000000..96e3d27d
Binary files /dev/null and b/v/pdev/_images/94dd5aec16b28f420bd4bc16ce0a8b219be2e780d2cad74f71502c636fdef1bc.png differ
diff --git a/v/pdev/_images/a2a99a5e6caf5bcf52a9fa60bde4b47e10130daebd6a9da7cd2821a0a8c066b1.png b/v/pdev/_images/a2a99a5e6caf5bcf52a9fa60bde4b47e10130daebd6a9da7cd2821a0a8c066b1.png
new file mode 100644
index 00000000..b1b7a146
Binary files /dev/null and b/v/pdev/_images/a2a99a5e6caf5bcf52a9fa60bde4b47e10130daebd6a9da7cd2821a0a8c066b1.png differ
diff --git a/v/pdev/_images/aa32f23eed647f24836317060ddb02aa088cd6dc8f04f487983f753fecccac95.png b/v/pdev/_images/aa32f23eed647f24836317060ddb02aa088cd6dc8f04f487983f753fecccac95.png
new file mode 100644
index 00000000..e1d15397
Binary files /dev/null and b/v/pdev/_images/aa32f23eed647f24836317060ddb02aa088cd6dc8f04f487983f753fecccac95.png differ
diff --git a/v/pdev/_images/be493213701176ec8baafa3e66be96f731376845939a8a275cd4c26fb7f6f628.png b/v/pdev/_images/be493213701176ec8baafa3e66be96f731376845939a8a275cd4c26fb7f6f628.png
new file mode 100644
index 00000000..96376367
Binary files /dev/null and b/v/pdev/_images/be493213701176ec8baafa3e66be96f731376845939a8a275cd4c26fb7f6f628.png differ
diff --git a/v/pdev/_images/c087b345427dae017339b3691ed7c6db96c69d8337a10059d7392911e8469009.png b/v/pdev/_images/c087b345427dae017339b3691ed7c6db96c69d8337a10059d7392911e8469009.png
new file mode 100644
index 00000000..51cc64af
Binary files /dev/null and b/v/pdev/_images/c087b345427dae017339b3691ed7c6db96c69d8337a10059d7392911e8469009.png differ
diff --git a/v/pdev/_images/c7f9458978f5ef7fad039ad498a3fb52ef92c7fe5ba4cf1802c0b11459d56922.png b/v/pdev/_images/c7f9458978f5ef7fad039ad498a3fb52ef92c7fe5ba4cf1802c0b11459d56922.png
new file mode 100644
index 00000000..69670dfc
Binary files /dev/null and b/v/pdev/_images/c7f9458978f5ef7fad039ad498a3fb52ef92c7fe5ba4cf1802c0b11459d56922.png differ
diff --git a/v/pdev/_images/dce4a1f9b85ed64dbec16d35c27a5b696f7e6f3b54b4bb68fabc68c4c14b9481.png b/v/pdev/_images/dce4a1f9b85ed64dbec16d35c27a5b696f7e6f3b54b4bb68fabc68c4c14b9481.png
new file mode 100644
index 00000000..481b0b9e
Binary files /dev/null and b/v/pdev/_images/dce4a1f9b85ed64dbec16d35c27a5b696f7e6f3b54b4bb68fabc68c4c14b9481.png differ
diff --git a/v/pdev/_images/dde15cffdc02fccde621fa200fe0bd0649b580de836521c6d1a9617525384f64.png b/v/pdev/_images/dde15cffdc02fccde621fa200fe0bd0649b580de836521c6d1a9617525384f64.png
new file mode 100644
index 00000000..2b36ad1f
Binary files /dev/null and b/v/pdev/_images/dde15cffdc02fccde621fa200fe0bd0649b580de836521c6d1a9617525384f64.png differ
diff --git a/v/pdev/_images/f127c162753db5b75918948d7ad87d6eca2a643f4225e6e3f8b302fbc6ac0364.png b/v/pdev/_images/f127c162753db5b75918948d7ad87d6eca2a643f4225e6e3f8b302fbc6ac0364.png
new file mode 100644
index 00000000..ce976de6
Binary files /dev/null and b/v/pdev/_images/f127c162753db5b75918948d7ad87d6eca2a643f4225e6e3f8b302fbc6ac0364.png differ
diff --git a/v/pdev/_images/fd798203e27423eafbbe8a47075f23524784f95dd033aed62e2f12fc1b2ee1c2.png b/v/pdev/_images/fd798203e27423eafbbe8a47075f23524784f95dd033aed62e2f12fc1b2ee1c2.png
new file mode 100644
index 00000000..af37e0c8
Binary files /dev/null and b/v/pdev/_images/fd798203e27423eafbbe8a47075f23524784f95dd033aed62e2f12fc1b2ee1c2.png differ
diff --git a/v/pdev/_sources/notebooks/00-00-Preface.ipynb b/v/pdev/_sources/notebooks/00-00-Preface.ipynb
new file mode 100644
index 00000000..a6e9bfe2
--- /dev/null
+++ b/v/pdev/_sources/notebooks/00-00-Preface.ipynb
@@ -0,0 +1,282 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Preface\n"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The purpose of this text is to walk through image reduction and photometry using\n",
+ "Python, especially Astropy and its affiliated packages. It assumes some basic\n",
+ "familiarity with astronomical images and with Python. The inspiration for this\n",
+ "work is a pair of guides written for IRAF, [\"A User's Guide to CCD Reductions with IRAF\" (Massey 1997)](http://www.ifa.hawaii.edu/~meech/a399/handouts/ccduser3.pdf) and\n",
+ "[\"A User's Guide to Stellar CCD Photometry with IRAF\" (Massey and Davis 1992)](https://www.mn.uio.no/astro/english/services/it/help/visualization/iraf/daophot2.pdf).\n",
+ "\n",
+ "The focus is on optical/IR images, not spectra."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Edition numbers"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The guide now has version numbers, roughly equivalent to editions in a printed\n",
+ "book. Each\n",
+ "number has three pieces -- for example the first official stable version is\n",
+ "`2.0.0`.\n",
+ "Think of that as roughly equivalent to \"edition\", \"revision\" and \"printing\" in\n",
+ "the physical\n",
+ "book world.\n",
+ "\n",
+ "The edition number is listed in the sidebar.\n",
+ "\n",
+ "This is what changes in each of those numbers means:\n",
+ "\n",
+ "+ Change in the first number, e.g. changing `2.0.0` → `3.0.0`, indicates a major\n",
+ "revision\n",
+ " that changes the numbering of sections or adds significant new sections.\n",
+ "+ Changes in the middle number, e.g. changing `2.0.0` → `2.1.0`, indicates\n",
+ "suibstantive\n",
+ " updates have been made to the content and/or important errors have been fixed.\n",
+ "The\n",
+ " section numbers will not change, though.\n",
+ "+ Changes in the last number, e.g. changing `2.0.0` → `2.0.1`, indicates minor\n",
+ "changes have\n",
+ " been made like fixing typographic errors, broken links, and similar\n",
+ "corrections. The\n",
+ " section numbers will not change in these revisions.\n",
+ "In addition, there is a \"development\" version available that reflects the latest\n",
+ "changes\n",
+ "made to the guide between releases.\n",
+ "\n",
+ "Those familiar with [semantic versioning](https://semver.org/) from software development\n",
+ "will recognize this as\n",
+ "roughly the equivalent for text."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Credits"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Authors"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This guide was written by Matt Craig and Lauren Chambers. Editing was done by\n",
+ "Lauren Glattly.\n",
+ "\n",
+ "New contributors will be moved from the acknowledgments to the author list when\n",
+ "they have either written roughly the equivalent of one section or provided\n",
+ "detailed review of several sections. This is intended as a rough guideline, and\n",
+ "when in doubt we will lean towards including people as authors rather than\n",
+ "excluding them."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Funding\n",
+ "\n",
+ "Made possible by the Astropy Project and ScienceBetter Consulting through\n",
+ "financial support from the Community Software Initiative at the Space Telescope\n",
+ "Science Institute."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Acknowledgments\n",
+ "\n",
+ "The following people contributed to this work by making suggestions, testing\n",
+ "code, or providing feedback on drafts. We are grateful for their assistance!\n",
+ "\n",
+ "+ Nicolás Cardiel\n",
+ "+ Simon Conseil\n",
+ "+ Lia Corrales\n",
+ "+ Kelle Cruz\n",
+ "+ Adam Ginsburg\n",
+ "+ Johannes Goller\n",
+ "+ Yash Gondhalekar\n",
+ "+ Lia G\n",
+ "+ Richard Hendricks\n",
+ "+ Stuart Littlefair\n",
+ "+ Matt Phillips\n",
+ "+ Benjamin Shafransky\n",
+ "+ Isobel Snellenberger\n",
+ "+ Kris Stern\n",
+ "+ Thomas Stibor\n",
+ "+ Sarah Tuttle\n",
+ "\n",
+ "If you have provided feedback and are not listed above, we apologize -- please\n",
+ "[open an issue here](https://github.com/astropy/ccd-reduction-and-photometry-guide/issues/new) so we can fix it."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Resources\n",
+ "\n",
+ "This astronomical content work was inspired by, and guided by, the excellent\n",
+ "resources below:\n",
+ "\n",
+ "+ [\"A User's Guide to CCD Reductions with IRAF\" (Massey 1997)](http://www.ifa.hawaii.edu/~meech/a399/handouts/ccduser3.pdf) is very thorough, but IRAF has become more\n",
+ "difficult to install over time and is no longer supported.\n",
+ "+ [\"A User's Guide to Stellar CCD Photometry with IRAF\" (Massey and Davis 1992)](https://www.mn.uio.no/astro/english/services/it/help/visualization/iraf/daophot2.pdf).\n",
+ "+ [The Handbook of Astronomical Image Processing](https://www.amazon.com/Handbook-Astronomical-Image-Processing/dp/0943396824) by Richard Berry and James Burnell. This\n",
+ "provides a very detailed overview of data reduction and photometry. One virtue\n",
+ "is its inclusion of *real* images with defects.\n",
+ "+ The [AAVSO CCD Obseving Manual](https://www.aavso.org/sites/default/files/publications_files/ccd_photometry_guide/CCDPhotometryGuide.pdf) provides a complete introduction to CCD\n",
+ "data reduction and photometry.\n",
+ "+ [A Beginner's Guide to Working with Astronomical Data](https://arxiv.org/abs/1905.13189) is much broader than this guide. It\n",
+ "includes an introduction to Python."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Software setup/getting started on your computer\n",
+ "\n",
+ "### Setting up Python\n",
+ "\n",
+ "To use any of this software you will need an installation of Python. We\n",
+ "recommend the [mambaforge installer](https://github.com/conda-forge/miniforge#mambaforge) or\n",
+ "[miniconda installer](https://conda.io/miniconda.html)\n",
+ " (or the much larger\n",
+ "[Anaconda Python distribution](https://www.anaconda.com/download/) if you must). Once you have that, you can\n",
+ "install\n",
+ "everything you need with:\n",
+ "\n",
+ "```\n",
+ "conda install -c astropy ccdproc photutils ipywidgets matplotlib jupyterlab\n",
+ "```\n",
+ "\n",
+ "(alternatively, `mamba` should work in place of `conda`; if you get an error\n",
+ "message saying you need to initialize `mamba` then follow the instructions in\n",
+ "that message.\n",
+ "\n",
+ "### Getting a copy of these notebooks\n",
+ "\n",
+ "Get a copy of these files from [https://github.com/astropy/ccd-reduction-and-photometry-guide](https://github.com/astropy/ccd-reduction-and-photometry-guide) -- click the\n",
+ "green \"Code\" button and either clone (if you are using `git`) or download the\n",
+ "zip file and extract it. If you are looking for a specific edition, e.g. `2.0.0`\n",
+ "then go to [https://github.com/astropy/ccd-reduction-and-photometry-guide/releases](https://github.com/astropy/ccd-reduction-and-photometry-guide/releases).\n",
+ "\n",
+ "\n",
+ "### If you want to run this code in a notebook from the command line\n",
+ "\n",
+ "Open a terminal, change directory into the `notebooks` folder in this\n",
+ "repository, type `jupyter lab` and a browser tab should open with the notebooks.\n",
+ "\n",
+ "_You will still need to download the data by following the instructions below in\n",
+ "[Data files](#data-files)_\n",
+ "\n",
+ "### If you want to run this code in some other way (`IPython`, `Spyder`, `Visual Studio Code`, ...)\n",
+ "\n",
+ "`Spyder` and `Visual Studio Code` can run notebooks; follow the instructions\n",
+ "[above](#getting-a-copy-of-these-notebooks) to get the notebooks. Follow the instructions\n",
+ "for opening notebooks in the tool you chose.\n",
+ "\n",
+ "If you are copy/pasting code from the online book into one of these tools, you\n",
+ "need two files from the repository. Follow the [instructions for getting a copy of the repository](#getting-a-copy-of-these-notebooks),\n",
+ "then copy the two files `download_data.py` and `convenience_functions.py` from\n",
+ "the `notebooks` folder to whatever folder you plan to run the code in.\n",
+ "\n",
+ "_You will still need to download the data by following the instructions below in\n",
+ "[Data files](#data-files)_"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Data files\n",
+ "\n",
+ "The list of the data files, and their approximate sizes, is below. You can\n",
+ "either download them one by one, or use the download helper included with these\n",
+ "notebooks.\n",
+ "\n",
+ "### Use this in a terminal to download the data\n",
+ "\n",
+ "```console\n",
+ "$ python download_data.py\n",
+ "```\n",
+ "\n",
+ "### Use this in a notebook cell to download the data\n",
+ "\n",
+ "```python\n",
+ "%run download_data.py\n",
+ "```\n",
+ "\n",
+ "### List of data files\n",
+ "\n",
+ "+ [Combination of 100 bias images (26MB)](https://zenodo.org/record/3320113/files/combined_bias_100_images.fit.bz2?download=1) (DOI: https://doi.org/10.5281/zenodo.3320113)\n",
+ "+ [Single bias image from thermoelectric camera](https://zenodo.org/record/5931364/files/single_bias_thermoelectric.fit.bz2?download=1) (DOI: https://doi.org/10.5281/zenodo.5931364)\n",
+ "+ [Single dark frame, exposure time 1,000 seconds (11MB)](https://zenodo.org/record/3312535/files/dark-test-0002d1000.fit.bz2?download=1) (DOI: https://doi.org/10.5281/zenodo.3312535)\n",
+ "+ [Combination of several dark frames, each 1,000 exposure time (52MB)](https://zenodo.org/record/4302262/files/combined_dark_exposure_1000.0.fit.bz2?download=1) (DOI: https://doi.org/10.5281/zenodo.4302262)\n",
+ "+ [Combination of several dark frames, each 300 sec (7MB)](https://zenodo.org/record/3332818/files/combined_dark_300.000.fits.bz2?download=1) (DOI: https://doi.org/10.5281/zenodo.3332818)\n",
+ "+ **\"Example 1\" in the reduction notebooks:** [Several images from the Palomar Large Format Camera, Chip 0 **(162MB)**](https://zenodo.org/record/3254683/files/example-cryo-LFC.tar.bz2?download=1)\n",
+ "(DOI: https://doi.org/10.5281/zenodo.3254683)\n",
+ "+ **\"Example 2\" in the reduction notebooks:** [Several images from an Andor Aspen CG16M **(483MB)**](https://zenodo.org/record/3245296/files/example-thermo-electric.tar.bz2?download=1)\n",
+ "(DOI: https://doi.org/10.5281/zenodo.3245296)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/01-00-Understanding-an-astronomical-CCD-image.ipynb b/v/pdev/_sources/notebooks/01-00-Understanding-an-astronomical-CCD-image.ipynb
new file mode 100644
index 00000000..f74fca7e
--- /dev/null
+++ b/v/pdev/_sources/notebooks/01-00-Understanding-an-astronomical-CCD-image.ipynb
@@ -0,0 +1,46 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Astronomical Images\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "An astronomical image like the one shown below is essentially a two-dimensional\n",
+ "array of values. In an ideal world, the value of each pixel (a pixel being one\n",
+ "element of the array) would be directly proportional to the amount of light that\n",
+ "fell on the pixel during the time the camera's shutter was open.\n",
+ "\n",
+ "But the ideal scenario does not in fact hold true. A solid understanding of\n",
+ "*why* pixel values are not directly proportional to light is useful before\n",
+ "diving into the details of image reduction."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/01-01-astronomical-CCD-image-components.ipynb b/v/pdev/_sources/notebooks/01-01-astronomical-CCD-image-components.ipynb
new file mode 100644
index 00000000..26b5dd3f
--- /dev/null
+++ b/v/pdev/_sources/notebooks/01-01-astronomical-CCD-image-components.ipynb
@@ -0,0 +1,135 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Understanding an astronomical CCD image"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "An astronomical image like the one shown below is essentially a two-dimensional\n",
+ "array of values. In an ideal world, the value of each pixel (a pixel being one\n",
+ "element of the array) would be directly proportional to the amount of light that\n",
+ "fell on the pixel during the time the camera's shutter was open.\n",
+ "\n",
+ "But the ideal scenario does not in fact hold true. A solid understanding of\n",
+ "*why* pixel values are not directly proportional to light is useful before\n",
+ "diving into the details of image reduction."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Counts, photons, and electrons\n",
+ "\n",
+ "The number stored in a raw astronomical image straight off a telescope is called\n",
+ "an Analog Digital Unit (ADU) or count, because internally the camera converts\n",
+ "the analog voltage in each pixel to a numerical count. The counts of interest to\n",
+ "an astronomer are the ones generated via the photoelectric effect when a photon\n",
+ "hits the detector. The number of photons (or equivalently, electrons) that reach\n",
+ "the pixel is related to the counts in the pixel by the gain.\n",
+ "\n",
+ "The gain is typically provided by the manufacturer of the camera and can be\n",
+ "measured from a combination of bias and flat images (Howell 2002; p. 71).\n",
+ "\n",
+ "**Take note** that trying to convert a raw image count to photons/electrons by\n",
+ "multiplying by the gain will not be meaningful because the raw counts include\n",
+ "contributions from sources other than light."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Not all counts are (interesting) light\n",
+ "\n",
+ "There are several contributions to the counts in a pixel. Image reduction is\n",
+ "essentially the process of removing all of these except those due to light from\n",
+ "an astronomical object:\n",
+ "\n",
+ "+ An offset voltage called **bias** is applied to the CCD chip to ensure there\n",
+ "are no negative counts during readout. There are small variations in the value\n",
+ "of the bias across the chip, and there can be small variations in the bias level\n",
+ "over time.\n",
+ "+ Counts can be generated in a pixel due to thermal motion of electrons in CCD;\n",
+ "cooling a CCD reduces, but may not fully eliminate, this **dark current**. In\n",
+ "modern CCDs the dark current is often ignorable exept for a small fraction of\n",
+ "pixels. Dark current is typically reported in electrons/second/pixel, and\n",
+ "depends strongly on temperature.\n",
+ "+ There is **read noise** intrinsic to the electronics of the CCD. It is\n",
+ "impossible to eliminate this noise (it's present in every image taken by the\n",
+ "camera) but there are approaches to minimizing it. Read noise is typically\n",
+ "reported in electrons as it can depend on temperature.\n",
+ "+ Some light received by the telescope is scattered light coming from the night\n",
+ "sky. The amount of **sky background** depends on the filter passband, the\n",
+ "atmospheric conditions, and the local light sources.\n",
+ "+ Though a CCD chip is fairly small, it's not unsual for **cosmic rays** to hit\n",
+ "the chip, releasing charge that is then converted to counts.\n",
+ "\n",
+ "Whatever remains after taking all of those things away is, in principle, light\n",
+ "from astronomical sources.\n",
+ "\n",
+ "In practice, there are additional complications."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## CCDs are not perfect\n",
+ "\n",
+ "There are a number of issues that affect the sensitivity of the CCD to light,\n",
+ "some of which can be corrected for and some of which cannot.\n",
+ "\n",
+ "+ Vignetting, a darkening of the images in the corners, is common and\n",
+ "correctable.\n",
+ "+ Dust in the optical path, which causes \"donuts\" or \"worms\" on the image, is\n",
+ "also common and correctable.\n",
+ "+ Variations in the sensitivity of individual pixels are also common and\n",
+ "correctable.\n",
+ "+ Dead pixels, which are pixels that don't respond to light, cannot be corrected\n",
+ "for.\n",
+ "\n",
+ "**Flat** corrections attempt to remove many of these effects. The idea is to\n",
+ "image something which is uniformly illuminated as a way to measure variations in\n",
+ "sensitivity (regardless of cause) and compensate for them."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## References\n",
+ "\n",
+ "Howell, S., *Handbook of CCD Astronomy*, Second Ed, Cambridge University Press\n",
+ "2006"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/01-03-Construction-of-an-artificial-but-realistic-image.ipynb b/v/pdev/_sources/notebooks/01-03-Construction-of-an-artificial-but-realistic-image.ipynb
new file mode 100644
index 00000000..3b01deb5
--- /dev/null
+++ b/v/pdev/_sources/notebooks/01-03-Construction-of-an-artificial-but-realistic-image.ipynb
@@ -0,0 +1,700 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Construction of an artificial (but realistic) image\n",
+ "\n",
+ "Before we move on to looking at a real image, let's spend a few minutes getting\n",
+ "comfortable with what each of the different sources of counts look like in an\n",
+ "artificial image. The advantage is that we can control how much of each count\n",
+ "source goes into the image. Looking at extreme examples can help build an\n",
+ "understanding of what's going on in your images."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Imports\n",
+ "\n",
+ "Almost all of the notebooks in this tutorial will start with the import of the\n",
+ "Python packages needed for that notebook. The lines below set up\n",
+ "[matplotlib](https://matplotlib.org/), a widely used plotting package."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "\n",
+ "%matplotlib inline\n",
+ "from matplotlib import pyplot as plt\n",
+ "import numpy as np\n",
+ "from photutils.aperture import EllipticalAperture"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the random number generator, allowing a seed to be set from the environment\n",
+ "seed = os.getenv('GUIDE_RANDOM_SEED', None)\n",
+ "\n",
+ "if seed is not None:\n",
+ " seed = int(seed)\n",
+ " \n",
+ "# This is the generator to use for any image component which changes in each image, e.g. read noise\n",
+ "# or Poisson error\n",
+ "noise_rng = np.random.default_rng(seed)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The Python file referenced below, `convenience_functions.py`, contains a\n",
+ "few functions for convenient display of images in a notebook.\n",
+ "\n",
+ "You can get a copy of this file a few ways. \n",
+ "\n",
+ "1. If you are running the notebooks in your browser then this link will open the \n",
+ "file in another Jupyter Lab tab: [convenience_functions.py](convenience_functions.py). You can\n",
+ "also read/edit it in your preferred editor, but it's handy to know that editing\n",
+ "Python files in the Jupyter notebook environment is possible.\n",
+ "2. If you are reading the book online you can view a copy of the file \n",
+ "here: [convenience_functions.py](https://github.com/astropy/ccd-reduction-and-photometry-guide/blob/main/notebooks/convenience_functions.py)\n",
+ "3. If you want a copy of the file without doing a copy/paste, you can \n",
+ "download or clone this repository, including the notebooks and code, \n",
+ "at [https://github.com/astropy/ccd-reduction-and-photometry-guide](https://github.com/astropy/ccd-reduction-and-photometry-guide) \n",
+ "(click on the green \"Code\" button for options) \n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Start: a blank image\n",
+ "\n",
+ "We'll begin with the simplest possible image: an array of zeros. The dimensions\n",
+ "of the image below are chosen to match some real images we'll be working with\n",
+ "later."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "synthetic_image = np.zeros([1000, 1000])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_image(synthetic_image, cmap='gray')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Add some read noise\n",
+ "\n",
+ "With each of the things we add, we'll write a small function for adding so that\n",
+ "it's easier to experiment with different values. Read noise has a Gaussian\n",
+ "distribution; the standard deviation of the Gaussian (in counts) is the read\n",
+ "noise (in electrons) divided by the gain (in electrons per count). Read noise is\n",
+ "almost always given in electrons.\n",
+ "\n",
+ "Note that each time you run this function you'll get a different set of pixels\n",
+ "so that it behaves like real noise."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def read_noise(image, amount, gain=1):\n",
+ " \"\"\"\n",
+ " Generate simulated read noise.\n",
+ " \n",
+ " Parameters\n",
+ " ----------\n",
+ " \n",
+ " image: numpy array\n",
+ " Image whose shape the noise array should match.\n",
+ " amount : float\n",
+ " Amount of read noise, in electrons.\n",
+ " gain : float, optional\n",
+ " Gain of the camera, in units of electrons/ADU.\n",
+ " \"\"\"\n",
+ " shape = image.shape\n",
+ " \n",
+ " noise = noise_rng.normal(scale=amount/gain, size=shape)\n",
+ " \n",
+ " return noise"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure()\n",
+ "noise_im = synthetic_image + read_noise(synthetic_image, 5)\n",
+ "show_image(noise_im, cmap='gray')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Bias\n",
+ "\n",
+ "Bias is an offset voltage (which translates into some non-zero number of counts)\n",
+ "added to every pixel in the image to ensure that when voltages are converted to\n",
+ "counts there is never a negative count. Note that in the noise image above, some\n",
+ "counts are positive and some are negative, as you would expect for a Gaussian\n",
+ "distribution centered on zero. Pixel values are typically read out from the\n",
+ "electronics as *positive* numbers, though. Adding a constant voltage, which\n",
+ "corresponds to a constant, positive number, ensures that even an image which\n",
+ "consists entirely of noise has no negative values.\n",
+ "\n",
+ "The bias value is roughly the same across the CCD chip, though it's not uncommon\n",
+ "to have \"bad\" columns and pixels in which the bias level is consistently offset\n",
+ "from the rest of the chip.\n",
+ "\n",
+ "To model a bias image, we create a uniform array and, optionally, add in some\n",
+ "\"bad\" columns. The bad columns are exaggerated here to ensure they are visible.\n",
+ "\n",
+ "The bad columns in a CCD are typically stable over a very long time. A random\n",
+ "number generator is used below to pick which columns in our CCD are bad, but\n",
+ "we'll use a seed to make sure that each time we generate the bias we get the\n",
+ "same bad columns (and pixel values within the bad columns).\n",
+ "\n",
+ "This stability is what makes it possible to correct for the effect in real\n",
+ "images.\n",
+ "\n",
+ "Finally, note that the bias doesn't depend on exposure time. That's because a\n",
+ "bias exposure is a zero-second exposure in which the camera simply reads the\n",
+ "chip out."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def bias(image, value, realistic=False):\n",
+ " \"\"\"\n",
+ " Generate simulated bias image.\n",
+ " \n",
+ " Parameters\n",
+ " ----------\n",
+ " \n",
+ " image: numpy array\n",
+ " Image whose shape the bias array should match.\n",
+ " value: float\n",
+ " Bias level to add.\n",
+ " realistic : bool, optional\n",
+ " If ``True``, add some columns with somewhat higher bias value (a not uncommon thing)\n",
+ " \"\"\"\n",
+ " # This is the whole thing: the bias is really suppose to be a constant offset!\n",
+ " bias_im = np.zeros_like(image) + value\n",
+ " \n",
+ " # If we want a more realistic bias we need to do a little more work. \n",
+ " if realistic:\n",
+ " shape = image.shape\n",
+ " number_of_colums = 5\n",
+ " \n",
+ " # We want a random-looking variation in the bias, but unlike the readnoise the bias should \n",
+ " # *not* change from image to image, so we make sure to always generate the same \"random\" numbers.\n",
+ " rng = np.random.RandomState(seed=8392) # 20180520\n",
+ " columns = rng.randint(0, shape[1], size=number_of_colums)\n",
+ " # This adds a little random-looking noise into the data.\n",
+ " col_pattern = rng.randint(0, int(0.1 * value), size=shape[0])\n",
+ " \n",
+ " # Make the chosen columns a little brighter than the rest...\n",
+ " for c in columns:\n",
+ " bias_im[:, c] = value + col_pattern\n",
+ " \n",
+ " return bias_im\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bias_only = bias(synthetic_image, 1100, realistic=True)\n",
+ "show_image(bias_only, cmap='gray', figsize=(10, 10))\n",
+ "plt.title('Bias alone, bad columns included', fontsize='20')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bias_noise_im = noise_im + bias_only\n",
+ "show_image(bias_noise_im, cmap='gray', figsize=(10, 10))\n",
+ "plt.title('Realistic bias frame (includes read noise)', fontsize='20')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Dark current\n",
+ "\n",
+ "Dark current depends on the temperature of the sensor. The amount of dark counts\n",
+ "in an image also depends on the exposure time. Dark current is typically very\n",
+ "small (0.1 electrons/pixel/second or less). Dark counts in this function are\n",
+ "calculated by multiplying the input dark current by the input exposure time\n",
+ "after converting the dark current unit from electrons to counts using the gain.\n",
+ "\n",
+ "A small fraction of pixels are \"hot\": their dark current is much larger than the\n",
+ "rest of the pixels. Hot pixels are modeled here by choosing a subset of the\n",
+ "pixels to have a dark current 10,000 times larger than the input dark current.\n",
+ "This exaggerates the effect to make those pixels more visible.\n",
+ "\n",
+ "The location and current of hot pixels is typically stable over long periods of\n",
+ "time, which makes it straightforward to remove their effect from science images\n",
+ "by subtracting them out.\n",
+ "\n",
+ "A dark frame (or dark image) is an image taken with the camera shutter closed.\n",
+ "\n",
+ "The function below simulates dark current only, i.e. it does *not* simulate the\n",
+ "read noise that is a part of any actual dark frame from a CCD.\n",
+ "\n",
+ "Note that the simulation dark image looks noisy even though it doesn't include\n",
+ "read noise. This is because the number of electrons generated obey a Poisson\n",
+ "distribution."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def dark_current(image, current, exposure_time, gain=1.0, hot_pixels=False):\n",
+ " \"\"\"\n",
+ " Simulate dark current in a CCD, optionally including hot pixels.\n",
+ " \n",
+ " Parameters\n",
+ " ----------\n",
+ " \n",
+ " image : numpy array\n",
+ " Image whose shape the cosmic array should match.\n",
+ " current : float\n",
+ " Dark current, in electrons/pixel/second, which is the way manufacturers typically \n",
+ " report it.\n",
+ " exposure_time : float\n",
+ " Length of the simulated exposure, in seconds.\n",
+ " gain : float, optional\n",
+ " Gain of the camera, in units of electrons/ADU.\n",
+ " strength : float, optional\n",
+ " Pixel count in the cosmic rays. \n",
+ " \"\"\"\n",
+ " \n",
+ " # dark current for every pixel; we'll modify the current for some pixels if \n",
+ " # the user wants hot pixels.\n",
+ " base_current = current * exposure_time / gain\n",
+ " \n",
+ " # This random number generation should change on each call.\n",
+ " dark_im = noise_rng.poisson(base_current, size=image.shape)\n",
+ " \n",
+ " if hot_pixels:\n",
+ " # We'll set 0.01% of the pixels to be hot; that is probably too high but should \n",
+ " # ensure they are visible.\n",
+ " y_max, x_max = dark_im.shape\n",
+ " \n",
+ " n_hot = int(0.0001 * x_max * y_max)\n",
+ " \n",
+ " # Like with the bias image, we want the hot pixels to always be in the same places\n",
+ " # (at least for the same image size) but also want them to appear to be randomly\n",
+ " # distributed. So we set a random number seed to ensure we always get the same thing.\n",
+ " rng = np.random.RandomState(16201649)\n",
+ " hot_x = rng.randint(0, x_max, size=n_hot)\n",
+ " hot_y = rng.randint(0, y_max, size=n_hot)\n",
+ " \n",
+ " hot_current = 10000 * current\n",
+ " \n",
+ " dark_im[(hot_y, hot_x)] = hot_current * exposure_time / gain\n",
+ " return dark_im"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "dark_exposure = 100\n",
+ "dark_cur = 0.1\n",
+ "dark_only = dark_current(synthetic_image, dark_cur, dark_exposure, hot_pixels=True)\n",
+ "show_image(dark_only, cmap='gray')\n",
+ "title_string = 'Dark current only, {dark_cur} $e^-$/sec/pix\\n{dark_exposure} sec exposure'.format(dark_cur=dark_cur, dark_exposure=dark_exposure)\n",
+ "plt.title(title_string, fontsize='20');"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that the central value of the image colorbar is 10, the product of the dark\n",
+ "current and the exposure time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "dark_bias_noise_im = bias_noise_im + dark_only\n",
+ "show_image(dark_bias_noise_im, cmap='gray')\n",
+ "plt.title('Realistic dark frame \\n(with bias, read noise)', fontsize='20')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Sky background\n",
+ "\n",
+ "The amount of sky background depends on the atmospheric conditions (humidity,\n",
+ "presence of light clouds, fires upwind from the observatory), the light sources\n",
+ "in the sky (the Moon), and light sources in the surrounding area (cities). It\n",
+ "may be uniform across the frame or it may not be, depending on the conditions.\n",
+ "\n",
+ "The function below generates some sky background. Each time you run it you'll\n",
+ "get slightly different results (even if you keep the desired amount of sky\n",
+ "counts the same) because the counts from a light source follow a Poisson\n",
+ "distribution.\n",
+ "\n",
+ "The amount of sky background is directly proportional to the exposure time. In\n",
+ "the function below however, you input the desired number of sky counts.\n",
+ "\n",
+ "It's not at all unusual to have a gradient in the sky across the image, but that\n",
+ "effect is not simulated here."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def sky_background(image, sky_counts, gain=1):\n",
+ " \"\"\"\n",
+ " Generate sky background, optionally including a gradient across the image (because\n",
+ " some times Moons happen).\n",
+ " \n",
+ " Parameters\n",
+ " ----------\n",
+ " \n",
+ " image : numpy array\n",
+ " Image whose shape the cosmic array should match.\n",
+ " sky_counts : float\n",
+ " The target value for the number of counts (as opposed to electrons or \n",
+ " photons) from the sky.\n",
+ " gain : float, optional\n",
+ " Gain of the camera, in units of electrons/ADU.\n",
+ " \"\"\"\n",
+ " sky_im = noise_rng.poisson(sky_counts * gain, size=image.shape) / gain\n",
+ " \n",
+ " return sky_im"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "sky_level = 20\n",
+ "sky_only = sky_background(synthetic_image, sky_level)\n",
+ "show_image(sky_only, cmap='gray')\n",
+ "plt.title('Sky background only, {} counts input'.format(sky_level), fontsize=20)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "sky_dark_bias_noise_im = dark_bias_noise_im + sky_only\n",
+ "show_image(sky_dark_bias_noise_im, cmap='gray')\n",
+ "plt.title('Sky, dark, bias and noise\\n(Realistic image of clouds)', fontsize=20);"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Summary of the backgrounds in an astronomical image"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that the central value of the pixels in the \"realistic\" cloud image above,\n",
+ "1130 or so, is the sum of the:\n",
+ "\n",
+ "+ bias level (1100 counts)\n",
+ "+ dark current (10 counts, which is 0.1 e/sec/pix $\\times$ 100 sec, divided by\n",
+ "the gain of 1 e/count)\n",
+ "+ sky counts (20 counts)\n",
+ "\n",
+ "The distribution of counts around that is determined by the read noise (5\n",
+ "electrons) and the expected width of a Poisson distribution for the sky counts,\n",
+ "which is the square root of the number of those counts, $\\sqrt{20} \\approx 4.5$.\n",
+ "Add those in quadrature and you get about 6.7."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Interactive demo\n",
+ "\n",
+ "The cell below sets up an interactive demo that lets you change the value of\n",
+ "read noise and other parameters to see the effect that changing them has on the\n",
+ "resulting image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from ipywidgets import interactive, interact\n",
+ "\n",
+ "# @interact(bias_level=(1000,1200,10), dark=(0.01,1,0.01), sky_counts=(0, 300, 10),\n",
+ "# gain=(0.5, 3.0, 0.1), read=(0, 50, 2.0),\n",
+ "# exposure=(0, 300, 10))\n",
+ "def complete_image(bias_level=1100, read=10.0, gain=1, dark=0.1, \n",
+ " exposure=30, hot_pixels=True, sky_counts=200):\n",
+ " synthetic_image = np.zeros([500, 500])\n",
+ " show_image(synthetic_image + \n",
+ " read_noise(synthetic_image, read) +\n",
+ " bias(synthetic_image, bias_level, realistic=True) + \n",
+ " dark_current(synthetic_image, dark, exposure, hot_pixels=hot_pixels) +\n",
+ " sky_background(synthetic_image, sky_counts),\n",
+ " cmap='gray',\n",
+ " figsize=None)\n",
+ " \n",
+ "i = interactive(complete_image, bias_level=(1000,1200,10), dark=(0.0,1,0.1), sky_counts=(0, 300, 50),\n",
+ " gain=(0.5, 3.0, 0.25), read=(0, 50, 5.0),\n",
+ " exposure=(0, 300, 30))\n",
+ "\n",
+ "for kid in i.children:\n",
+ " try:\n",
+ " kid.continuous_update = False\n",
+ " except KeyError:\n",
+ " pass\n",
+ "#i"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Add some \"stars\"\n",
+ "\n",
+ "The \"stars\" we'll add below are essentially just (round) Gaussian sources. The\n",
+ "function that does most of the work is from [photutils](https://photutils.readthedocs.io/en/stable/index.html),\n",
+ "which we'll return to later for doing photometry.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def stars(image, number, max_counts=10000, gain=1):\n",
+ " \"\"\"\n",
+ " Add some stars to the image.\n",
+ " \"\"\"\n",
+ " from photutils.datasets import make_random_gaussians_table, make_gaussian_sources_image\n",
+ " # Most of the code below is a direct copy/paste from\n",
+ " # https://photutils.readthedocs.io/en/stable/_modules/photutils/datasets/make.html#make_100gaussians_image\n",
+ " \n",
+ " flux_range = [max_counts/10, max_counts]\n",
+ " \n",
+ " y_max, x_max = image.shape\n",
+ " xmean_range = [0.1 * x_max, 0.9 * x_max]\n",
+ " ymean_range = [0.1 * y_max, 0.9 * y_max]\n",
+ " xstddev_range = [4, 4]\n",
+ " ystddev_range = [4, 4]\n",
+ " params = dict([('amplitude', flux_range),\n",
+ " ('x_mean', xmean_range),\n",
+ " ('y_mean', ymean_range),\n",
+ " ('x_stddev', xstddev_range),\n",
+ " ('y_stddev', ystddev_range),\n",
+ " ('theta', [0, 2*np.pi])])\n",
+ "\n",
+ " sources = make_random_gaussians_table(number, params,\n",
+ " seed=12345)\n",
+ " \n",
+ " star_im = make_gaussian_sources_image(image.shape, sources)\n",
+ " \n",
+ " return star_im"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "stars_only = stars(synthetic_image, 50, max_counts=2000)\n",
+ "show_image(stars_only, cmap='gray', percu=99.9)\n",
+ "plt.title('Stars only'.format(stars_only), fontsize=20)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "stars_with_background = sky_dark_bias_noise_im + stars_only"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_image(stars_with_background, cmap='gray', percu=99.9)\n",
+ "plt.title('Stars with noise, bias, dark, sky'.format(stars_with_background), fontsize=20)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Discussion\n",
+ "\n",
+ "In the image above the single-pixel bright dots are hot pixels while the\n",
+ "remaining dots that are larger than a pixel are simulated stars."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Summary"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Everything we have included so far has been additive. Symbolically, the\n",
+ "simulated image above was built like this:\n",
+ "\n",
+ "$$\n",
+ "\\text{image} = \\text{bias} + \\text{noise} + \\text{dark current} + \\text{sky} + \\text{stars}\n",
+ "$$\n",
+ "\n",
+ "Extracting the science (i.e. the stars) is in principle a matter of subtraction:\n",
+ "\n",
+ "$$\n",
+ "\\text{stars} = \\text{image} - \\text{bias} - \\text{noise} - \\text{dark current} - \\text{sky} \n",
+ "$$\n",
+ "\n",
+ "There are a few complications:\n",
+ "\n",
+ "1. There are multiplicative effects that will be discussed in the next notebook.\n",
+ "2. The way to measure each of the the things we need to subtract (bias and dark\n",
+ "current) is to take images, each of which includes read noise. That can be\n",
+ "minimized by combining several calibration images.\n",
+ "3. There is no way to subtract the read noise because it is random."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.5"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/01-04-Nonuniform-sensitivity.ipynb b/v/pdev/_sources/notebooks/01-04-Nonuniform-sensitivity.ipynb
new file mode 100644
index 00000000..0b80d155
--- /dev/null
+++ b/v/pdev/_sources/notebooks/01-04-Nonuniform-sensitivity.ipynb
@@ -0,0 +1,214 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Nonuniform sensitivity"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Background\n",
+ "\n",
+ "Not all pixels in a camera have the same sensitivity to light: there are\n",
+ "intrinsic differences from pixel-to-pixel. Vignetting, a dimming near the\n",
+ "corners of an image caused by the optical system to which the camera is\n",
+ "attached, and dust on optical elements such as filters, the glass window\n",
+ "covering the CCD, and the CCD chip itself can also block some light.\n",
+ "\n",
+ "Vignetting and dust can reduce the amount of light reaching the CCD chip while\n",
+ "pixel-to-pixel sensitivity variations affects the counts read from the chip.\n",
+ "\n",
+ "The code to produce the simulated sensitivity map (aka flat image) is long\n",
+ "enough that is not included in this notebook. We load it instead from\n",
+ "[image_sim.py](image_sim.py)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "from convenience_functions import show_image\n",
+ "import image_sim as isim"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## A sample flat image\n",
+ "\n",
+ "The sample flat image below has the same size as the simulated image in the\n",
+ "previous notebook."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "image = np.zeros([2000, 2000])\n",
+ "flat = isim.sensitivity_variations(image)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_image(flat, cmap='gray')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The \"donuts\" in the image are dust on elements like filters in the optical path.\n",
+ "Note that the size of the variations is small, a few percent at most."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Effect of nonuniform sensitivity on images\n",
+ "\n",
+ "Recall that an image read off a CCD, ignoring variations in sensitivity, can be\n",
+ "thought of as a combination of several pieces:\n",
+ "\n",
+ "$$\n",
+ "\\text{image} = \\text{bias} + \\text{noise} + \\text{dark current} + \\text{sky} + \\text{stars}\n",
+ "$$\n",
+ "\n",
+ "The effect of sensitivity variations is to reduce the amount of *light* reaching\n",
+ "the sensor. In the equation above, that means that the flat multiplies just the\n",
+ "sky and stars portion of the input:\n",
+ "\n",
+ "$$\n",
+ "\\text{image} = \\text{bias} + \\text{noise} + \\text{dark current} + \\text{flat} \\times (\\text{sky} + \\text{stars})\n",
+ "$$\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## A realistic image"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In the cell below we construct the last image from the previous notebook. Recall\n",
+ "that there we used a read noise of 5 electrons/pixel, dark current of 0.1\n",
+ "electron/pix/sec, bias level of 1100, and sky background of 20 counts."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gain = 1.0\n",
+ "exposure = 30.0\n",
+ "dark = 0.1\n",
+ "sky_counts = 20\n",
+ "bias_level = 1100\n",
+ "read_noise_electrons = 5\n",
+ "max_star_counts = 2000\n",
+ "bias_only = isim.bias(image, bias_level, realistic=True)\n",
+ "noise_only = isim.read_noise(image, read_noise_electrons, gain=gain)\n",
+ "dark_only = isim.dark_current(image, dark, exposure, gain=gain, hot_pixels=True)\n",
+ "sky_only = isim.sky_background(image, sky_counts, gain=gain)\n",
+ "stars_only = isim.stars(image, 50, max_counts=max_star_counts)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The individual pieces of the image are assembled below; it is the inclusion of\n",
+ "the flat that makes this the closest of the simulated images to a realistic\n",
+ "images."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "final_image = bias_only + noise_only + dark_only + flat * (sky_only + stars_only)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_image(final_image, cmap='gray', percu=99.9)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Visually, this does not look any different than the final image in the previous\n",
+ "notebook; the effects of sensitivity variations are typically not evident in raw\n",
+ "images unless the sky background is large.\n",
+ "\n",
+ "You can see the effect by artificially increasing the sky background."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "final_image2 = bias_only + noise_only + dark_only + flat * (isim.sky_background(image, 3000 * sky_counts, gain=gain) + stars_only)\n",
+ "show_image(final_image2, cmap='gray', percu=99.9)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/01-05-Calibration-overview.ipynb b/v/pdev/_sources/notebooks/01-05-Calibration-overview.ipynb
new file mode 100644
index 00000000..1de4bbd7
--- /dev/null
+++ b/v/pdev/_sources/notebooks/01-05-Calibration-overview.ipynb
@@ -0,0 +1,341 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Calibration overview\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "An image of the sky contains counts from several sources. The task of data\n",
+ "reduction (another name for image calibration) is to remove all non-celestial\n",
+ "counts from the image and to correct for non-uniform sensitivity.\n",
+ "\n",
+ "At the end of the previous notebook we arrived at an expression for the counts\n",
+ "in a science image in terms of the sources of counts:\n",
+ "\n",
+ "$$\n",
+ "\\text{raw image} = \\text{bias} + \\text{noise} + \\text{dark current} + \\text{flat} \\times (\\text{sky} + \\text{stars}).\n",
+ "$$\n",
+ "\n",
+ "Solving for the counts just from the stars is as follows:\n",
+ "\n",
+ "$$\n",
+ "\\text{stars} + \\text{noise} = \\frac{\\text{raw image} - \\text{bias} - \\text{dark current}}{\\text{flat}} - \\text{sky}\n",
+ "$$\n",
+ "\n",
+ "**It is *impossible* to remove the noise from the raw image because the noise is\n",
+ "random.**\n",
+ "\n",
+ "The dark current is typically calculated from a *dark frame* (aka dark image).\n",
+ "Such an image has bias and read noise in it as well, so:\n",
+ "\n",
+ "$$\n",
+ "\\text{dark current} + \\text{noise} = (\\text{dark frame} - \\text{bias})/(\\text{dark exposure time})\n",
+ "$$\n",
+ "\n",
+ "Once again, note that the noise cannot be removed."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## This noise cannot be removed from CCD images\n",
+ "\n",
+ "To demonstrate that you cannot remove the noise from an image, let's construct\n",
+ "an image with just stars and noise and try to subtract a noise image created\n",
+ "with the same parameters. The amount of noise here is exaggerated to make it\n",
+ "clear in the images."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "%matplotlib inline\n",
+ "from matplotlib import pyplot as plt\n",
+ "\n",
+ "from astropy.visualization import hist\n",
+ "from astropy.stats import histogram\n",
+ "\n",
+ "import image_sim as imsim\n",
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### First, some stars with noise\n",
+ "\n",
+ "The image below shows stars (the larger \"blobs\" in the image) but shows quite a\n",
+ "bit of noise as well (the much smaller \"dots\")."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "image = np.zeros([2000, 2000])\n",
+ "gain = 1.0\n",
+ "noise_amount = 1500 \n",
+ "\n",
+ "stars_with_noise = imsim.stars(image, 50, max_counts=2000, fwhm=10) + imsim.read_noise(image, noise_amount, gain=gain)\n",
+ "\n",
+ "show_image(stars_with_noise, cmap='gray', percu=99.9)\n",
+ "plt.title('Stars with noise')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Now an *incorrect* attempt at reducing noise\n",
+ "\n",
+ "Notice that the call to the noise function has exactly the same arguments as\n",
+ "above, in much the same way your camera's electronics will have the same noise\n",
+ "properties every time you read out an image.\n",
+ "\n",
+ "However, the amount of noise has **increased**, not decreased. It's much harder\n",
+ "to pick out the stars in this image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "incorrect_attempt_to_remove_noise = stars_with_noise - imsim.read_noise(image, noise_amount, gain=gain)\n",
+ "\n",
+ "show_image(incorrect_attempt_to_remove_noise, cmap='gray', percu=99.9)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Every image has noise\n",
+ "\n",
+ "Every image, including calibration images like bias and dark frames, has noise.\n",
+ "If we tried to calibrate images by taking a single bias image and a single dark\n",
+ "image, the final result might well look worse than before the image is reduced.\n",
+ "\n",
+ "For demonstration, we'll see what happens below.\n",
+ "\n",
+ "Note that here we construct *realistic* bias and dark, but leave read noise out\n",
+ "of the flat; we'll return to that point later."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### First, set parameters for the CCD\n",
+ "\n",
+ "These are the same as in the previous notebook, except for the read noise, which\n",
+ "is 700$e-$, 100 times larger than in the previous notebook."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gain = 1.0\n",
+ "star_exposure = 30.0\n",
+ "dark_exposure = 60.0\n",
+ "dark = 0.1\n",
+ "sky_counts = 20\n",
+ "bias_level = 1100\n",
+ "read_noise_electrons = 700\n",
+ "max_star_counts = 2000"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Generate the images, with noise"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bias_with_noise = (imsim.bias(image, bias_level, realistic=True) + \n",
+ " imsim.read_noise(image, read_noise_electrons, gain=gain))\n",
+ "\n",
+ "dark_frame_with_noise = (imsim.bias(image, bias_level, realistic=True) + \n",
+ " imsim.dark_current(image, dark, dark_exposure, gain=gain, hot_pixels=True) +\n",
+ " imsim.read_noise(image, read_noise_electrons, gain=gain))\n",
+ "\n",
+ "flat = imsim.sensitivity_variations(image)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "realistic_stars = (imsim.stars(image, 50, max_counts=max_star_counts) +\n",
+ " imsim.dark_current(image, dark, star_exposure, gain=gain, hot_pixels=True) +\n",
+ " imsim.bias(image, bias_level, realistic=True) +\n",
+ " imsim.read_noise(image, read_noise_electrons, gain=gain)\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Uncalibrated image\n",
+ "\n",
+ "Below we display the uncalibrated image; in a moment we'll compare it to the\n",
+ "calibrated version. Even though they don't stand out there really are stars in\n",
+ "it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(12, 12))\n",
+ "show_image(realistic_stars, cmap='gray', percu=99.9, figsize=(9, 9))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Reduce (calibrate) the star image\n",
+ "\n",
+ "First we calculate the dark current, scaled to the exposure time of our light\n",
+ "image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "scaled_dark_current = star_exposure * (dark_frame_with_noise - bias_with_noise) / dark_exposure"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, we subtract the bias and dark current from the star image and then apply\n",
+ "the flat correction."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_stars = (realistic_stars - bias_with_noise - scaled_dark_current) / flat"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_image(calibrated_stars, cmap='gray', percu=99.9)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Reducing the image cleans up the image a bit\n",
+ "\n",
+ "The stars stand more clearly than in the unreduced image.\n",
+ "\n",
+ "This image does not look *much* better than the uncalibrated image, but remember\n",
+ "that the read noise used in this simulated image, 700 $e^-$ per pixel, is\n",
+ "unrealistically high."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Reducing the image increases the noise in the image\n",
+ "\n",
+ "The histogram below shows pixel values before and after calibration. The width\n",
+ "of the distribution is a measure of the read noise. As expected, reducing the\n",
+ "image increases the read noise. One reason one takes several calibration images\n",
+ "of each type is to reduce the amount of noise in the calibration image. That\n",
+ "will, in turn, keep the noise in the final image as small as possible."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(9, 9))\n",
+ "hist(calibrated_stars.flatten(), bins='freedman', label='calibrated star image', alpha=0.5)\n",
+ "hist(stars_with_noise.flatten(), bins='freedman', label='raw star image', alpha=0.5)\n",
+ "plt.legend()\n",
+ "plt.grid()\n",
+ "plt.xlabel('Count level in image')\n",
+ "plt.ylabel('Number of pixels with that count');"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/01-06-Image-combination.ipynb b/v/pdev/_sources/notebooks/01-06-Image-combination.ipynb
new file mode 100644
index 00000000..5bcb7ed7
--- /dev/null
+++ b/v/pdev/_sources/notebooks/01-06-Image-combination.ipynb
@@ -0,0 +1,586 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Image combination\n",
+ "\n",
+ "Image combination serves several purposes. Combining images:\n",
+ "\n",
+ "+ reduces noise in images\n",
+ "+ can remove transient artifacts like cosmic rays and satellite tracks\n",
+ "+ can remove stars in flat images taken at twilight\n",
+ "\n",
+ "It's essential that several of each type of calibration image (bias, dark, flat)\n",
+ "be taken. Combining them reduces the noise in the images by roughly a factor of\n",
+ "$1/\\sqrt{N}$, where $N$ is the number of images being combined. As shown in the\n",
+ "previous notebook, using a single calibration image actually *increases* the\n",
+ "noise in your image.\n",
+ "\n",
+ "There are a few ways to combine images; if done properly, features that show up\n",
+ "in only one of the images (like cosmic rays) are not present in the combination.\n",
+ "If done incorrectly, those features show up in your combined images and then\n",
+ "contaminate your calibrated science images too.\n",
+ "\n",
+ "## The bottom line: combine by averaging images, but clip extreme values\n",
+ "\n",
+ "The remainder of this notebook demonstrates this conclusion and explains how to\n",
+ "do a combination by averaging images with [ccdproc](https://ccdproc.readthedocs.io/en/latest/)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "\n",
+ "import numpy as np\n",
+ "\n",
+ "%matplotlib inline\n",
+ "from matplotlib import pyplot as plt\n",
+ "from matplotlib import rc\n",
+ "\n",
+ "from astropy.visualization import hist\n",
+ "from astropy.stats import mad_std"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set some default parameters for the plots below\n",
+ "rc('font', size=20)\n",
+ "rc('axes', grid=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the random number generator, allowing a seed to be set from the environment\n",
+ "seed = os.getenv('GUIDE_RANDOM_SEED', None)\n",
+ "\n",
+ "if seed is not None:\n",
+ " seed = int(seed)\n",
+ " \n",
+ "# This is the generator to use for any image component which changes in each image, e.g. read noise\n",
+ "# or Poisson error\n",
+ "noise_rng = np.random.default_rng(seed)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Combination method: average or median?\n",
+ "\n",
+ "In this section we'll look at a simplified version of the challenges of\n",
+ "combining images to reduce noise. It's fair to think of astronomical images\n",
+ "(especially bias and dark images) as being a Gaussian distribution of pixel\n",
+ "values around the bias level, and a width related to the read noise of the\n",
+ "detector. To simplify what follows, we will work arrays of random numbers drawn\n",
+ "from a Gaussian distribution instead of with astronomical images.\n",
+ "\n",
+ "In properly done flat images the noise is technically a Poisson distribution,\n",
+ "but with a large enough number of counts, the distribution is indistinguishable\n",
+ "from a Gaussian distribution whose width is related to the square root of the\n",
+ "number of counts. While some regions of a science image are dominated by Poisson\n",
+ "noise from sources in the image, most of the image will be dominated by Gaussian\n",
+ "read noise from the detector or Poisson noise from the sky background.\n",
+ "\n",
+ "Instead of working with a combination of images, we'll create 100 Gaussian\n",
+ "distributions with a mean of zero, and a standard deviation of one, and combine\n",
+ "those two different ways: by finding the average and by finding the median. Each\n",
+ "distribution has size $320^2$ so that we can view it as either a distribution of\n",
+ "102,400 values or as an image that is $320 \\times 320$.\n",
+ "\n",
+ "We can think of each of these 100 distributions as representing an image, like a\n",
+ "bias or dark. To make the analogy to real images a little more direct, a \"bias\"\n",
+ "of 1000 is added to each distribution."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n_distributions = 100\n",
+ "bias_level = 1000\n",
+ "n_side = 320\n",
+ "bits = noise_rng.normal(size=(n_distributions, n_side**2)) + bias_level\n",
+ "average = np.average(bits, axis=0)\n",
+ "median = np.median(bits, axis=0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now that we've created the distributions and combined them in two different\n",
+ "ways, let's take a look at them. The [`hist` function from astropy.visualization](https://astropy.readthedocs.io/en/stable/visualization/histogram.html) is used\n",
+ "below because it can figure out what bin size to use for your data.\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, ax = plt.subplots(1, 2, sharey=True, tight_layout=True, figsize=(20, 10))\n",
+ "\n",
+ "hist(bits[0, :], bins='freedman', ax=ax[0]);\n",
+ "ax[0].set_title('One sample distribution')\n",
+ "ax[0].set_xlabel('Pixel value')\n",
+ "ax[0].set_ylabel('Number of pixels')\n",
+ "\n",
+ "hist(average, bins='freedman', label='average', alpha=0.5, ax=ax[1]);\n",
+ "hist(median, bins='freedman', label='median', alpha=0.5, ax=ax[1]);\n",
+ "ax[1].set_title('{} distributions combined'.format(n_distributions))\n",
+ "ax[1].set_xlabel('Pixel value')\n",
+ "ax[1].legend()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Combining by averaging gives a narrower (i.e. less noisy) distribution than\n",
+ "combining by median, though both substantially reduced the width of the\n",
+ "distribution. The conclusion so far is that combining by averaging is mildly\n",
+ "preferable to combining by median. Computationally, the mean is also faster to\n",
+ "compute than the median."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Image view of these distributions\n",
+ "\n",
+ "As suggested above, we could view each of these distributions as an image\n",
+ "instead of a histogram. One take away from the diagram below is that in this\n",
+ "case, the difference between mean and median is not apparent.\n",
+ "\n",
+ "In all cases, the extreme values of the image display are set to bracket the\n",
+ "width of the initial distribution."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, axes = plt.subplots(1, 3, sharey=True, tight_layout=True, figsize=(20, 10))\n",
+ "data_source = [bits[0, :], average, median]\n",
+ "titles = ['One distrbution', 'Average of {n}'.format(n=n_distributions), 'Median of {n}'.format(n=n_distributions)]\n",
+ "\n",
+ "for axis, data, title in zip(axes, data_source, titles):\n",
+ " axis.imshow(data.reshape(n_side, n_side), vmin=bias_level - 3, vmax=bias_level + 3)\n",
+ " axis.set_xticks([])\n",
+ " axis.set_yticks([])\n",
+ " axis.grid(False)\n",
+ " axis.set_title(title)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## The effect of outliers\n",
+ "\n",
+ "Suppose that, in just one of the 100 distributions we're combining, there are a\n",
+ "small number of extreme values. In astronomical images these extremes happen\n",
+ "very frequently because of cosmic ray hits on the detector that cause, in one\n",
+ "small patch of a calibration image, much higher counts. Another case occurs when\n",
+ "combining twilight flats, which often contain faint images of stars.\n",
+ "\n",
+ "In the example below, we set just 50 points out of the 102,400 in the first\n",
+ "distribution to a somewhat higher value than the rest."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bits[0, 10000:10050] = 2 * bias_level"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Remember, we can think of the values in this distribution as an image, a view\n",
+ "that will be particularly convenient in this case."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.imshow(bits[0, :].reshape(n_side, n_side), vmin=bias_level - 3, vmax=bias_level + 3)\n",
+ "plt.xticks([])\n",
+ "plt.yticks([])\n",
+ "plt.title('One distribution with outliers')\n",
+ "plt.grid(False)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now that we know what the outliers in this (and *only* this) distribution look\n",
+ "like, we'll combine all of the distributions as we did above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "average = np.average(bits, axis=0)\n",
+ "median = np.median(bits, axis=0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Even though only one out of the 100 \"images\" we're combining has these high\n",
+ "pixel values, the distribution of pixels for the average is clearly affected\n",
+ "(well, maybe not clearly, since seeing it requires a logarithmic $y$-axis). The\n",
+ "distribution for the median looks much the same as above. Since median simply\n",
+ "looks for the middle value, an extreme value doesn't affect the result too much."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(10, 10))\n",
+ "hist(average, bins='freedman', alpha=0.5, label='average');\n",
+ "hist(median, bins='freedman', alpha=0.5, label='median');\n",
+ "plt.legend()\n",
+ "plt.xlabel('Counts')\n",
+ "plt.ylabel('Number of pixels')\n",
+ "plt.semilogy();"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Combining using the average has a noticeable effect on the result; median\n",
+ "removes the artifact\n",
+ "\n",
+ "The effect of the outlier is *much* clearer if the distributions are displayed\n",
+ "as images. If the distributions we're combining were calibration images then the\n",
+ "outliers that appear in one image (e.g. a cosmic ray) would affect the combined\n",
+ "image we hoped to use for calibration."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, axes = plt.subplots(1, 3, sharey=True, tight_layout=True, figsize=(20, 10))\n",
+ "data_source = [bits[0, :], average, median]\n",
+ "titles = ['One distribution with outliers', 'Average of {n}'.format(n=n_distributions), 'Median of {n}'.format(n=n_distributions)]\n",
+ "\n",
+ "for axis, data, title in zip(axes, data_source, titles):\n",
+ " axis.imshow(data.reshape(n_side, n_side), vmin=bias_level - 3, vmax=bias_level + 3)\n",
+ " axis.set_xticks([])\n",
+ " axis.set_yticks([])\n",
+ " axis.grid(False)\n",
+ " axis.set_title(title)\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On one hand, the noise properties are better when you combine by taking the\n",
+ "average. On the other hand, the median eliminates features that appear in only\n",
+ "one image.\n",
+ "\n",
+ "Astronomical images will almost always have those transient features. Even at an\n",
+ "observatory near sea level in an exposure that is very short, cosmic ray hits\n",
+ "are common."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## The solution: average combine, but clip the extreme values\n",
+ "\n",
+ "The answer here is to first clip extreme values from the distributions and then\n",
+ "combine using the average. That rejects outlying values like the median but with\n",
+ "the modestly better statistical properties of the average. A method called\n",
+ "\"sigma clipping\" is used to remove the extreme values."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Please do not use the code below for reducing your data...**\n",
+ "\n",
+ "...in the next set of notebooks we'll walk through the package\n",
+ "[ccdproc](https://ccdproc.readthedocs.io), which automates much of what you see below.\n",
+ "The section below demonstrates and explains some of what's happening behind the\n",
+ "scenes in [ccdproc](https://ccdproc.readthedocs.io)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Sigma clipping\n",
+ "\n",
+ "Sigma clipping means calculating how \"far\" each pixel to be combined is from the\n",
+ "\"typical\" value and excluding values from the combination if they are \"too far\"\n",
+ "from the pixel value.\n",
+ "\n",
+ "To be clear, when evaluating which values to reject we're doing it for each of\n",
+ "the 102,400 points in the distribution (or, if you prefer, each of the\n",
+ "320$\\times$320 pixels in the image) we're going to combine. In other words, for\n",
+ "each point (or pixel), we'll compute a \"typical\" value for the 100 distributions\n",
+ "(images) we're combining and exclude any from the average that are \"too far\"\n",
+ "from the \"typical value.\"\n",
+ "\n",
+ "What should be used as the \"typical\" value, how do we measure how \"far\" away a\n",
+ "value is, and how far is \"too far\"?\n",
+ "\n",
+ "The last question is easiest to answer: it depends a bit on the noise level in\n",
+ "your camera but something like 5 farther from the \"typical\" value than most of\n",
+ "the pixels are.\n",
+ "\n",
+ "Using the average as the typical value and the standard deviation as a measure\n",
+ "of how far a particular value is from the typical value is often not the best\n",
+ "choice. The problem with this is that outlying values in a single distribution\n",
+ "(or image) strongly bias the average and exaggerate the standard deviation. In\n",
+ "this example, where we're combining 100 distributions (images), using the\n",
+ "average and standard deviation might work since there are so many distributions.\n",
+ "A more typical number of bias or dark images that one might combine is 10 or 20.\n",
+ "In that case, an extreme value in one image strongly affects the mean and\n",
+ "standard deviation.\n",
+ "\n",
+ "As an example, consider combining 10, 20, or 100 of our distributions, as shown\n",
+ "in the cell below. Only in the case of 100 distributions would our extreme value\n",
+ "of 2000 be excluded if we excluded values more than 5 times the standard\n",
+ "deviation from the average."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print('Number combined\\t Average\\t Standard dev σ \\t 10σ ')\n",
+ "\n",
+ "for n_to_combine in [10, 20, n_distributions]:\n",
+ " avg = np.mean(bits[:n_to_combine, 10000])\n",
+ " std = np.std(bits[:n_to_combine, 10000])\n",
+ " print('{n:10d}\\t{avg:10.2f}\\t{std:10.2f}\\t{ten_sig:10.2f}'.format(n=n_to_combine, \n",
+ " avg=avg, \n",
+ " std=std, ten_sig=10 * std))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "A better choice is to use the median as the typical value and the *median\n",
+ "absolute deviation* in place of the standard deviation as the measure of how far\n",
+ "a value is from the typical value. The [median absolute deviation](https://en.wikipedia.org/wiki/Median_absolute_deviation), or MAD,\n",
+ "of a set of points $x$ is defined by:\n",
+ "$$\n",
+ "\\text{MAD} = \\text{median}\\big( x_i - \\text{median}(x) \\big).\n",
+ "$$\n",
+ "This is a measure of the typical absolute distance from the median of the set of\n",
+ "values. The MAD is not directly equivalent to the standard deviation. The\n",
+ "relationship between the two depends on the distribution of values, but for a\n",
+ "Gaussian distribution multiplying the MAD by 1.4826 does the trick. The\n",
+ "[astropy function `mad_std`](http://docs.astropy.org/en/stable/api/astropy.stats.mad_std.html) will calculate the MAD and multiply by the\n",
+ "appropriate factor for you.\n",
+ "\n",
+ "\n",
+ "Repeating the calculation above but with median as the central value and the MAD\n",
+ "in place of the standard deviation demonstrates that even for 10 distributions\n",
+ "the extreme value will be excluded."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print('{:^20}{:^20}{:^20}{:^20}'.format('Number combined', 'Median', 'MAD σ', '10σ'))\n",
+ "\n",
+ "for n_to_combine in [10, 20, n_distributions]:\n",
+ " avg = np.median(bits[:n_to_combine, 10000])\n",
+ " std = mad_std(bits[:n_to_combine, 10000])\n",
+ " print('{n:^20d}{avg:^20.2f}{std:^20.2f}{ten_sig:^20.2f}'.format(n=n_to_combine, \n",
+ " avg=avg, \n",
+ " std=std, ten_sig=10 * std))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The downside to using the median and median absolute deviation? They can be slow\n",
+ "to compute for large images or large stacks of images."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The cells below perform the actual clipping; you should generally use the\n",
+ "astropy function [`sigma_clip`](https://astropy.readthedocs.io/en/stable/stats/robust.html) to do this, but here we'll do\n",
+ "it manually to illustrate the process.\n",
+ "\n",
+ "We begin by calculating the MAD standard deviation estimator for our data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mad_sigma = mad_std(bits, axis=0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The expression below is true for all of the points farther than $10\n",
+ "\\sigma_{MAD}$ from the median of the distributions and false everywhere else.\n",
+ "This array will be used to exclude the extreme points."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "exclude = (bits - median) / mad_sigma > 10"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, we calculate the average, excluding the points identified as \"too far\"\n",
+ "from from the median. There are two approaches we can take here. One is to use\n",
+ "numpy masked arrays; the other is to temporarily set the excluded values to the\n",
+ "special value `np.nan` and use a numpy function that excludes `nan` from the\n",
+ "calculation. The latter approach is often faster than the former.\n",
+ "\n",
+ "The best approach is really to use a higher-level function from astropy for\n",
+ "ccdproc. Those will take care of the details of implementing the clipping for\n",
+ "you."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "original_values = bits[exclude]\n",
+ "bits[exclude] = np.nan\n",
+ "\n",
+ "clip_combine = np.nanmean(bits, axis=0)\n",
+ "bits[exclude] = original_values"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Summary\n",
+ "\n",
+ "Combine images by (1) excluding extreme values using sigma clipping, with the\n",
+ "median as the typical value and the MAD estimator of the standard deviation, and\n",
+ "then (2) averaging the remaining pixels across all of the images.\n",
+ "\n",
+ "Note that in the distribution below the clipped average is a narrower\n",
+ "distribution (less noise) than the median but that it still excludes the extreme\n",
+ "value that appeared in one image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(10, 10))\n",
+ "hist(clip_combine, bins='freedman', alpha=0.5, label='clipped average')\n",
+ "hist(median, bins='freedman', alpha=0.5, label='median');\n",
+ "plt.legend()\n",
+ "plt.xlabel('Counts')\n",
+ "plt.ylabel('Number of pixels')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.5"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/01-08-Overscan.ipynb b/v/pdev/_sources/notebooks/01-08-Overscan.ipynb
new file mode 100644
index 00000000..d34b33ec
--- /dev/null
+++ b/v/pdev/_sources/notebooks/01-08-Overscan.ipynb
@@ -0,0 +1,312 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Overscan"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The overscan region of a CCD, if present, is a part of the chip that is covered.\n",
+ "Depending on the camera, it can be a useful way to remove small variations in\n",
+ "the bias level from frame to frame.\n",
+ "\n",
+ "However, whether or not the overscan is useful depends on the camera. It's\n",
+ "advisable to examine the overscan part of the camera you're using before\n",
+ "deciding if you should include it in image reduction.\n",
+ "\n",
+ "One important note: *overscan always includes bias, read noise, and dark\n",
+ "current*. The overscan pixels are still pixels, and just like any other pixel\n",
+ "includes dark current and is subject to read noise. Many sources describe\n",
+ "overscan as correcting for bias, but if the dark current for the camera is\n",
+ "negligible, as it often is for cryogenically cooled cameras, then the overscan\n",
+ "is essentially bias.\n",
+ "\n",
+ "The read noise in the overscan is reduced by averaging over the overscan region.\n",
+ "That will be covered in a later notebook; this notebook focuses on what the\n",
+ "overscan looks like and how to decide whether or not to use it.\n",
+ "\n",
+ "In this notebook we will look at the overscan region for two different cameras,\n",
+ "a cryogenically cooled camera in which the overscan provides useful information\n",
+ "and a thermo-electrically cooled camera in which the overscan does not provide\n",
+ "useful information."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "from astropy.nddata import CCDData\n",
+ "from astropy.visualization import hist\n",
+ "from ccdproc import subtract_overscan\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Case 1: Cryogenically cooled Large Format Camera (LFC) at Palomar\n",
+ "\n",
+ "The images below are from chip 0 of the LFC at the Palomar 200-inch telescope.\n",
+ "Technical information about the camera is [here](http://www.astro.caltech.edu/palomar/observer/200inchResources/lfcspecs.html). It\n",
+ "turns out that the images are not actually 2048 × 4096; as you can see below,\n",
+ "the images are 2080 × 4128. The \"extra\" in each direction is overscan.\n",
+ "\n",
+ "The FITS header for these files includes the keyword `BIASSEC`, which indicates\n",
+ "the nominal extent of the overscan region. Its value is `[2049:2080,1:4127]`,\n",
+ "which indicates the overscan extends from 2048 to 2079 (Python indexing starts\n",
+ "at 0, not 1 like in FITS) in the \"short\" direction and over the entire chip in\n",
+ "the other direction. As we'll see shortly, the useful overscan region is smaller\n",
+ "than this.\n",
+ "\n",
+ "We'll focus here on the overscan in the side that is nominally 2048 wide; in\n",
+ "Python that's the second index. The pixel count cross-sections plotted below are\n",
+ "from a bias, science, and flat image. Flat images are particularly helpful in\n",
+ "evaluating how much of the overscan region is useful because the average pixel\n",
+ "value in the exposed part of the camera is typically large."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cryo_path = Path('example-cryo-LFC')\n",
+ "bias_lfc = CCDData.read(cryo_path / 'ccd.001.0.fits', unit='count')\n",
+ "science_g_lfc = CCDData.read(cryo_path / 'ccd.037.0.fits', unit='count')\n",
+ "flat_g_lfc = CCDData.read(cryo_path / 'ccd.014.0.fits', unit='count')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bias_lfc.shape"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(20,10))\n",
+ "plt.plot(science_g_lfc.data.mean(axis=0), label='Science image')\n",
+ "plt.plot(bias_lfc.data.mean(axis=0), label='Bias image')\n",
+ "plt.plot(flat_g_lfc.data.mean(axis=0), label='Flat image')\n",
+ "plt.grid()\n",
+ "plt.axvline(x=2048, color='black', linewidth=3, linestyle='dashed', label='start of overscan')\n",
+ "plt.legend()\n",
+ "plt.ylim(1000, 2000)\n",
+ "plt.xlim(2040, 2090)\n",
+ "plt.xlabel('pixel number')\n",
+ "plt.ylabel('Counts')\n",
+ "plt.title('Overscan region, averaged over all rows')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Discussion of Example 1\n",
+ "\n",
+ "There are a few interesting things here.\n",
+ "\n",
+ "**The count value is nearly uniform in the overscan region.**\n",
+ "\n",
+ "This is good; ideally the overscan is nearly uniform since the pixels are not\n",
+ "illuminated.\n",
+ "\n",
+ "**Some light leaks from the imaging region into the overscan region.**\n",
+ "\n",
+ "This is\n",
+ "clearest in the flat image, where the counts are much higher than the value to\n",
+ "which they asymptote until at least pixel number 2055.\n",
+ "\n",
+ "Though the FITS header indicates the overscan starts at pixel 2048, the *useful*\n",
+ "part of the overscan (i.e. the part not contaminated by light) extends from\n",
+ "pixel 2055 to the end.\n",
+ "\n",
+ "**There is an offset between the science image and the other two images, and\n",
+ "perhaps between the flat and bias images.**\n",
+ "\n",
+ "This sort of variation is what overscan is intended to correct. It could be that\n",
+ "this one science image has a different overscan value (it was taken several\n",
+ "hours after the flat image) or it could be that all science images have a\n",
+ "different overscan value than other types of images.\n",
+ "\n",
+ "Either way, subtracting overscan from each of the images allows for correction\n",
+ "of these offsets.\n",
+ "\n",
+ "**Dark current *in this camera* is essentially zero so the overscan is\n",
+ "measuring bias.**\n",
+ "\n",
+ "To be clear, this isn't apparent from the graph above, but cryogenically cooled\n",
+ "cameras have negligible dark current.\n",
+ "\n",
+ "#### What happens if you don't use the overscan?\n",
+ "\n",
+ "Nothing particularly bad. In the specific case above, ignoring the overscan will\n",
+ "shift the background level in the science image by roughly 20 counts, since the\n",
+ "difference between the overscan region of the science image is lower than the\n",
+ "overscan in the other images by roughly 20 counts. If, before doing science, the\n",
+ "background of those images is subtracted, then this shift should be removed with\n",
+ "the background."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Conclusion for case 1\n",
+ "\n",
+ "The overscan is useful, but the usable overscan region extends from 2055 to the\n",
+ "end of the chip rather than from 2048 to end of the chip as the FITS header\n",
+ "claims. Put a little differently, the appropriate `BIASSEC` for these images is\n",
+ "`[2056:2080,1:4127]`. (Note that FITS starts numbering at 1 instead of 0, so\n",
+ "2055 in Python is 2056 in FITS notation.)\n",
+ "\n",
+ "If the science you are using requires knowing the counts to a precision of a\n",
+ "count or two, and modeling the background in the science image isn't an option,\n",
+ "consider using the overscan."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Case 2: Thermo-electrically cooled Apogee Aspen CG16M\n",
+ "\n",
+ "This is a low-end, research-grade CCD sold by Andor. Basic information is\n",
+ "[here](https://andor.oxinst.com/assets/uploads/documents/Andor/apogee/Apogee_Aspen_CG16M_Specifications.pdf), though you need to track down the description\n",
+ "of the sensor chip, [KAF-16803 CCD](http://www.onsemi.com/pub/Collateral/KAF-16803-D.PDF) to find out that the\n",
+ "overscan region of this 4096 × 4096 pixel camera extends from pixel 4097 to 4109\n",
+ "along one of the directions."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "therma_path = Path('example-thermo-electric')\n",
+ "kelt = CCDData.read(therma_path / 'kelt-16-b-S001-R001-C084-r.fit', unit='adu')\n",
+ "dark1000 = CCDData.read('dark-test-0002d1000.fit.bz2', unit='adu')\n",
+ "flat = CCDData.read(therma_path / 'AutoFlat-PANoRot-r-Bin1-006.fit', unit='adu')\n",
+ "master = CCDData.read('combined_bias_100_images.fit.bz2', unit='adu')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(20,10))\n",
+ "\n",
+ "plt.plot(kelt.data.mean(axis=0), label='night sky average')\n",
+ "plt.plot(master.data.mean(axis=0), label='100 bias combined')\n",
+ "plt.plot(dark1000.data.mean(axis=0), label='1000sec dark average')\n",
+ "plt.plot(flat.data.mean(axis=0), label='flat average')\n",
+ "\n",
+ "plt.grid()\n",
+ "plt.axvline(x=4096, color='black', linewidth=3, linestyle='dashed', label='start of overscan')\n",
+ "plt.legend()\n",
+ "plt.xlim(4090, 4110)\n",
+ "plt.ylim(900, 1300)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Discussion of Example 2\n",
+ "\n",
+ "The camera also has some interesting features.\n",
+ "\n",
+ "**Count values change quite a bit in the overscan region**\n",
+ "\n",
+ "This is clearest in the overscan for the flat. Not only is light leaking into\n",
+ "the overscan, the overscan appears to be mostly light leakage. One pixel may be\n",
+ "useful at best.\n",
+ "\n",
+ "**Overscan includes dark current**\n",
+ "\n",
+ "The overscan for the dark image in the figure above is roughly 10 counts higher\n",
+ "than the counts for the bias. The dark current for this camera is roughly 0.01\n",
+ "counts/pixel/second. For a 1000 second dark exposure, the expected dark counts\n",
+ "are about 10, which is the difference seen in the graph.\n",
+ "\n",
+ "**There is an offset between the bias/dark and science/flat images**\n",
+ "\n",
+ "The offset in this camera is roughly 50 counts. It's large enough that one ought\n",
+ "to be hesitant to use the overscan for this camera.\n",
+ "\n",
+ "**The overscan counts are higher than the average bias counts**\n",
+ "\n",
+ "Note that for the bias image, counts increase up to the pixel where overscan\n",
+ "starts and then level out. It turns out that overscan counts are *higher* than\n",
+ "the average of the bias counts, so subtracting the overscan would lead to a bias\n",
+ "image that is negative. This is another reason to be suspicious of the overscan\n",
+ "region on this camera."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Conclusion for case 2\n",
+ "\n",
+ "Do not use the overscan in this case. There are serious issues with light\n",
+ "leakage and large differences in the overscan counts between the bias and\n",
+ "science images."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/01-09-Calibration-choices-you-need-to-make.ipynb b/v/pdev/_sources/notebooks/01-09-Calibration-choices-you-need-to-make.ipynb
new file mode 100644
index 00000000..9c642a7f
--- /dev/null
+++ b/v/pdev/_sources/notebooks/01-09-Calibration-choices-you-need-to-make.ipynb
@@ -0,0 +1,68 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Calibration choices you need to make"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There are a few choices you need to make about how you will calibrate your data.\n",
+ "Sometimes the decision will be made for you by the data you have."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Subtract bias and dark as separate steps or in one step?\n",
+ "\n",
+ "Every dark image contains bias when it comes off of the camera, so in principle\n",
+ "you can take care of both bias and dark by constructing a master dark image that\n",
+ "leaves the bias already present in the dark images in place.\n",
+ "\n",
+ "This only works if for every image from which you need to remove dark currrent\n",
+ "you have a master dark of exactly the same exposure length.\n",
+ "\n",
+ "If not, you need to produce master dark images with the bias removed so that\n",
+ "they can be scaled by exposure time."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Subtract overscan or not?\n",
+ "\n",
+ "This is only applicable if your images have an overscan region. Subtracting\n",
+ "overscan can be useful for removing small (typically a few counts) variations\n",
+ "from image-to-image over the course of a night."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/01-11-reading-images.ipynb b/v/pdev/_sources/notebooks/01-11-reading-images.ipynb
new file mode 100644
index 00000000..43617229
--- /dev/null
+++ b/v/pdev/_sources/notebooks/01-11-reading-images.ipynb
@@ -0,0 +1,395 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Reading images\n",
+ "\n",
+ "Astropy provides a few ways to read in FITS images, some in the core package and\n",
+ "others in affiliated packages.\n",
+ "\n",
+ "Before exploring those, we'll create a set of (fake) images to work with."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "from astropy.nddata import CCDData\n",
+ "from astropy.io import fits"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Working with directories\n",
+ "\n",
+ "The cell below contains the path to the images. In this notebook we'll use it\n",
+ "both to store the fake images we generate and to read images. In normal use, you\n",
+ "wouldn't start by writing images there, however.\n",
+ "\n",
+ "If the images are in the same directory as the notebook, you can omit this or\n",
+ "set it to an empty string `''`. Having images in the same directory as the\n",
+ "notebook is less complicated, but it's not at all uncommon to need to work with\n",
+ "images in a different directory.\n",
+ "\n",
+ "Later, we'll look at how to generate the full path to an image (directory plus\n",
+ "file name) in a way that will work on any platform. One of the approaches to\n",
+ "loading images (using `ccdproc.ImageFileCollection`) lets you mostly forget\n",
+ "about this."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "data_directory = 'path/to/my/images'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Generate some fake images\n",
+ "\n",
+ "The cells below generate some fake images to use later in the notebook."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "from itertools import cycle\n",
+ "\n",
+ "import numpy as np\n",
+ "\n",
+ "image_path = Path(data_directory)\n",
+ "\n",
+ "image_path.mkdir(parents=True, exist_ok=True)\n",
+ "\n",
+ "images_to_generate = {\n",
+ " 'BIAS': 5,\n",
+ " 'DARK': 10,\n",
+ " 'FLAT': 3,\n",
+ " 'LIGHT': 10\n",
+ "}\n",
+ "\n",
+ "exposure_times = {\n",
+ " 'BIAS': [0.0],\n",
+ " 'DARK': [5.0, 30.0],\n",
+ " 'FLAT': [5.0, 6.1, 7.3],\n",
+ " 'LIGHT': [30.0],\n",
+ "}\n",
+ "\n",
+ "filters = {\n",
+ " 'FLAT': 'V',\n",
+ " 'LIGHT': 'V'\n",
+ "}\n",
+ "\n",
+ "objects = {\n",
+ " 'LIGHT': ['m82', 'xx cyg']\n",
+ "}\n",
+ "\n",
+ "image_size = [300, 200]\n",
+ "\n",
+ "image_number = 0\n",
+ "for image_type, num in images_to_generate.items():\n",
+ " exposures = cycle(exposure_times[image_type])\n",
+ " try:\n",
+ " filts = cycle(filters[image_type])\n",
+ " except KeyError:\n",
+ " filts = []\n",
+ " \n",
+ " try:\n",
+ " objs = cycle(objects[image_type])\n",
+ " except KeyError:\n",
+ " objs = []\n",
+ " for _ in range(num):\n",
+ " img = CCDData(data=np.random.randn(*image_size), unit='adu')\n",
+ " img.meta['IMAGETYP'] = image_type\n",
+ " img.meta['EXPOSURE'] = next(exposures)\n",
+ " if filts:\n",
+ " img.meta['FILTER'] = next(filts)\n",
+ " if objs:\n",
+ " img.meta['OBJECT'] = next(objs)\n",
+ " image_name = str(image_path / f'img-{image_number:04d}.fits')\n",
+ " img.write(image_name)\n",
+ " print(image_name)\n",
+ " image_number += 1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Option 1: Reading a single image with `astropy.io.fits`\n",
+ "\n",
+ "This option gives you the most flexibility but is the least adapted to CCD\n",
+ "images specifically. What you read in is a list of FITS extensions; you must\n",
+ "first select the one you want then access the data or header as desired.\n",
+ "\n",
+ "We'll open up the first of the fake images, `img-0001.fits`. To combine that\n",
+ "with the directory name we'll use Python 3's `pathlib`, which ensures that the\n",
+ "path combination will work on Windows too."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "image_name = 'img-0001.fits'\n",
+ "\n",
+ "image_path = Path(data_directory) / image_name\n",
+ "\n",
+ "hdu_list = fits.open(image_path)\n",
+ "hdu_list.info()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `hdu_list` is a list of FITS Header-Data Units. In this case there is just\n",
+ "one, containing both the image header and data, which can be accessed as shown\n",
+ "below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "hdu = hdu_list[0]\n",
+ "hdu.header"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "hdu.data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The [documentation for io.fits](https://astropy.readthedocs.io/en/stable/io/fits/index.html) describes more of its capabilities."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Option 2: Use `CCDData` to read in a single image\n",
+ "\n",
+ "Astropy contains a `CCDData` object for representing a single image. It's not as\n",
+ "flexible as using `astrop.io.fits` directly (for example, it assumes there is\n",
+ "only one FITS extension and that it contains image data) but it sets up several\n",
+ "properties that make the data easier to work with.\n",
+ "\n",
+ "We'll read in the same single image we did in the example above,\n",
+ "`img-0001.fits`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccd = CCDData.read(image_path)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The data and header are accessed similarly to how you access it in an HDU\n",
+ "returned by `astropy.io.fits`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccd.header"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccd.data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There are a [number of features of `CCDData`](https://astropy.readthedocs.io/en/stable/nddata/ccddata.html) that make it convenient for working\n",
+ "with WCS, slicing, and more. Some of those features will be discussed in more\n",
+ "detail in the notebooks that follow."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Option 3: Working with a directory of images using `ImageFileCollection`\n",
+ "\n",
+ "The affiliated package [ccdproc](https://ccdproc.readthedocs.io/) provides an easier way\n",
+ "to work with collections of images in a directory: an `ImageFileCollection`. The\n",
+ "`ImageFileCollection` is initialized with the name of the directory containing\n",
+ "the images."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from ccdproc import ImageFileCollection\n",
+ "im_collection = ImageFileCollection(data_directory)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that we didn't need to worry about using `pathlib` to combine the directory\n",
+ "and file name, instead we give the collection the name of the directory."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Summary of directory contents\n",
+ "\n",
+ "The `summary` property provides an overview of the files in the directory: it's\n",
+ "an astropy `Table`, so you can access columns in the usual way."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "im_collection.summary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Filtering and iterating over images\n",
+ "\n",
+ "The great thing about `ImageFileCollection` is that it provides convenient ways\n",
+ "to filter or loop over files via FITS header keyword values.\n",
+ "\n",
+ "For example, looping over just the flat files is one line of code:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for a_flat in im_collection.hdus(imagetyp='FLAT'):\n",
+ " print(a_flat.header['EXPOSURE'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Instead of iterating over HDUs, as in the example above, you can iterate over\n",
+ "just the headers (with `.headers`) or just the data (with `.data`). You can use\n",
+ "any FITS keyword from the header as a keyword for selecting the images you want.\n",
+ "In addition, you can return the file name while also iterating."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for a_flat, fname in im_collection.hdus(imagetyp='LIGHT', object='m82', return_fname=True):\n",
+ " print(f'In file {fname} the exposure is:', a_flat.header['EXPOSURE'], 'with standard deviation ', a_flat.data.std())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The [documentation for `ImageFileCollection`](https://ccdproc.readthedocs.io/en/latest/ccdproc/image_management.html) describes more of its capabilities.\n",
+ "`ImageFileCollection` can automatically save a copy of each image as you iterate\n",
+ "over them, for example."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for a_flat, fname in im_collection.ccds(bunit='ADU', return_fname=True):\n",
+ " print(a_flat.unit)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "a_flat.header"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/02-00-Handling-overscan-trimming-and-bias-subtraction.ipynb b/v/pdev/_sources/notebooks/02-00-Handling-overscan-trimming-and-bias-subtraction.ipynb
new file mode 100644
index 00000000..598cd064
--- /dev/null
+++ b/v/pdev/_sources/notebooks/02-00-Handling-overscan-trimming-and-bias-subtraction.ipynb
@@ -0,0 +1,50 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Handling overscan, trimming, and bias subtraction\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The bias in a CCD camera is a DC offset applied to all pixels so that when the\n",
+ "voltage in each pixel is converted to a number, the number will always be\n",
+ "positive. In an ideal CCD the bias would be the same for every pixel and not\n",
+ "change over time. In practice, the bias is slightly different for each pixel,\n",
+ "and can vary by a count or two from night to night or during a night.\n",
+ "\n",
+ "A bias *image* is a picture taken with the shutter closed and zero exposure\n",
+ "time; think about it as a command to the camera to do whatever it usually does\n",
+ "to prepare the camera's electronics to take an image and then immediately read\n",
+ "out the CCD as though you had taken a picture.\n",
+ "\n",
+ "The *overscan* is a portion of CCD chip that is not exposed to light. In some cameras this is literally several extra rows and/or columns that are not exposed to light while in others the overscan is produced electronically. It can be useful if the bias level changes over the course of a night."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/02-01-overscan-trimming-and-bias-subtraction-background.ipynb b/v/pdev/_sources/notebooks/02-01-overscan-trimming-and-bias-subtraction-background.ipynb
new file mode 100644
index 00000000..7f42b417
--- /dev/null
+++ b/v/pdev/_sources/notebooks/02-01-overscan-trimming-and-bias-subtraction-background.ipynb
@@ -0,0 +1,190 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# About bias and overscan "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Sample bias images\n",
+ "\n",
+ "The images below are a single bias frame and an average 100 bias frames from an\n",
+ "[Andor Apogee Aspen CG16M](http://www.andor.com/pdfs/specifications/Apogee_Aspen_CG16M_Specifications.pdf), a low-end 4k × 4k CCD with a\n",
+ "[Kodak KAF-16803 sensor chip](http://www.onsemi.com/pub/Collateral/KAF-16803-D.PDF). That model camera has a typical bias level\n",
+ "around 1000 and read noise around 10 $e^-$, though the precise value varies from\n",
+ "camera to camera and with temperature."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%load_ext autoreload\n",
+ "%autoreload 2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%matplotlib inline\n",
+ "import matplotlib.pyplot as plt"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from astropy.nddata import CCDData\n",
+ "from astropy.visualization import hist\n",
+ "\n",
+ "import numpy as np\n",
+ "\n",
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "one_bias = CCDData.read('single_bias_thermoelectric.fit.bz2', unit='adu')\n",
+ "one_hundred_bias = CCDData.read('combined_bias_100_images.fit.bz2', unit='adu')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, (ax_1_bias, ax_avg_bias) = plt.subplots(1, 2, figsize=(30, 15))\n",
+ "\n",
+ "show_image(one_bias.data, cmap='gray', ax=ax_1_bias, fig=fig, input_ratio=8)\n",
+ "ax_1_bias.set_title('Single bias image')\n",
+ "show_image(one_hundred_bias.data, cmap='gray', ax=ax_avg_bias, fig=fig, input_ratio=8)\n",
+ "ax_avg_bias.set_title('100 bias images combined');"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Note a few things\n",
+ "\n",
+ "+ The bias level in this specific camera is about 1023 (the mid-range of the\n",
+ "colorbar).\n",
+ "+ The image is brighter on the left and right edges. This \"amplifier glow\" is\n",
+ "frequently present and caused by the CCD electronics (photosensors with an\n",
+ "applied voltage are LEDs).\n",
+ "+ There are several vertical lines; these are columns for which the bias level\n",
+ "is consistently higher.\n",
+ "+ There is noticeable \"static\" in the images; that is read noise.\n",
+ "+ None of the variations are particularly large.\n",
+ "+ Combining several bias images vastly reduces the read noise. This example is a\n",
+ "little unrealistic in that 100 bias images were combined, but it still illustrates the\n",
+ "idea that combining images reduces noise."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Impact of combining images on noise\n",
+ "\n",
+ "As discussed at length in the [notebook on combination](01-06-Image-combination.ipynb), the reason for\n",
+ "taking and combining several calibration images is to reduce the noise if the\n",
+ "images are used for calibration. The difference between a single image and a\n",
+ "combination of images is apparent in the images above. Another way to see the\n",
+ "impact of combining images is in the histogram of pixel values. Notice that the\n",
+ "distribution of values is much narrower for the combined image than for a single\n",
+ "bias. Pixels near the edges, where the amplifier glow is large, are binned\n",
+ "separately from the rest of the pixels to emphasize the uniformity of the chip\n",
+ "away from the glow."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(20, 10))\n",
+ "hist(one_bias.data[:, 15:-30].flatten(), bins=800, alpha=0.4, label='One bias', color='deepskyblue')\n",
+ "hist(np.concatenate((one_bias.data[:, :15].flatten(), one_bias.data[:, -30:].flatten())), bins=400, alpha=0.2, label='One bias (edges only)', color='lightskyblue')\n",
+ "#hist(, bins=800, alpha=0.2, label='One bias (edges only)', color='darkblue')\n",
+ "hist(one_hundred_bias.data[:, 15:-30].flatten(), bins=800, alpha=0.4, label='One hundred bias images', color='darkgreen')\n",
+ "hist(np.concatenate((one_hundred_bias.data[:, :15].flatten(), one_hundred_bias.data[:, -30:].flatten())), bins=800, alpha=0.4, label='One hundred bias images (edges only)', color='lightgreen')\n",
+ "\n",
+ "#hist(one_hundred_bias.data[:, :15].flatten(), bins=800, alpha=0.4, label='One hundred bias images', color='darkgreen')\n",
+ "\n",
+ "plt.grid()\n",
+ "plt.xlim(975, 1400)\n",
+ "plt.legend()\n",
+ "plt.xlabel('Pixel value')\n",
+ "plt.ylabel('Number of pixels')\n",
+ "plt.semilogy();"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Bias calibration overview"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The progression here is to \"calibrate\" the bias images by subtracting overscan,\n",
+ "if desired, trim the overscan from the bias images if it is present, and combine\n",
+ "all of the bias images to make a \"combined\" bias (another common term for these\n",
+ "images is \"master\" bias and occasionally \"super\" bias)."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/02-02-Calibrating-bias-images.ipynb b/v/pdev/_sources/notebooks/02-02-Calibrating-bias-images.ipynb
new file mode 100644
index 00000000..4eee1edf
--- /dev/null
+++ b/v/pdev/_sources/notebooks/02-02-Calibrating-bias-images.ipynb
@@ -0,0 +1,659 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Calibrating bias images"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The purpose of calibrating bias images is threefold:\n",
+ "\n",
+ "+ Subtract overscan if you have decided your science will be better if you\n",
+ "subtract overscan. See [this discussion of overscan](01-08-Overscan.ipynb) for some guidance.\n",
+ "+ Trim the overscan region off of the image if it is present, regardless of\n",
+ "whether or not you have chosen to subtract the overscan.\n",
+ "+ Combine the bias images into a \"combined\" bias to be used in calibrating the\n",
+ "rest of the images. The purpose of combining several images is to reduce as much\n",
+ "as possible the read noise in the combined bias.\n",
+ "\n",
+ "The approach in this notebook will be to reduce a single image, look at the\n",
+ "effects the reduction step had on that image and then demonstrate how to\n",
+ "calibrate a folder containing several images of that type."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "import os\n",
+ "\n",
+ "from astropy.nddata import CCDData\n",
+ "from astropy.visualization import hist\n",
+ "import ccdproc as ccdp\n",
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "\n",
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Data for these examples"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "See the [Preface notebook](00-00-Preface.ipynb) for download links for all data."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 1: With overscan subtraction"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Decide where to put your Example 1 calibrated images\n",
+ "Though it is possible to overwrite your raw data with calibrated images, this is not recommended. Here we create a folder called `example1-reduced` that will contain\n",
+ "the calibrated data and create it if it doesn't exist."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_data = Path('.', 'example1-reduced')\n",
+ "calibrated_data.mkdir(exist_ok=True)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Make an image file collection for the raw data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "example_cryo_path = Path('example-cryo-LFC')\n",
+ "files = ccdp.ImageFileCollection(example_cryo_path)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "files.summary['file', 'imagetyp', 'filter', 'exptime', 'naxis1', 'naxis2']"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "darks_only = ccdp.ImageFileCollection(example_cryo_path / 'darks')\n",
+ "darks_only.summary['file', 'imagetyp', 'exptime']"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Determine overscan region for the LFC Chip 0"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Please see the discussion of this camera in\n",
+ "[the Overscan notebook](01-08-Overscan.ipynb#case-1-cryogenically-cooled-large-format-camera-lfc-at-palomar) for the appropriate overscan region\n",
+ "to use for this camera. Note, in particular, that it differs from the value\n",
+ "given in the `BIASSEC` keyword in the header of the images."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The astropy affiliated package [ccdproc](https://ccdproc.readthedocs.io) provides two\n",
+ "useful functions here:\n",
+ "\n",
+ "+ `subtract_overscan` for subtracting the overscan from the image, and\n",
+ "+ `trim_image` for trimming off the overscan.\n",
+ "\n",
+ "First, let's see the values of `BIASSEC`, which sometimes (but do not always)\n",
+ "indicate that there is is overscan and which part of the chip is the overscan,\n",
+ "as well as the values of `CCDSEC`, which is sometimes but not always present, and indicates which\n",
+ "part of the chip light hit.\n",
+ "\n",
+ "Note that neither of these are standard; sometimes, for example, `trimsec` is\n",
+ "used instead of `ccdsec`, and there are likely other variants. Some images may\n",
+ "have neither keyword in the header. This does not necessarily indicate that\n",
+ "ovserscan isn't present. The best advice is to carefully check the documentation\n",
+ "for the camera you are using."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "files.summary['file', 'imagetyp', 'biassec', 'ccdsec', 'datasec'][0]"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The fits header claims the overscan extends from the 2049$^{th}$ column to the\n",
+ "end of the image (this is one-based indexing) and that the part of the image\n",
+ "exposed to light extends over all rows and from the first column to the\n",
+ "2048$^{th}$ column (again, this is one-indexed)."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### FITS vs. Python indexing\n",
+ "\n",
+ "There are two differences between FITS and Python in terms of indexing:\n",
+ "\n",
+ "+ Python indexes are zero-based (i.e., numbering starts at zero), while FITS indexes\n",
+ "are one-based (i.e., numbering starts at one).\n",
+ "+ The *order* of the indexes is swapped.\n",
+ "\n",
+ "For example, the **FITS** representation of the part of the chip exposed to\n",
+ "light is `[1:2048,1:4128]`. To access that part of the data from a NumPy array\n",
+ "in **Python**, switch the order so that the indexing looks like this: `[0:4128,\n",
+ "0:2048]` (or, more compactly `[:, :2048]`). Note that the *ending* indexes given\n",
+ "here for Python are correct because the second part of a range (after the colon)\n",
+ "is *not included* in the array slice. For example, `0:2048` starts at 0 (the\n",
+ "first pixel) and goes up to but does not include 2048, so the last pixel included\n",
+ "is `2047` (the 2048$^{th}$ pixel)."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As discussed in [the Overscan notebook](01-08-Overscan.ipynb#case-1-cryogenically-cooled-large-format-camera-lfc-at-palomar), the useful\n",
+ "overscan region for this camera starts at the 2055$^{th}$ column, not column\n",
+ "2049 as indicated by the `BIASSEC` keyword in the header. This situation is not\n",
+ "unusual; column 2049 is the first of the columns masked from light by the manufacturer, \n",
+ "but there is some leakage into this region from the rest of the CCD.\n",
+ "\n",
+ "If you are going to use overscan you need to carefully examine the overscan in a few\n",
+ "representative images to understand which part of the overscan to use.\n",
+ "\n",
+ "In what follows, we will use for the overscan the region (Python/NumPy indexing)\n",
+ "`[:, 2055:]`."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Subtract and then trim the overscan (one sample image)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Using `subtract_overscan` is reasonably concise, as shown in the cell\n",
+ "below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "raw_biases = files.files_filtered(include_path=True, imagetyp='BIAS')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "first_bias = CCDData.read(raw_biases[0], unit='adu')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bias_overscan_subtracted = ccdp.subtract_overscan(first_bias, overscan=first_bias[:, 2055:], median=True)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, we trim off the full overscan region (not just the part we used for\n",
+ "subtracting overscan)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "trimmed_bias = ccdp.trim_image(bias_overscan_subtracted[:, :2048])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))\n",
+ "\n",
+ "show_image(first_bias.data, cmap='gray', ax=ax1, fig=fig)\n",
+ "ax1.set_title('Raw bias')\n",
+ "show_image(trimmed_bias.data, cmap='gray', ax=ax2, fig=fig)\n",
+ "ax2.set_title('Bias, overscan subtracted and trimmed')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Discussion\n",
+ "\n",
+ "Visually, the images look nearly identical before and after calibration. The\n",
+ "only prominent difference is a shift in the pixel values, as one would expect\n",
+ "from subtracting the same value from each pixel in an image. It simply shifts\n",
+ "the zero point.\n",
+ "\n",
+ "There is one other important difference between the images: the input image\n",
+ "uses 32MB of memory while the calibrated, overscan-subtracted image uses roughly\n",
+ "128MB. The input image is stored as unsigned 16-bit integers; the calibrated\n",
+ "image is stored as floating point numbers, which default in Python to 64-bit\n",
+ "floats. The memory size is also the size the files will have when written to\n",
+ "disk (ignoring any compression). You can reduce the memory and disk footprint by\n",
+ "changing the `dtype` of the image: `trimmed_bias.data = trimmed_bias.data.astype('float32')`. It is best\n",
+ "to do this just before writing the image out because arithmetic operations on\n",
+ "the image may convert its `dtype` back to `float64`.\n",
+ "\n"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Processing the folder of bias images for the LFC Chip 0"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Processing each of the bias images individually would be tedious, at best.\n",
+ "Instead, we can use the [`ImageFileCollection`](https://ccdproc.readthedocs.io/en/latest/ccdproc/image_management.html) we created above to\n",
+ "loop over only the bias images, saving each in the folder `calibrated_data`. In\n",
+ "this example, the files are saved uncompressed because the Python library for\n",
+ "compressing gzip files is extremely slow."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for ccd, file_name in files.ccds(imagetyp='BIAS', # Just get the bias frames\n",
+ " ccd_kwargs={'unit': 'adu'}, # CCDData requires a unit for the image if \n",
+ " # it is not in the header\n",
+ " return_fname=True # Provide the file name too.\n",
+ " ):\n",
+ " # Subtract the overscan\n",
+ " ccd = ccdp.subtract_overscan(ccd, overscan=ccd[:, 2055:], median=True)\n",
+ " \n",
+ " # Trim the overscan\n",
+ " ccd = ccdp.trim_image(ccd[:, :2048])\n",
+ " \n",
+ " # Save the result\n",
+ " ccd.write(calibrated_data / file_name)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's check that we really did get the images we expect by creating an\n",
+ "[`ImageFileCollection`](https://ccdproc.readthedocs.io/en/latest/ccdproc/image_management.html) for the reduced folder and displaying the size\n",
+ "of each image. We are expecting the images to be 2048 × 4128, and that there\n",
+ "will be the same number of reduced bias images as input bias images (six)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "reduced_images = ccdp.ImageFileCollection(calibrated_data)\n",
+ "reduced_images.summary['file', 'imagetyp', 'naxis1', 'naxis2']"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 2: No overscan subtraction, but trim the images"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "If you are not subtracting overscan then the only manipulation you may need to\n",
+ "do is trimming the overscan from the images. If there is no overscan region in\n",
+ "your images then even that is unnecessary."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Decide where to put your calibrated Example 2 images"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Though it is possible to overwrite your raw data with calibrated images, this is not recommended. Here we create a folder called `example2-reduced` that will contain\n",
+ "the calibrated data and create it if it doesn't exist."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_data = Path('.', 'example2-reduced')\n",
+ "calibrated_data.mkdir(exist_ok=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "files = ccdp.ImageFileCollection('example-thermo-electric')\n",
+ "files.summary['file', 'imagetyp', 'filter', 'exptime', 'naxis1', 'naxis2']"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Determine overscan region for this camera"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Please see the discussion of this camera in [the Overscan notebook](01-08-Overscan.ipynb#case-2-thermo-electrically-cooled-apogee-aspen-cg16m) for\n",
+ "a discussion of the overscan region of this camera. The overscan for this camera\n",
+ "is not useful but should be trimmed out at this stage.\n",
+ "\n",
+ "These headers have some information in the keywords `BIASSEC` and `TRIMSEC`\n",
+ "indicating, in the FITS numbering convention, the overscan region and the\n",
+ "science region of the chip."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "files.summary['file', 'imagetyp', 'biassec', 'trimsec'][0]"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Based on this, and the decision not to subtract overscan for this camera, we\n",
+ "will only need to trim the overscan region off of the images. See the discussion\n",
+ "at [FITS vs. Python indexing](#fits-vs-python-indexing), above, for some details about the\n",
+ "difference between FITS and Python indexing. Essentially, to get Python indexes\n",
+ "from FITS, reverse the order and subtract one."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Trim the overscan (one sample image)\n",
+ "\n",
+ "The function `trim_image` from [ccdproc](https://ccdproc.readthedocs.io) removes a\n",
+ "portion of the image and updates the image metadata as needed.\n",
+ "\n",
+ "Below we get the first bias image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "raw_biases = files.files_filtered(include_path=True, imagetyp='BIAS')\n",
+ "\n",
+ "first_bias = CCDData.read(raw_biases[0], unit='adu')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There are two ways of specifying the region to trim. One is to slice the image in\n",
+ "Python; the other is to use the `fits_section` argument to `trim_image`.\n",
+ "\n",
+ "The cell below uses a FITS-style section."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "trimmed_bias_fits = ccdp.trim_image(first_bias, fits_section='[1:4096, :]')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The cell below does the same trimming as the one above, but with Python-style\n",
+ "slicing."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "trimmed_bias_python = ccdp.trim_image(first_bias[:, :4096])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "np.testing.assert_allclose(trimmed_bias_python, trimmed_bias_fits)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Processing the folder of bias images for Example 2"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As in [Example 1](#example-1-with-overscan-subtraction), above, we can use the\n",
+ "[`ImageFileCollection`](https://ccdproc.readthedocs.io/en/latest/ccdproc/image_management.html) we created to loop over only the bias images,\n",
+ "saving each in the folder `calibrated_data`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for ccd, file_name in files.ccds(imagetyp='BIAS', # Just get the bias frames\n",
+ " return_fname=True # Provide the file name too.\n",
+ " ): \n",
+ " # Trim the overscan\n",
+ " ccd = ccdp.trim_image(ccd[:, :4096])\n",
+ " \n",
+ " # Save the result\n",
+ " ccd.write(calibrated_data / file_name)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 3: No overscan at all\n",
+ "\n",
+ "If there is no overscan then there is, in principle, nothing to be done with the\n",
+ "bias frames. It may be convenient to copy them to the directory with the rest of\n",
+ "your reduced images. The code below does that."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_data = Path('.', 'example3-reduced')\n",
+ "calibrated_data.mkdir(exist_ok=True)\n",
+ "\n",
+ "biases = files.files_filtered(imagetyp='BIAS', include_path=True)\n",
+ "\n",
+ "import shutil\n",
+ "\n",
+ "for bias in biases:\n",
+ " shutil.copy(bias, calibrated_data)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/02-04-Combine-bias-images-to-make-master.ipynb b/v/pdev/_sources/notebooks/02-04-Combine-bias-images-to-make-master.ipynb
new file mode 100644
index 00000000..5cc8f83e
--- /dev/null
+++ b/v/pdev/_sources/notebooks/02-04-Combine-bias-images-to-make-master.ipynb
@@ -0,0 +1,261 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Combine bias images to make master\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The final step is to combine the individual calibrated bias images into a single\n",
+ "combined image. That combined image will have less noise than the individual\n",
+ "images, minimizing the noise added to the remaining images when the bias is\n",
+ "subtracted.\n",
+ "\n",
+ "Regardless of which path you took through the calibration of the biases (with\n",
+ "overscan or without), there should be a folder named `reduced` that contains the\n",
+ "calibrated bias images. If there is not, please run the previous notebook before\n",
+ "continuing with this one."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "import os\n",
+ "\n",
+ "from astropy.nddata import CCDData\n",
+ "from astropy.stats import mad_std\n",
+ "\n",
+ "import ccdproc as ccdp\n",
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "\n",
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Recommended settings for image combination\n",
+ "\n",
+ "As discussed in the [notebook about combining images](01-06-Image-combination.ipynb), the recommendation is\n",
+ "that you combine by averaging the individual images but sigma clip to remove\n",
+ "extreme values.\n",
+ "\n",
+ "[ccdproc](https://ccdproc.readthedocs.org) provides two ways to combine:\n",
+ "\n",
+ "+ An object-oriented interface built around the `Combiner` object, described in\n",
+ "the [ccdproc documentation on image combination](https://ccdproc.readthedocs.io/en/latest/image_combination.html).\n",
+ "+ A function called [`combine`](https://ccdproc.readthedocs.io/en/latest/api/ccdproc.combine.html#ccdproc.combine), which we will use here because the function\n",
+ "allows you to specify the maximum amount of memory that should be used during\n",
+ "combination. This feature can be essential depending on how many images you need\n",
+ "to combine, how big they are, and how much memory your computer has.\n",
+ "\n",
+ "*NOTE: If using a version of ccdproc lower than 2.0, set the memory limit a\n",
+ "factor of 2-3 lower than you want the maximum memory consumption to be.*"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 1: Cryogenically-cooled camera"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The remainder of this section assumes the calibrated bias images are in the\n",
+ "folder `example1-reduced` which is created in the previous notebook."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_path = Path('example1-reduced')\n",
+ "reduced_images = ccdp.ImageFileCollection(calibrated_path)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The code below:\n",
+ "\n",
+ "+ selects the calibrated bias images,\n",
+ "+ combines them using the `combine` function,\n",
+ "+ adds the keyword `COMBINED` to the header so that later calibration steps can\n",
+ "easily identify which bias to use, and\n",
+ "+ writes the file."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_biases = reduced_images.files_filtered(imagetyp='bias', include_path=True)\n",
+ "\n",
+ "combined_bias = ccdp.combine(calibrated_biases,\n",
+ " method='average',\n",
+ " sigma_clip=True, sigma_clip_low_thresh=5, sigma_clip_high_thresh=5,\n",
+ " sigma_clip_func=np.ma.median, sigma_clip_dev_func=mad_std,\n",
+ " mem_limit=350e6\n",
+ " )\n",
+ "\n",
+ "combined_bias.meta['combined'] = True\n",
+ "\n",
+ "combined_bias.write(calibrated_path / 'combined_bias.fit')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Result for Example 1\n",
+ "\n",
+ "A single calibrated image and the combined image are shown below. There is\n",
+ "significant two-dimensional structure in the bias that cannot easily be removed\n",
+ "by subtracting only the overscan in the next image reduction steps. It takes\n",
+ "little time to acquire bias images and doing so will result in higher quality\n",
+ "science images."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))\n",
+ "\n",
+ "show_image(CCDData.read(calibrated_biases[0]).data, cmap='gray', ax=ax1, fig=fig, percl=90)\n",
+ "ax1.set_title('Single calibrated bias')\n",
+ "show_image(combined_bias.data, cmap='gray', ax=ax2, fig=fig, percl=90)\n",
+ "ax2.set_title('{} bias images combined'.format(len(calibrated_biases)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 2: Thermo-electrically cooled camera\n",
+ "\n",
+ "The process for combining the images is exactly the same as in example 1. The\n",
+ "only difference is the directory that contains the calibrated bias frames."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_path = Path('example2-reduced')\n",
+ "reduced_images = ccdp.ImageFileCollection(calibrated_path)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The code below:\n",
+ "\n",
+ "+ selects the calibrated bias images,\n",
+ "+ combines them using the `combine` function,\n",
+ "+ adds the keyword `COMBINED` to the header so that later calibration steps can\n",
+ "easily identify which bias to use, and\n",
+ "+ writes the file."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_biases = reduced_images.files_filtered(imagetyp='bias', include_path=True)\n",
+ "\n",
+ "combined_bias = ccdp.combine(calibrated_biases,\n",
+ " method='average',\n",
+ " sigma_clip=True, sigma_clip_low_thresh=5, sigma_clip_high_thresh=5,\n",
+ " sigma_clip_func=np.ma.median, signma_clip_dev_func=mad_std,\n",
+ " mem_limit=350e6\n",
+ " )\n",
+ "\n",
+ "combined_bias.meta['combined'] = True\n",
+ "\n",
+ "combined_bias.write(calibrated_path / 'combined_bias.fit')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Result for Example 2\n",
+ "\n",
+ "The difference between a single calibrated bias image and the combined bias\n",
+ "image is much clearer in this case."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))\n",
+ "\n",
+ "show_image(CCDData.read(calibrated_biases[0]).data, cmap='gray', ax=ax1, fig=fig)\n",
+ "ax1.set_title('Single calibrated bias')\n",
+ "show_image(combined_bias.data, cmap='gray', ax=ax2, fig=fig)\n",
+ "ax2.set_title('{} bias images combined'.format(len(calibrated_biases)))"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/03-00-Dark-current-and-hot-pixels.ipynb b/v/pdev/_sources/notebooks/03-00-Dark-current-and-hot-pixels.ipynb
new file mode 100644
index 00000000..20bf5dbe
--- /dev/null
+++ b/v/pdev/_sources/notebooks/03-00-Dark-current-and-hot-pixels.ipynb
@@ -0,0 +1,52 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Dark current and hot pixels"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Every image from a CCD contains *dark current*, which are counts in a raw image\n",
+ "caused by thermal effects in the CCD.\n",
+ "The dark current in modern CCDs is extremely small if the camera is cooled in\n",
+ "some way. Cameras cooled with liquid nitrogen have nearly zero dark current\n",
+ "while thermoelectrically-cooled CCDs have a somewhat larger dark current. The\n",
+ "dark current in a CCD operating at room temperature will typically be very\n",
+ "large.\n",
+ "\n",
+ "Even a camera in which the dark current is *typically* very small will have a\n",
+ "small fraction of pixels, called hot pixels, in which the dark current is much\n",
+ "higher.\n",
+ "\n",
+ "The next notebook walks through how to identify those pixels and how to decide\n",
+ "the right way to remove dark current from your data."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/03-01-Dark-current-The-ideal-case.ipynb b/v/pdev/_sources/notebooks/03-01-Dark-current-The-ideal-case.ipynb
new file mode 100644
index 00000000..4758746b
--- /dev/null
+++ b/v/pdev/_sources/notebooks/03-01-Dark-current-The-ideal-case.ipynb
@@ -0,0 +1,583 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Dark current: the ideal case"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "from scipy import stats\n",
+ "\n",
+ "%matplotlib inline\n",
+ "from matplotlib import pyplot as plt\n",
+ "\n",
+ "from image_sim import dark_current, read_noise\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## A dark frame measures dark current\n",
+ "\n",
+ "Recall that *dark current* refers to counts (electrons) generated in a pixel\n",
+ "because an electron in the pixel happens to have enough energy to \"break free\"\n",
+ "and register as a count. The distribution of electron thermal energies in a pixel\n",
+ "follows a [Maxwell-Boltzmann distribution](https://en.wikipedia.org/wiki/Maxwell%E2%80%93Boltzmann_distribution) in which most electrons have energy\n",
+ "around $kT$, where $T$ is the temperature of the sensor and $k$ is the Boltzmann\n",
+ "constant. There is a distribution of energies, though, and occasionally an\n",
+ "electron will be high energy enough to jump to the conducting band in the chip,\n",
+ "registering the same as an electron excited by a photon. Since the\n",
+ "Maxwell-Boltzmann distribution depends on temperature, the rate at which dark\n",
+ "current appears in a pixel is also expected to depend on temperature.\n",
+ "\n",
+ "A *dark frame* (also called a *dark image*) is an image taken with your camera\n",
+ "with the shutter closed. It is the sum of the bias level of your camera, the\n",
+ "readout noise, and the dark current.\n",
+ "\n",
+ "You measure the dark current in your camera by taking dark frames."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Convenience functions\n",
+ "\n",
+ "A couple of functions that will be reused a few times in this notebook are\n",
+ "defined below."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**A function to generate dark frames**\n",
+ "\n",
+ "This function generates a set of dark frames. It will be used several times\n",
+ "below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def generate_dark_frames(n_frames, \n",
+ " dark_rate=0, \n",
+ " noise=0,\n",
+ " gain=1,\n",
+ " exposure=10,\n",
+ " image_size=500):\n",
+ " \n",
+ " base_image = np.zeros([image_size, image_size])\n",
+ " darks = np.zeros([n_frames, image_size, image_size])\n",
+ " \n",
+ " for n in range(n_images):\n",
+ " darks[n] = dark_current(base_image, dark_rate, exposure, gain=gain, hot_pixels=False)\n",
+ " if noise > 0:\n",
+ " darks[n] = darks[n] + read_noise(base_image, noise, gain=gain)\n",
+ "\n",
+ " return darks"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**A function to plot the distribution of dark counts**\n",
+ "\n",
+ "The function below plots the:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def plot_dark_with_distributions(image, rn, dark_rate, \n",
+ " n_images=1,\n",
+ " exposure=1,\n",
+ " gain=1,\n",
+ " show_poisson=True, \n",
+ " show_gaussian=True):\n",
+ " \"\"\"\n",
+ " Plot the distribution of dark pixel values, optionally overplotting the expected Poisson and\n",
+ " normal distributions corresponding to dark current only or read noise only.\n",
+ " \n",
+ " Parameters\n",
+ " ----------\n",
+ " \n",
+ " image : numpy array\n",
+ " Dark frame to histogram.\n",
+ " \n",
+ " rn : float\n",
+ " The read noise, in electrons.\n",
+ " \n",
+ " dark_rate : float\n",
+ " The dark current in electrons/sec/pixel.\n",
+ " \n",
+ " n_images : float, optional\n",
+ " If the image is formed from the average of some number of dark frames then \n",
+ " the resulting Poisson distribution depends on the number of images, as does the \n",
+ " expected standard deviation of the Gaussian.\n",
+ " \n",
+ " exposure : float\n",
+ " Exposure time, in seconds.\n",
+ " \n",
+ " gain : float, optional\n",
+ " Gain of the camera, in electron/ADU.\n",
+ " \n",
+ " show_poisson : bool, optional\n",
+ " If ``True``, overplot a Poisson distribution with mean equal to the expected dark\n",
+ " counts for the number of images.\n",
+ " \n",
+ " show_gaussian : bool, optional\n",
+ " If ``True``, overplot a normal distribution with mean equal to the expected dark\n",
+ " counts and standard deviation equal to the read noise, scaled as appropiate for \n",
+ " the number of images.\n",
+ " \"\"\"\n",
+ " \n",
+ " h = plt.hist(image.flatten(), bins=20, align='mid', \n",
+ " density=True, label=\"Dark frame\");\n",
+ "\n",
+ " bins = h[1]\n",
+ " \n",
+ " expected_mean_dark = dark_rate * exposure / gain\n",
+ " \n",
+ " pois = stats.poisson(expected_mean_dark * n_images)\n",
+ "\n",
+ " pois_x = np.arange(0, 300, 1)\n",
+ "\n",
+ " new_area = np.sum(1/n_images * pois.pmf(pois_x))\n",
+ "\n",
+ " if show_poisson:\n",
+ " plt.plot(pois_x / n_images, pois.pmf(pois_x) / new_area, \n",
+ " label=\"Poisson dsitribution, mean of {:5.2f} counts\".format(expected_mean_dark)) \n",
+ "\n",
+ " if show_gaussian:\n",
+ " # The expected width of the Gaussian depends on the number of images.\n",
+ " expected_scale = rn / gain * np.sqrt(n_images)\n",
+ " \n",
+ " # Mean value is same as for the Poisson distribution \n",
+ " expected_mean = expected_mean_dark * n_images\n",
+ " gauss = stats.norm(loc=expected_mean, scale=expected_scale)\n",
+ " \n",
+ " gauss_x = np.linspace(expected_mean - 5 * expected_scale,\n",
+ " expected_mean + 5 * expected_scale,\n",
+ " num=100)\n",
+ " plt.plot(gauss_x / n_images, gauss.pdf(gauss_x) * n_images, label='Gaussian, standard dev is read noise in counts') \n",
+ " \n",
+ " plt.xlabel(\"Dark counts in {} sec exposure\".format(exposure))\n",
+ " plt.ylabel(\"Fraction of pixels (area normalized to 1)\")\n",
+ " plt.grid()\n",
+ " plt.legend()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Dark current theory\n",
+ "\n",
+ "The expected signal in a dark frame exposure of time $t$ is proportional to $t$.\n",
+ "If we call the dark electrons in an exposure $d_e(t)$ and the dark current\n",
+ "$d_c(T)$, where $T$ is the temperature, then\n",
+ "\n",
+ "$$\n",
+ "d_e(t) = d_c(T) t.\n",
+ "$$\n",
+ "\n",
+ "For liquid-cooled cameras, particularly ones cooled by liquid nitrogen, the\n",
+ "operating temperature doesn't change. For thermoelectrically-cooled cameras you are\n",
+ "able to set the desired operating temperature. As a result, you should be\n",
+ "able to ignore the temperature dependence of the dark current.\n",
+ "\n",
+ "The thermoelectric coolers can usually cool by some fixed amount below the\n",
+ "ambient temperature. Though in principle you could choose to always cool by the\n",
+ "same fixed amount, like $50^\\circ$C below the ambient temperature, there is an\n",
+ "advantage to always running your camera at the same temperature: dark frames\n",
+ "taken on one date are potentially useful on another date. If the operating\n",
+ "temperature varies then you need to make sure to take dark frames every time you\n",
+ "observe unless you carefully characterize the temperature dependence of your\n",
+ "dark current.\n",
+ "\n",
+ "It will turn out that for practical reasons — not all pixels in your camera\n",
+ "have the same dark current — it is usually best to take dark frames every time\n",
+ "you observe anyway."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Illustration with dark current only\n",
+ "\n",
+ "For the purposes of illustrating some of the properties of dark current and dark\n",
+ "frames, we'll generate some simulated images in which the counts are due to dark\n",
+ "current alone. We'll use these values:\n",
+ "\n",
+ "+ Dark current is $d_c(T) = 0.1 e^-$/pixel/sec\n",
+ "+ Gain is $g = 1.5 e^-$/ADU\n",
+ "+ Read noise is 0 $e^-$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "dark_rate = 0.1\n",
+ "gain = 1.5\n",
+ "read_noise_electrons = 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Dark current is a random process\n",
+ "\n",
+ "The dark counts in a dark frame are counts and so they follow a Poisson\n",
+ "distribution. The plot below shows the dark current in a number of randomly\n",
+ "chosen pixels in 20 different simulated images each with exposure time 100 sec.\n",
+ "Note that the counts vary from image to image but that the average is very close\n",
+ "to the expected value.\n",
+ "\n",
+ "The expected value of the dark counts for this image is $d_e(t)/g =\n",
+ "6.67~$counts."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "exposure = 100\n",
+ "\n",
+ "n_images = 20\n",
+ "n_pixels = 10\n",
+ "image_size = 500\n",
+ "\n",
+ "\n",
+ "\n",
+ "plt.figure(figsize=(20, 10))\n",
+ "\n",
+ "darks = generate_dark_frames(n_images, dark_rate=dark_rate, noise=read_noise_electrons,\n",
+ " gain=gain, exposure=100, image_size=image_size)\n",
+ "\n",
+ "\n",
+ "# Pick several random pixels to plot\n",
+ "pixels = np.random.randint(50, high=190, size=n_pixels)\n",
+ "pixel_values = np.zeros(n_images)\n",
+ "pixel_averages = np.zeros(n_images)\n",
+ "\n",
+ "for pixel in pixels:\n",
+ " for n in range(n_images):\n",
+ " pixel_values[n] = darks[n, pixel, pixel]\n",
+ " plt.plot(pixel_values, label='pixel [{0}, {0}]'.format(pixel), alpha=0.5)\n",
+ " pixel_averages += pixel_values\n",
+ "\n",
+ "\n",
+ "plt.plot(pixel_averages / n_pixels, \n",
+ " linewidth=3,\n",
+ " label='Average over {} pixels'.format(n_pixels))\n",
+ "# plt.xlim(0, n_images - 1)\n",
+ "plt.hlines(dark_rate * exposure / gain, *plt.xlim(), \n",
+ " linewidth=3, \n",
+ " label=\"Expected counts\")\n",
+ "plt.xlabel('Image number')\n",
+ "plt.ylabel('Counts due to dark current')\n",
+ "\n",
+ "plt.legend()\n",
+ "plt.grid()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Dark counts follow a Poisson distribution\n",
+ "\n",
+ "The distribution below shows a normalized histogram of number of pixels as a\n",
+ "function of dark counts in each pixel for one of the simulated dark frames.\n",
+ "Overlaid on the histogram is a Poisson distribution with a mean of $d_e(t_{exp})\n",
+ "= d_C(T) * t_{exp} / g$, where $t_{exp}$ is the exposure time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(20, 10))\n",
+ "\n",
+ "\n",
+ "h = plt.hist(darks[-1].flatten(), bins=20, align='mid', density=True, \n",
+ " label=\"Histogram of dark current counts in single image\");\n",
+ "bins = h[1]\n",
+ "pois = stats.poisson(dark_rate * exposure / gain)\n",
+ "pois_x = np.arange(0, 20, 1)\n",
+ "\n",
+ "plt.plot(pois_x, pois.pmf(pois_x), \n",
+ " label=\"Poisson dsitribution, mean of {:5.2f} counts\".format(dark_rate * exposure / gain)) \n",
+ "\n",
+ "plt.xlabel(\"Dark counts in {} exposure\".format(exposure))\n",
+ "plt.ylabel(\"Number of pixels (area normalized to 1)\")\n",
+ "plt.legend()\n",
+ "plt.grid()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Illustration with dark current and read noise\n",
+ "\n",
+ "Now let's run through the same couple of plots with a non-zero read noise. For\n",
+ "the sake of illustration, we'll look at two cases:\n",
+ "\n",
+ "1. Moderate read noise of 10 $e^-$ per read, typical of a low-end research-grade\n",
+ "CCD\n",
+ "2. Low read noise of 1 $e^-$ per read\n",
+ "\n",
+ "In both cases we'll continue with the parameters above to generate our frames:\n",
+ "\n",
+ "+ Dark current is $d_c(T) = 0.1 e^-$/pixel/sec\n",
+ "+ Gain is $g = 1.5 e^-$/ADU\n",
+ "+ Exposure time 100 sec\n",
+ "\n",
+ "With those choices the expected dark count is 6.67 count, which is 10 $e^-$.\n",
+ "That is, not coincidentally, one of the values for read noise that was chosen.\n",
+ "\n",
+ "The dark current rate in this example is somewhat higher than that of a real\n",
+ "CCD. A large dark current is used here to demonstrate the interaction of dark\n",
+ "current and read noise, and the exposure time has been chosen that expected dark\n",
+ "electrons is 10$e-$."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### High noise\n",
+ "\n",
+ "In this first case, the read noise and the dark current are both 10$e^-$. As we\n",
+ "will see shortly, the dark frame really measures read noise, not dark current,\n",
+ "under these circumstances."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "high_read_noise = 10"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "darks = generate_dark_frames(n_images, dark_rate=dark_rate, noise=high_read_noise, \n",
+ " gain=gain, exposure=exposure, image_size=image_size)\n",
+ "\n",
+ "image_average = darks.mean(axis=0)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(20, 10))\n",
+ "\n",
+ "plot_dark_with_distributions(darks[-1], high_read_noise, dark_rate, \n",
+ " n_images=1, exposure=exposure, gain=gain)\n",
+ "\n",
+ "plt.xlim(-20, 30);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**This dark frame measures noise, not dark current**\n",
+ "\n",
+ "The pixel distribution is clearly a Gaussian distribution with a width\n",
+ "determined by the read noise, not the underlying Poisson distribution that a\n",
+ "dark frame is trying to measure. The only way around this (assuming the dark\n",
+ "current is large enough that it needs to be subtracted at all) is to make the\n",
+ "exposure long enough that the expected counts exceed the dark current.\n",
+ "\n",
+ "We explore that case below by adding in a much smaller amount of noise."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Combining does not recover the dark current**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The plots below show the result of averaging several (20 in this case) dark\n",
+ "frames together. We might hope that averaging reduces the noise enough that the\n",
+ "distribution of pixels becomes the Poisson distribution expected for a dark\n",
+ "frame. It does not.\n",
+ "\n",
+ "*Note:* To correctly calculate the expected distribution of counts in the\n",
+ "average of the dark frames you should start with the expectations for the *sum*\n",
+ "of the images, then scale counts down by the number of images and the\n",
+ "distribution up by the same factor so that the distribution remains normalized."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(20, 10))\n",
+ "\n",
+ "plot_dark_with_distributions(image_average, high_read_noise, dark_rate, \n",
+ " n_images=20, exposure=exposure, gain=gain)\n",
+ "\n",
+ "plt.title(\"Distribution of counts for the average of 20 dark images, high noise case\");"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Even the combination of 20 dark frames doesn't lead to a Poisson distribution;\n",
+ "the distribution is still a Gaussian whose width is determined by the read noise\n",
+ "and the number of images."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Low noise\n",
+ "\n",
+ "In this case the read noise is 1 $e^-$, lower by a factor of ten than the\n",
+ "expected dark current for this exposure time, 10$e^-$.\n",
+ "\n",
+ "This case almost never occurs in real CCDs, but it illustrates that with a low\n",
+ "enough read noise you can recover the Poisson distribution of the dark current."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "low_read_noise = 1\n",
+ "\n",
+ "darks = generate_dark_frames(n_images, dark_rate=dark_rate, noise=low_read_noise, \n",
+ " gain=gain, exposure=exposure, image_size=image_size)\n",
+ "\n",
+ "image_average = darks.mean(axis=0)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(20, 10))\n",
+ "\n",
+ "\n",
+ "plot_dark_with_distributions(darks[-1], low_read_noise, dark_rate, \n",
+ " n_images=1, exposure=exposure, gain=gain)\n",
+ "\n",
+ "#plt.ylim(0, 0.8)\n",
+ "plt.xlim(-10, 20)\n",
+ "plt.title(\"Distribution of counts for the average of one dark images, low noise case\");"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(20, 10))\n",
+ "\n",
+ "\n",
+ "plot_dark_with_distributions(image_average, low_read_noise, dark_rate, \n",
+ " n_images=20, exposure=exposure, gain=gain)\n",
+ "\n",
+ "plt.ylim(0, 0.8)\n",
+ "plt.xlim(0, 14)\n",
+ "plt.title(\"Distribution of counts for the average of 20 dark images, low noise case\");"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Dark frame is measuring dark current**\n",
+ "\n",
+ "Unlike the high noise case, the pixels in a single low-noise dark frame follow\n",
+ "the Poisson distribution expected for the dark current in this simulated camera.\n",
+ "When multiple images are combined the pixels still follow the Poisson\n",
+ "distribution, as expected in this case."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/03-02-Real-dark-current-noise-and-other-artifacts.ipynb b/v/pdev/_sources/notebooks/03-02-Real-dark-current-noise-and-other-artifacts.ipynb
new file mode 100644
index 00000000..4678a02c
--- /dev/null
+++ b/v/pdev/_sources/notebooks/03-02-Real-dark-current-noise-and-other-artifacts.ipynb
@@ -0,0 +1,496 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Real dark current: noise and other artifacts"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The dark current on modern CCDs, even relatively inexpensive ones, is very\n",
+ "small. In a typical exposure the dark current is likely smaller, maybe much\n",
+ "smaller, than the read noise. Despite that, it is worth examining some dark\n",
+ "frames from your camera because not all pixels have the same dark current.\n",
+ "Some, called hot pixels, have much higher dark current than the rest of the\n",
+ "sensor. Some sensors have non-astronomical sources of counts whose value are\n",
+ "proportional to exposure time, like dark current, but whose origin is nonthermal\n",
+ "effects.\n",
+ "\n",
+ "As we did in the [notebook about overscan](01-08-Overscan.ipynb), we will focus on a pair of\n",
+ "cameras to illustrate this point and demonstrate how to take similar images with\n",
+ "your camera and analyze them."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "\n",
+ "from astropy.nddata import CCDData\n",
+ "from astropy.visualization import hist\n",
+ "\n",
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Making an image to check for hot pixels\n",
+ "\n",
+ "Measuring the dark current in a camera requires taking images with fairly long\n",
+ "exposure time, ideally long enough that the expected counts due to dark current\n",
+ "is larger than the expected noise in the image. If that isn't the case then the\n",
+ "dark images will be measuring noise, not dark current, as we saw in the\n",
+ "[previous section](03-01-Dark-current-The-ideal-case.ipynb).\n",
+ "\n",
+ "There are two ways to ensure that you are measuring dark current:\n",
+ "\n",
+ "+ Take some long-exposure dark frames. The exposure time should be long enough\n",
+ "that the expected dark counts is at least as large as the noise expected in a\n",
+ "single image.\n",
+ "+ Take several of these images and combine them to reduce the noise in the\n",
+ "result. The noise in the combined image is proportional to $1/\\sqrt{N}$, where\n",
+ "$N$ is the number of images that are combined.\n",
+ "\n",
+ "\n",
+ "### What if that isn't possible?\n",
+ "\n",
+ "Do your best. The darks from the Large Format Camera were taken as part of a\n",
+ "science run in which there was limited time for taking dark frames. The key\n",
+ "thing to keep in mind is the lower limit on the dark current you can measure,\n",
+ "given the read noise and exposure time.\n",
+ "\n",
+ "The darks in Case 2 were taken with a new camera that was not yet mounted on the\n",
+ "telescope as part of commissioning the camera. That made taking over five hours of\n",
+ "dark frames more feasible!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Case 1: Cryogenically cooled Large Format Camera (LFC) at Palomar\n",
+ "\n",
+ "The images in this section are from chip 0 of the LFC at the Palomar 200-inch\n",
+ "telescope. Technical information about the camera is\n",
+ "[here](http://www.astro.caltech.edu/palomar/observer/200inchResources/lfcspecs.html). The technical information says nothing about\n",
+ "the expected dark current of this camera. The expectation for cameras cooled by\n",
+ "liquid nitrogen is that their dark current is essentially zero.\n",
+ "\n",
+ "What we will see with this camera is that\n",
+ "\n",
+ "+ not all of the pixels have negligible dark current, and\n",
+ "+ not all of the counts in a dark image are from dark current.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Estimating exposure time and number of frames for measuring dark current\n",
+ "\n",
+ "Unfortunately, the technical information for this camera does not include any\n",
+ "information about the expected dark current. For thermoelectrically-cooled\n",
+ "cameras the dark current is no more than 0.1 $e^-$/sec/pixel, and for newer\n",
+ "camers is more like 0.01$e^-$/sec/pixel. We will assume the smaller dark current\n",
+ "here.\n",
+ "\n",
+ "The read noise for this camera is 11$e^-$/pixel, so the exposure time needed to\n",
+ "just reach the case where the dark counts is equal to the read noise is\n",
+ "\n",
+ "$$\n",
+ "t_\\text{dark, min} = \\frac{11 e^-/\\text{pixel}}{0.01 e^-/\\text{sec/pixel}} = 1100~\\text{sec}.\n",
+ "$$\n",
+ "\n",
+ "Even that leaves us with an image dominated by read noise rather than dark\n",
+ "current. Combining 100 of those images would reduce the read noise by a factor\n",
+ "of 10, which would be ideal for precisely measuring the dark current in each\n",
+ "pixel. Unfortunately, taking over 30 hours of dark images is not feasible.\n",
+ "\n",
+ "Instead, we will work with what we have for this camera: three dark images, each\n",
+ "300 seconds. This places a lower limit on the dark current we can measure — we\n",
+ "will only be able to measure the dark current for fairly \"hot\" pixels — but it\n",
+ "at least gives us some idea of the dark properties of the camera.\n",
+ "\n",
+ "Another way to phrase this is much more positive: for most of the pixels in the\n",
+ "image the dark current simply does not matter because it is completely swamped\n",
+ "by the read noise."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First, we read in the image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_images = Path('.')\n",
+ "combined_dark_lfc = CCDData.read(calibrated_images / 'combined_dark_300.000.fits.bz2')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Examine the image\n",
+ "\n",
+ "The full image is on the left, and the lower left corner is shown on the right.\n",
+ "The light area in the lower left corner is due to sensor glow, which is light\n",
+ "emitted by the electronics of the CCD. The counts in the image due to sensor\n",
+ "glow should grow linearly with exposure time, like dark counts do, but the pixel\n",
+ "values are much higher than for pixels whose counts are due to dark current.\n",
+ "\n",
+ "The top right panel in the figure below shows the part of the CCD near the\n",
+ "sensor glow; pixel values are several thousand counts. The bottom right panel\n",
+ "shows a portion of the CCD in the upper right part of the chip; pixel values are\n",
+ "*much* lower, typically under 10."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = plt.figure(figsize=(10, 10))\n",
+ "whole_im_ax = plt.subplot(121)\n",
+ "glow_ax = plt.subplot(222)\n",
+ "dark_ax = plt.subplot(224)\n",
+ "\n",
+ "show_image(combined_dark_lfc, cmap='gray', ax=whole_im_ax, fig=fig)\n",
+ "whole_im_ax.set_title('Entire chip')\n",
+ "\n",
+ "show_image(combined_dark_lfc[:200, :200], cmap='gray', ax=glow_ax, fig=fig)\n",
+ "glow_ax.set_title('Lower left corner of chip')\n",
+ "\n",
+ "show_image(combined_dark_lfc[-200:, -200:], cmap='gray', ax=dark_ax, fig=fig)\n",
+ "dark_ax.set_title('Upper right corner of chip')\n",
+ "\n",
+ "plt.tight_layout()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Dark current in this CCD\n",
+ "\n",
+ "The histogram below shows dark current along the horizontal axis and the number\n",
+ "of pixels in the CCD with that dark current along the vertical axis. Since there\n",
+ "is obvious sensor glow in the darks, three distributions are shown:\n",
+ "\n",
+ "+ The full sensor, which at first glance has some pixels with dark current as\n",
+ "high as 10 $e^-$/sec.\n",
+ "+ The portion of the sensor *excluding* the sensor glow, in which the dark\n",
+ "current is, as expected, low, though there is a small fraction of \"hot pixels\"\n",
+ "with dark current around 1 $e^-$/sec.\n",
+ "+ The sensor glow region. That region is apparent in the clump of pixels with\n",
+ "equivalent dark current of 0.5$e^-$/sec and higher.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gain = 2.0\n",
+ "exposure_time = 300\n",
+ "dark_current_ln2 = gain * combined_dark_lfc.data / exposure_time\n",
+ "\n",
+ "fig = plt.figure(figsize=(20, 10))\n",
+ "hist(dark_current_ln2.flatten(), bins=500, density=False, alpha=0.5, color='C0',\n",
+ " label='Full sensor');\n",
+ "hist(dark_current_ln2[200:, 200:].flatten(), bins=500, density=False, alpha=0.5, color='C1',\n",
+ " label='Sensor, excluding sensor glow region')\n",
+ "hist(dark_current_ln2[:200, :200].flatten(), bins=500, density=False, alpha=0.5, color='C9',\n",
+ " label='Sensor glow region only');\n",
+ "#plt.semilogy()\n",
+ "plt.loglog()\n",
+ "\n",
+ "plt.grid()\n",
+ "noise_limit = 2 * 11/exposure_time/np.sqrt(3)\n",
+ "plt.vlines(2 * 11/exposure_time/np.sqrt(3), 0.1, 8e6)\n",
+ "plt.ylim(0.1, 8e6)\n",
+ "plt.xlim(1e-3, 1e2)\n",
+ "x_min, x_max = plt.xlim()\n",
+ "y_min, y_max = plt.ylim()\n",
+ "noisy_region = plt.Rectangle((x_min, y_min), noise_limit - x_min, y_max - y_min, label='Noise-dominated region',\n",
+ " color='gray', alpha=0.2, hatch='/')\n",
+ "ax = plt.gca()\n",
+ "ax.add_patch(noisy_region)\n",
+ "plt.legend()\n",
+ "plt.xlabel('dark current, electrons per second')\n",
+ "plt.ylabel('Number of pixels with that dark current');\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Case 2: Thermoelectrically-cooled camera"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Taking an image to measure dark current"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The image below is a combination of 20 dark frames, each a 1000 sec exposure.\n",
+ "That exposure time was chosen so that the expected number of dark electrons was\n",
+ "at least somewhat larger than the read noise expected in a combination of 20\n",
+ "images. As we saw in the previous notebook, unless that is the case the dark\n",
+ "frames will measure read noise instead of dark current.\n",
+ "\n",
+ "This camera has a dark current of $0.01 e^-$/sec/pixel and read noise of roughly\n",
+ "$10 e^-$/read/pixel/, so the exposure time at which the dark current is as large\n",
+ "as the read noise is\n",
+ "\n",
+ "$$\n",
+ "t_\\text{dark, min} = \\frac{10 e^-/\\text{pixel}}{0.01 e^-/\\text{sec/pixel}} = 1000~\\text{sec}.\n",
+ "$$\n",
+ "\n",
+ "By combining 20 of the images, the expected noise in the combination is reduced\n",
+ "by a factor of $\\sqrt{20}$ to $2.2 e^-$/pixel. This puts us in the \"low read\n",
+ "noise\" limit in the previous notebook about ideal dark current.\n",
+ "\n",
+ "Measuring the dark current once you have the images is relatively\n",
+ "direct: subtract the bias, multiply the result by the gain, and then\n",
+ "divide by the exposure time to obtain the dark current in $e^-$/pixel/sec."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "dark_1000 = CCDData.read('combined_dark_exposure_1000.0.fit.bz2')\n",
+ "show_image(dark_1000, cmap='gray')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Calculate the dark current for each pixel\n",
+ "\n",
+ "Recall that the dark current $d_c(T)$ is given by\n",
+ "\n",
+ "$$\n",
+ "d_c(T) = d_e(t) / t,\n",
+ "$$\n",
+ "\n",
+ "where $d_e(t)$ is the number of dark electrons in the images. That is related to\n",
+ "the dark counts, $n_{dark}(t)$, the image values displayed in the image above,\n",
+ "by the gain of the camera, $d_e(t) = g n_{dark}(t)$, so that\n",
+ "\n",
+ "$$\n",
+ "d_C(T) = g n_{dark}(t) / t.\n",
+ "$$\n",
+ "\n",
+ "This particular camera has a gain of $g = 1.5 e^-$/ADU."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gain = 1.5\n",
+ "read_noise = 10.0\n",
+ "exposure_time = 1000\n",
+ "\n",
+ "dark_current = gain * dark_1000.data / exposure_time\n",
+ "\n",
+ "noisy_region = 2 * read_noise / exposure_time / np.sqrt(20)\n",
+ "\n",
+ "plt.figure(figsize=(20, 10))\n",
+ "hist(dark_current.flatten(), bins=5000, density=False, label='Dark current');\n",
+ "\n",
+ "plt.ylim(0.1, 4e7)\n",
+ "plt.vlines(noisy_region, *plt.ylim())\n",
+ "\n",
+ "plt.loglog()\n",
+ "plt.grid()\n",
+ "\n",
+ "\n",
+ "x_min, x_max = plt.xlim()\n",
+ "y_min, y_max = plt.ylim()\n",
+ "noisy_region = plt.Rectangle((x_min, y_min), noisy_region - x_min, y_max - y_min, label='Noise-dominated region',\n",
+ " color='gray', alpha=0.2, hatch='/')\n",
+ "ax = plt.gca()\n",
+ "ax.add_patch(noisy_region)\n",
+ "\n",
+ "plt.legend()\n",
+ "\n",
+ "plt.xlabel('dark current, electrons per second')\n",
+ "plt.ylabel('Number of pixels with that dark current');\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### There is a large range in the dark current\n",
+ "\n",
+ "While the vast majority of the pixels do, as expected, have very low dark\n",
+ "current, it is much higher for other pixels. These pixels, called hot pixels,\n",
+ "can occur even in cryogenically-cooled cameras.\n",
+ "\n",
+ "The vast majority of the pixels in the image have a dark current around the\n",
+ "value promised by the manufacturer, 0.01 $e^-$/sec; the highest nominally have a\n",
+ "dark current of 98 $e^-$/sec.\n",
+ "\n",
+ "However, there is an upper limit to the dark current that can be measured in a\n",
+ "particular exposure because the CCD saturates once the counts become large\n",
+ "enough. In other words, there is an upper limit to the number of counts a CCD\n",
+ "can represent; for this camera the limit is $2^{16} -1$, or $65,563$ counts. If\n",
+ "we convert that to a dark current for a 1000 sec exposure, it is approximately\n",
+ "\n",
+ "$$\n",
+ "\\text{dark current for saturated pixel} = \\text{gain} \\times \\frac{\\text{saturation counts}}{\\text{exposure time}} = 1.5 \\frac{e^-}{\\text{count}}\\frac{65,563~\\text{count}}{1000 \\text{sec}} = 98 \\frac{e^-}{\\text{sec}}.\n",
+ "$$\n",
+ "\n",
+ "For the hottest pixels in this image, that is the *lower limit* of the dark\n",
+ "current for those pixels.\n",
+ "\n",
+ "The dark current is also not well estimated for pixels a bit below the maximum\n",
+ "because CCDs stop responding linearly once they pass a pixel value that depends\n",
+ "on the camera. For this camera, the linearity limit is around 55,000 counts,\n",
+ "corresponding to a dark current of roughly 82 $e^-$/sec.\n",
+ "\n",
+ "You can, in principle, also check whether or not the hot pixels have a constant dark\n",
+ "current that does not change with time by creating a number of these darks at\n",
+ "different exposure times and plotting the dark current as a function of exposure\n",
+ "time for the hot pixels. If the dark current is constant then the dark counts\n",
+ "will be properly removed when the dark is subtracted from the science image. If\n",
+ "the dark current is not constant, the pixel should be excluded from analysis.\n",
+ "\n",
+ "The fraction of very hot pixels, those above 1 $e^-$/sec, is relatively small,\n",
+ "roughly 0.1% of the pixels."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(20, 10))\n",
+ "hist(dark_current_ln2[200:, 200:].flatten(), bins=5000, density=True, alpha=0.5, label='Cryogenically cooled, excluding sensor glow');\n",
+ "hist(dark_current.flatten(), bins=5000, density=True, alpha=0.5, label='Thermo-electrically cooled');\n",
+ "plt.grid()\n",
+ "plt.loglog()\n",
+ "plt.xlabel('dark current, electrons per second')\n",
+ "plt.ylabel('Number of pixels with that dark current');\n",
+ "plt.legend();"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## How to handle hot pixels\n",
+ "\n",
+ "There are a few ways you could handle this, which can be used in combination\n",
+ "with each other:\n",
+ "\n",
+ "+ Mark all pixels above some threshold as bad and create a mask to keep track of\n",
+ "these bad pixels.\n",
+ "+ Mark only the really high dark current pixels as bad and mask them; for the\n",
+ "rest, subtract dark current as usual.\n",
+ "+ Always take dark images with exposure times that match your flat and light\n",
+ "frames and subtract dark current as usual.\n",
+ "+ Take one set of darks with exposure equal to the *longest* exposure time in\n",
+ "your flat and light images and scale the dark current downwards to match your\n",
+ "other exposure times.\n",
+ "\n",
+ "## One thing NOT to do\n",
+ "\n",
+ "Do not take short dark frames and scale them up to longer exposure times. Modern\n",
+ "cameras, even thermoelectrically-cooled ones, have very low dark current. If\n",
+ "your dark frames have low exposure time then most of the pixels are measuring\n",
+ "read noise, not dark current. If you rescale those images to a longer exposure\n",
+ "time then you inappropriately amplify that noise. Ideally, the expected dark\n",
+ "counts (or dark electrons) in your dark frames should be at least a few times\n",
+ "larger than the expected read noise in the frames you combine to make a\n",
+ "reference dark."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Summary\n",
+ "\n",
+ "1. A dark frame only measures dark current if the expected dark counts exceed\n",
+ "the read noise of the camera by a factor of a few.\n",
+ "2. Take multiple dark frames and combine them to reduce the noise level in the\n",
+ "combined image as much as possible.\n",
+ "2. Most pixels in a CCD have very low dark current.\n",
+ "3. A consequence of 1 and 3 is that you should almost never scale your dark\n",
+ "frames up to a longer exposure time because you will amplify noise instead of\n",
+ "eliminating dark current, even if you have combined multiple images as in 2.\n",
+ "3. Identify hot pixels and mask them out or otherwise deal with them."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/03-04-Handling-overscan-and-bias-for-dark-frames.ipynb b/v/pdev/_sources/notebooks/03-04-Handling-overscan-and-bias-for-dark-frames.ipynb
new file mode 100644
index 00000000..e9dab132
--- /dev/null
+++ b/v/pdev/_sources/notebooks/03-04-Handling-overscan-and-bias-for-dark-frames.ipynb
@@ -0,0 +1,89 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Handling overscan and bias for dark frames"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## The options for reducing dark frames\n",
+ "\n",
+ "The next steps to take depend on two things:\n",
+ "\n",
+ "1. Are you subtracting overscan? If so, you should subtract overscan for the\n",
+ "dark frames.\n",
+ "1. Will you need to scale these darks to a different exposure time? If so, you\n",
+ "need to subtract bias from the darks. If not, leave the bias in."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 1. Do you need to subtract overscan?\n",
+ "\n",
+ "If you decide to subtract the overscan from *any* of the images used in your\n",
+ "data reduction then you must subtract overscan from *all* of the images. This\n",
+ "includes the darks, and is independent of whether or not you intend to scale the dark\n",
+ "frames to other exposure times.\n",
+ "\n",
+ "Use [`ccdproc.subtract_overscan`](https://ccdproc.readthedocs.io/en/latest/ccdproc/reduction_toolbox.html#overscan-subtraction) to remove the overscan. See the notebook XX\n",
+ "for a discussion of overscan, and see YY for a worked example in which overscan\n",
+ "is subtracted."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 2. Do you need to scale the darks?\n",
+ "\n",
+ "It depends on the exposure times of the other images you need to reduce. If the\n",
+ "other images have exposure times that match your dark frames then you do not\n",
+ "need to scale the darks. If any other images (except bias frames, which are\n",
+ "always zero exposure) have exposure time that do not match the exposure times of\n",
+ "the darks, then you will need to scale the darks by exposure time.\n",
+ "\n",
+ "If you do need to scale the dark frames, then you should subtract the bias from\n",
+ "them using [`ccdproc.subtract_bias`](https://ccdproc.readthedocs.io/en/latest/ccdproc/reduction_toolbox.html#subtract-bias-and-dark). Examples of using\n",
+ "[`ccdproc.subtract_bias`](https://ccdproc.readthedocs.io/en/latest/ccdproc/reduction_toolbox.html#subtract-bias-and-dark) are in the next notebook.\n",
+ "\n",
+ "If you do not need to scale the dark frames to a different exposure time then do\n",
+ "not subtract the bias from the darks. The dark frames will serve to remove both\n",
+ "the bias and the dark current your images.\n",
+ "\n",
+ "\n",
+ "As a reminder, you should try very hard to avoid scaling dark frames up to a\n",
+ "longer exposure time because you will primarily be scaling up noise rather than\n",
+ "dark current.\n",
+ "\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/03-05-Calibrate-dark-images.ipynb b/v/pdev/_sources/notebooks/03-05-Calibrate-dark-images.ipynb
new file mode 100644
index 00000000..5c9310b3
--- /dev/null
+++ b/v/pdev/_sources/notebooks/03-05-Calibrate-dark-images.ipynb
@@ -0,0 +1,317 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Calibrate dark images\n",
+ "\n",
+ "Dark images, like any other images, need to be calibrated. Depending on the data\n",
+ "you have and the choices you have made in reducing your data, the steps to\n",
+ "reducing your images may include:\n",
+ "\n",
+ "1. Subtracting overscan (only if you decide to subtract overscan from all\n",
+ "images).\n",
+ "2. Trim the image (if it has overscan, whether you are using the overscan or\n",
+ "not).\n",
+ "3. Subtract bias (if you need to scale the calibrated dark frames to a different\n",
+ "exposure time)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "from astropy.nddata import CCDData\n",
+ "from ccdproc import ImageFileCollection\n",
+ "import ccdproc as ccdp"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 1: Overscan subtracted, bias not removed"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Take a look at what images you have\n",
+ "\n",
+ "First we gather up some information about the raw images and the reduced images\n",
+ "up to this point. These examples have darks stored in a subdirectory of the\n",
+ "folder with the rest of the images, so we create an `ImageFileCollection` for\n",
+ "each."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex1_path_raw = Path('example-cryo-LFC')\n",
+ "\n",
+ "ex1_images_raw = ImageFileCollection(ex1_path_raw)\n",
+ "ex1_darks_raw = ImageFileCollection(ex1_path_raw / 'darks')\n",
+ "\n",
+ "ex1_path_reduced = Path('example1-reduced')\n",
+ "ex1_images_reduced = ImageFileCollection(ex1_path_reduced)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Raw images, everything except the darks"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex1_images_raw.summary['file', 'imagetyp', 'exptime', 'filter']"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Raw dark frames"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex1_darks_raw.summary['file', 'imagetyp', 'exptime', 'filter']"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Decide which calibration steps to take\n",
+ "\n",
+ "This example is, again, one of the chips of the LFC camera at Palomar. In\n",
+ "earlier notebooks we have seen that the chip has a [useful overscan region](01-08-Overscan.ipynb#case-1-cryogenically-cooled-large-format-camera-lfc-at-palomar), has little dark current except for some hot pixels, and sensor glow in\n",
+ "one corner of the chip.\n",
+ "\n",
+ "Looking at the list of non-dark images (i.e., the flat and light images) shows\n",
+ "that for each exposure time in the non-dark images there is a set of dark\n",
+ "exposures that has a matching, or very close to matching, exposure time.\n",
+ "\n",
+ "To be more explicit, there are flats with exposure times of 7.0 sec and 70.011\n",
+ "sec and darks with exposure time of 7.0 and 70.0 sec. The dark and flat exposure\n",
+ "times are close enough that there is no need to scale them. The two images of\n",
+ "an object are each roughly 300 sec, matching the darks with exposure time 300\n",
+ "sec. The very small difference in exposure time, under 0.1 sec, does not need to\n",
+ "be compensated for.\n",
+ "\n",
+ "Given this, we will:\n",
+ "\n",
+ "1. Subtract overscan from each of the darks. The useful overscan region is XXX\n",
+ "(see LINK).\n",
+ "2. Trim the overscan out of the dark images.\n",
+ "\n",
+ "We will *not* subtract bias from these images because we will *not* need to\n",
+ "rescale them to a different exposure time."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Calibrate the individual dark frames"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for ccd, file_name in ex1_darks_raw.ccds(imagetyp='DARK', # Just get the dark frames\n",
+ " ccd_kwargs={'unit': 'adu'}, # CCDData requires a unit for the image if \n",
+ " # it is not in the header\n",
+ " return_fname=True # Provide the file name too.\n",
+ " ): \n",
+ " # Subtract the overscan\n",
+ " ccd = ccdp.subtract_overscan(ccd, overscan=ccd[:, 2055:], median=True)\n",
+ " \n",
+ " # Trim the overscan\n",
+ " ccd = ccdp.trim_image(ccd[:, :2048])\n",
+ " \n",
+ " # Save the result\n",
+ " ccd.write(ex1_path_reduced / file_name)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Reduced images (so far)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex1_images_reduced.refresh()\n",
+ "ex1_images_reduced.summary['file', 'imagetyp', 'exptime', 'filter', 'combined']"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 2: Overscan not subtracted, bias is removed"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex2_path_raw = Path('example-thermo-electric')\n",
+ "\n",
+ "ex2_images_raw = ImageFileCollection(ex2_path_raw)\n",
+ "\n",
+ "ex2_path_reduced = Path('example2-reduced')\n",
+ "ex2_images_reduced = ImageFileCollection(ex2_path_reduced)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We begin by looking at what exposure times we have in this data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex2_images_raw.summary['file', 'imagetyp', 'exposure'].show_in_notebook()"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Decide what steps to take next\n",
+ "\n",
+ "In this case the only dark frames have exposure time 90 sec. Though that matches\n",
+ "the exposure time of the science images, the flat field images are much shorter\n",
+ "exposure time, ranging from 1 sec to 1.21 sec. This type of range of exposure is\n",
+ "typical when twilight flats are taken. Since these are a much different\n",
+ "exposure time than the darks, the dark frames will need to be scaled.\n",
+ "\n",
+ "Recall that for this camera the overscan is not useful and should be\n",
+ "trimmed off.\n",
+ "\n",
+ "Given this, we will:\n",
+ "\n",
+ "1. Trim the overscan from each of the dark frames.\n",
+ "2. Subtract calibration bias from the dark frames so that we can scale the darks\n",
+ "to a different exposure time."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Calibration the individual dark frames"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First, we read the combined bias image created in the previous notebook. Though\n",
+ "we could do this based on the file name, using a systematic set of header\n",
+ "keywords to keep track of which images have been combined is less likely to lead\n",
+ "to errors."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combined_bias = CCDData.read(ex2_images_reduced.files_filtered(imagetyp='bias', \n",
+ " combined=True, \n",
+ " include_path=True)[0])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for ccd, file_name in ex2_images_raw.ccds(imagetyp='DARK', # Just get the bias frames\n",
+ " return_fname=True # Provide the file name too.\n",
+ " ):\n",
+ " \n",
+ " # Trim the overscan\n",
+ " ccd = ccdp.trim_image(ccd[:, :4096])\n",
+ " \n",
+ " # Subtract bias\n",
+ " ccd = ccdp.subtract_bias(ccd, combined_bias)\n",
+ " # Save the result\n",
+ " ccd.write(ex2_path_reduced / file_name)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/03-06-Combine-darks-for-use-in-later-calibration-steps.ipynb b/v/pdev/_sources/notebooks/03-06-Combine-darks-for-use-in-later-calibration-steps.ipynb
new file mode 100644
index 00000000..fc320aa6
--- /dev/null
+++ b/v/pdev/_sources/notebooks/03-06-Combine-darks-for-use-in-later-calibration-steps.ipynb
@@ -0,0 +1,320 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Combine calibrated dark images for use in later reduction steps\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The final step is to combine the individual calibrated dark images into a single\n",
+ "combined image. That combined image will have less noise than the individual\n",
+ "images, minimizing the noise added to the remaining images when the dark is\n",
+ "subtracted.\n",
+ "\n",
+ "Regardless of which path you took through the calibration of the biases (with\n",
+ "overscan or without) there should be a folder named either `example1-reduced` or\n",
+ "`example2-reduced` that contains the calibrated bias and dark images. If there\n",
+ "is not, please run the previous notebook before continuing with this one."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "import os\n",
+ "\n",
+ "from astropy.nddata import CCDData\n",
+ "from astropy.stats import mad_std\n",
+ "\n",
+ "import ccdproc as ccdp\n",
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "\n",
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Recommended settings for image combination\n",
+ "\n",
+ "As discussed in the [notebook about combining images](01-06-Image-combination.ipynb), the recommendation is\n",
+ "that you combine by averaging the individual images but sigma clip to remove\n",
+ "extreme values.\n",
+ "\n",
+ "[ccdproc](https://ccdproc.readthedocs.org) provides two ways to combine:\n",
+ "\n",
+ "+ An object-oriented interface built around the `Combiner` object, described in\n",
+ "the [ccdproc documentation on image combination](https://ccdproc.readthedocs.io/en/latest/image_combination.html).\n",
+ "+ A function called [`combine`](https://ccdproc.readthedocs.io/en/latest/api/ccdproc.combine.html#ccdproc.combine), which we will use here because the function\n",
+ "allows you to specify the maximum amount of memory that should be used during\n",
+ "combination. That feature can be essential depending on how many images you need\n",
+ "to combine, how big they are, and how much memory your computer has.\n",
+ "\n",
+ "*NOTE: If using a version of ccdproc lower than 2.0, set the memory limit a\n",
+ "factor of 2-3 lower than you want the maximum memory consumption to be.*"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 1: Cryogenically-cooled camera"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The remainder of this section assumes that the calibrated bias images are in the\n",
+ "folder `example1-reduced` which was created in the previous notebook."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_path = Path('example1-reduced')\n",
+ "reduced_images = ccdp.ImageFileCollection(calibrated_path)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Make a combined image for each exposure time in Example 1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There are several dark exposure times in this data set. By converting the times\n",
+ "in the summary table to a set it returns only the unique values."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "darks = reduced_images.summary['imagetyp'] == 'DARK'\n",
+ "dark_times = set(reduced_images.summary['exptime'][darks])\n",
+ "print(dark_times)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The code below loops over the dark exposure times and, for each exposure time:\n",
+ "\n",
+ "+ selects the relevant calibrated dark images,\n",
+ "+ combines them using the `combine` function,\n",
+ "+ adds the keyword `COMBINED` to the header so that later calibration steps can\n",
+ "easily identify which bias to use, and\n",
+ "+ writes the file whose name includes the exposure time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for exp_time in sorted(dark_times):\n",
+ " calibrated_darks = reduced_images.files_filtered(imagetyp='dark', exptime=exp_time,\n",
+ " include_path=True)\n",
+ "\n",
+ " combined_dark = ccdp.combine(calibrated_darks,\n",
+ " method='average',\n",
+ " sigma_clip=True, sigma_clip_low_thresh=5, sigma_clip_high_thresh=5,\n",
+ " sigma_clip_func=np.ma.median, sigma_clip_dev_func=mad_std,\n",
+ " mem_limit=350e6\n",
+ " )\n",
+ "\n",
+ " combined_dark.meta['combined'] = True\n",
+ "\n",
+ " dark_file_name = 'combined_dark_{:6.3f}.fit'.format(exp_time)\n",
+ " combined_dark.write(calibrated_path / dark_file_name)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Result for Example 1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "A single calibrated 300 second dark image and the combined 300 second image are\n",
+ "shown below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))\n",
+ "\n",
+ "show_image(CCDData.read(calibrated_darks[0]).data, cmap='gray', ax=ax1, fig=fig)\n",
+ "ax1.set_title('Single calibrated dark')\n",
+ "show_image(combined_dark.data, cmap='gray', ax=ax2, fig=fig)\n",
+ "ax2.set_title('{} dark images combined'.format(len(calibrated_darks)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 2: Thermoelectrically-cooled camera\n",
+ "\n",
+ "The process for combining the images is exactly the same as in example 1. The\n",
+ "only difference is the directory that contains the calibrated bias frames."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_path = Path('example2-reduced')\n",
+ "reduced_images = ccdp.ImageFileCollection(calibrated_path)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Make a combined image for each exposure time in Example 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In this example there are only darks of a single exposure time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "darks = reduced_images.summary['imagetyp'] == 'DARK'\n",
+ "dark_times = set(reduced_images.summary['exptime'][darks])\n",
+ "print(dark_times)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Despite the fact that there is only one exposure time, we might as well reuse\n",
+ "the code from above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for exp_time in sorted(dark_times):\n",
+ " calibrated_darks = reduced_images.files_filtered(imagetyp='dark', exptime=exp_time,\n",
+ " include_path=True)\n",
+ "\n",
+ " combined_dark = ccdp.combine(calibrated_darks,\n",
+ " method='average',\n",
+ " sigma_clip=True, sigma_clip_low_thresh=5, sigma_clip_high_thresh=5,\n",
+ " sigma_clip_func=np.ma.median, signma_clip_dev_func=mad_std,\n",
+ " mem_limit=350e6\n",
+ " )\n",
+ "\n",
+ " combined_dark.meta['combined'] = True\n",
+ "\n",
+ " dark_file_name = 'combined_dark_{:6.3f}.fit'.format(exp_time)\n",
+ " combined_dark.write(calibrated_path / dark_file_name)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Result for Example 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The difference between a single calibrated bias image and the combined bias\n",
+ "image is much clearer in this case."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))\n",
+ "\n",
+ "show_image(CCDData.read(calibrated_darks[0]).data, cmap='gray', ax=ax1, fig=fig)\n",
+ "ax1.set_title('Single calibrated dark')\n",
+ "show_image(combined_dark.data, cmap='gray', ax=ax2, fig=fig)\n",
+ "ax2.set_title('{} dark images combined'.format(len(calibrated_darks)))"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/05-00-Flat-corrections.ipynb b/v/pdev/_sources/notebooks/05-00-Flat-corrections.ipynb
new file mode 100644
index 00000000..9b7a9a16
--- /dev/null
+++ b/v/pdev/_sources/notebooks/05-00-Flat-corrections.ipynb
@@ -0,0 +1,67 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Flat corrections"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The purpose of flat corrections is to compensate for any nonuniformity in the\n",
+ "response of the CCD to light. There can be several reasons that the response is\n",
+ "not uniform across the detector:\n",
+ "\n",
+ "+ Variations in the sensitivity of pixels in the detector, though this source is\n",
+ "usually small.\n",
+ "+ Dust on either the filter or the glass window covering the detector.\n",
+ "+ Vignetting, a dimming in the corners of the image.\n",
+ "+ Anything else in the optical path that affects how much light reaches the\n",
+ "sensor.\n",
+ "\n",
+ "The fix for nonuniformity is the same in all cases: take an image in which\n",
+ "the illumination is uniform and use that to measure the response of the CCD.\n",
+ "\n",
+ "Unfortunately, achieving uniform illumination is difficult, and uniform\n",
+ "illumination with the same spectrum as the astronomical objects of interest is impossible."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.3"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": true,
+ "sideBar": true,
+ "skip_h1_title": true,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": true,
+ "toc_position": {},
+ "toc_section_display": true,
+ "toc_window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/05-01-about-flat-corrections.ipynb b/v/pdev/_sources/notebooks/05-01-about-flat-corrections.ipynb
new file mode 100644
index 00000000..198cd093
--- /dev/null
+++ b/v/pdev/_sources/notebooks/05-01-about-flat-corrections.ipynb
@@ -0,0 +1,95 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# More about flat corrections"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Overview of taking images for flat corrections\n",
+ "\n",
+ "There are a few ways of taking \"flat\" images:\n",
+ "\n",
+ "+ Twilight flats are images of the sky near zenith taken around sunrise or\n",
+ "sunset.\n",
+ "+ Dome flats are images of the inside of the dome (typically of a smooth\n",
+ "surface, not of the dome itself), illuminated by some light source in the dome.\n",
+ "For smaller telescopes an electroluminescent or LED illuminated panel can be\n",
+ "used as the light source.\n",
+ "+ Sky flats are composed of several science images.\n",
+ "\n",
+ "Ideally the flat images have fairly high counts (roughly half the maximum counts\n",
+ "of the detector) so that the *only* important source of error is Poisson error\n",
+ "due to the light in the flat images, and so that the signal-to-noise ratio in\n",
+ "those images is essentially zero."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Calibrating and combining flat images\n",
+ "\n",
+ "The process of calibrating and combining flat frames is largely the same\n",
+ "regardless of the light source being used.\n",
+ "\n",
+ "It is useful to think of flat frames as just like science images of stars or\n",
+ "galaxies. The telescope is taking a picture of a light source, so bias and dark\n",
+ "need to be removed from the individual images.\n",
+ "\n",
+ "When combining the images there is a new step we have not discussed yet:\n",
+ "normalizing (also called rescaling) the calibrated flat frames to a common mean\n",
+ "or median before combining them. In both sky and twilight flats the illumination\n",
+ "varies naturally from frame-to-frame. If the images are not scaled to a common\n",
+ "value before combining, then the ones taken while the sky is brighter will\n",
+ "inappropriately dominate the result. Dome flats ought to be, in principle,\n",
+ "perfectly stable with no time variation in their illumination. In practice,\n",
+ "every light source varies at some level; if you are trying to correct 1%\n",
+ "differences in illumination, then 1% fluctuations in the light source matter.\n",
+ "\n",
+ "Typically the mean or median is scaled to 1.0 before combining so that when the\n",
+ "science images are divided by the calibrated, combined flats, the science image\n",
+ "values do not change too much."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.3"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": true,
+ "sideBar": true,
+ "skip_h1_title": true,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": true,
+ "toc_position": {},
+ "toc_section_display": true,
+ "toc_window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/05-03-Calibrating-the-flats.ipynb b/v/pdev/_sources/notebooks/05-03-Calibrating-the-flats.ipynb
new file mode 100644
index 00000000..10160eea
--- /dev/null
+++ b/v/pdev/_sources/notebooks/05-03-Calibrating-the-flats.ipynb
@@ -0,0 +1,773 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Calibrating the flats\n",
+ "\n",
+ "Recall that the counts in an astronomical image include dark current, noise, and\n",
+ "a near-constant offset, the bias. The individual flat frames need to have bias\n",
+ "and dark removed from them. Depending on the exposure times of the images you\n",
+ "have, you may or may not need to subtract dark and bias separately.\n",
+ "\n",
+ "If the combined dark frame needs to be scaled to a different exposure time, then\n",
+ "bias and dark must be handled separately; otherwise, the dark and bias can be\n",
+ "removed in a single step because dark frames also include bias.\n",
+ "\n",
+ "The potential reduction steps for flat frames are below:\n",
+ "\n",
+ "+ Subtract overscan and trim, if necessary.\n",
+ "+ Subtract bias, if necessary.\n",
+ "+ Subtract dark current, scaling if necessary (scale down when possible).\n",
+ "\n",
+ "As in the chapters about bias and dark we will work through two examples. In\n",
+ "Example 1 the darks are not scaled and the overscan region is used as part of\n",
+ "the calibration. In Example 2 the darks are scaled and the overscan region is\n",
+ "trimmed off without being used."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Function definition\n",
+ "\n",
+ "The function below finds the nearest dark exposure time to the exposure time of\n",
+ "a given image. An exception is raised if the difference in exposure time is\n",
+ "larger than `tolerance`, unless `tolerance` is set to `None`. A small numerical\n",
+ "tolerance is most useful if you anticipate not scaling the dark frames and\n",
+ "finding a dark exposure time close to the time of the image. Disregarding the\n",
+ "tolerance is useful if the intent is to scale the dark frames anyway."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def find_nearest_dark_exposure(image, dark_exposure_times, tolerance=0.5):\n",
+ " \"\"\"\n",
+ " Find the nearest exposure time of a dark frame to the exposure time of the image,\n",
+ " raising an error if the difference in exposure time is more than tolerance.\n",
+ " \n",
+ " Parameters\n",
+ " ----------\n",
+ " \n",
+ " image : astropy.nddata.CCDData\n",
+ " Image for which a matching dark is needed.\n",
+ " \n",
+ " dark_exposure_times : list\n",
+ " Exposure times for which there are darks.\n",
+ " \n",
+ " tolerance : float or ``None``, optional\n",
+ " Maximum difference, in seconds, between the image and the closest dark. Set\n",
+ " to ``None`` to skip the tolerance test.\n",
+ " \n",
+ " Returns\n",
+ " -------\n",
+ " \n",
+ " float\n",
+ " Closest dark exposure time to the image.\n",
+ " \"\"\"\n",
+ "\n",
+ " dark_exposures = np.array(list(dark_exposure_times))\n",
+ " idx = np.argmin(np.abs(dark_exposures - image.header['exptime']))\n",
+ " closest_dark_exposure = dark_exposures[idx]\n",
+ "\n",
+ " if (tolerance is not None and \n",
+ " np.abs(image.header['exptime'] - closest_dark_exposure) > tolerance):\n",
+ " \n",
+ " raise RuntimeError('Closest dark exposure time is {} for flat of exposure '\n",
+ " 'time {}.'.format(closest_dark_exposure, image.header['exptime']))\n",
+ " \n",
+ " \n",
+ " return closest_dark_exposure"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "from astropy import units as u\n",
+ "from astropy.nddata import CCDData\n",
+ "import ccdproc as ccdp\n",
+ "from matplotlib import pyplot as plt\n",
+ "import numpy as np\n",
+ "\n",
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 1: No scaling of dark frames\n",
+ "\n",
+ "The images for this example are from chip 0 of the Large Format Camera at\n",
+ "Palomar Observatory. The raw images are [on Zenodo](https://doi.org/10.5281/zenodo.3254683), and this notebook assumes that\n",
+ "you have worked through the notebooks on bias and dark so that there is a folder\n",
+ "called `example1-reduced` in the same folder as this notebook.\n",
+ "\n",
+ "We'll go through this example twice: once with a single image to explain each\n",
+ "step, and then again to process all of the flat frames in the directory of raw data.\n",
+ "\n",
+ "An image collection is defined below, along with a couple of settings useful for\n",
+ "this example."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "reduced_path = Path('example1-reduced')\n",
+ "\n",
+ "ifc_reduced = ccdp.ImageFileCollection(reduced_path)\n",
+ "\n",
+ "combined_dark_files = ifc_reduced.files_filtered(imagetyp='dark', combined=True)\n",
+ "\n",
+ "flat_image_type = 'FLATFIELD'"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The raw data should be in the directory `example-cryo-LFC`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "raw_data = Path('example-cryo-LFC')\n",
+ "\n",
+ "ifc_raw = ccdp.ImageFileCollection(raw_data)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The cell below checks that the files needed are available."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n_combined_dark = len(combined_dark_files)\n",
+ "expected_exposure_times = set([7, 70, 300])\n",
+ "\n",
+ "if n_combined_dark < 3:\n",
+ " raise RuntimeError('One or more combined dark is missing. Please re-run the dark notebook.')\n",
+ "elif n_combined_dark > 3:\n",
+ " raise RuntimeError('There are more combined dark frames than expected.')\n",
+ " \n",
+ "actual_exposure_times = set(h['exptime'] for h in ifc_reduced.headers(imagetyp='dark', combined=True))\n",
+ "\n",
+ "if (expected_exposure_times - actual_exposure_times):\n",
+ " raise RuntimeError('Encountered unexpected exposure time in combined darks. '\n",
+ " 'The unexpected times are {}'.format(actual_exposure_times - expected_exposure_times))"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First, get one of the flat frames as a `CCDData` object and display it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "a_flat = CCDData.read(ifc_raw.files_filtered(imagetyp='flatfield', include_path=True)[0], unit='adu')\n",
+ "\n",
+ "show_image(a_flat, cmap='gray')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There is not a lot of variation in this. Note that the overscan region on the\n",
+ "right stands out as a black bar, and there is apparently also an overscan\n",
+ "region across the top of the chip. There appears to be a slight variation in\n",
+ "pixel values from the bottom to the top of the image."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Subtract overscan and trim, if necessary\n",
+ "\n",
+ "The overscan is useful for the LFC and needs to be subtracted and trimmed off.\n",
+ "See [this example in the dark reduction notebook](03-05-Calibrate-dark-images.ipynb#decide-which-calibration-steps-to-take) for a review of the overscan parameters.\n",
+ "The overscan region is the Python slice `[:, 2055:]` while the region to be\n",
+ "retained after trimming is the Python slice `[:, :2048]`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Subtract the overscan\n",
+ "a_flat_reduced = ccdp.subtract_overscan(a_flat, overscan=a_flat[:, 2055:], median=True)\n",
+ "\n",
+ "# Trim the overscan\n",
+ "a_flat_reduced = ccdp.trim_image(a_flat_reduced[:, :2048])\n",
+ "\n",
+ "# Display the result so far\n",
+ "show_image(a_flat_reduced, cmap='gray')\n",
+ "plt.title('Single flat frame, overscan subtracted and trimmed')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Trimming off the overscan makes such a big difference primarily because the\n",
+ "image stretch changed; the lowest pixel values are now around 18000 instead of \n",
+ "2000. With that change, the nonuniformity across the detector is much clearer."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Subtracting bias is not necessary in this example\n",
+ "\n",
+ "For this particular set of images there are darks with exposure time 7, 70, and\n",
+ "300 sec. The flat images have the exposure times listed below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "set(ifc_raw.summary['exptime'][ifc_raw.summary['imagetyp'] == 'FLATFIELD'])"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "These are close enough to the exposure time of the dark frames that there is no\n",
+ "need to scale the darks by exposure time. If the darks are not going to be\n",
+ "scaled then there is no need to subtract the bias."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Subtract dark current, no scaling necessary in this example\n",
+ "\n",
+ "We need to subtract the dark without scaling it. Rather than manually figuring\n",
+ "out which dark to subtract, we use the dark frame closest in exposure time to the\n",
+ "flat, within a tolerance of 1 second to ensure that we do not end up using a\n",
+ "dark *too* far off in exposure time from the flat.\n",
+ "\n",
+ "First, find the dark exposure time closest to the flat. We will need to do this\n",
+ "again later in the notebook, so we define a function to do it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "closest_dark = find_nearest_dark_exposure(a_flat_reduced, actual_exposure_times)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "It will be convenient to be able to access the darks via a dictionary whose key\n",
+ "is the exposure time, so we set that up below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combined_darks = {ccd.header['exptime']: ccd for ccd in ifc_reduced.ccds(imagetyp='dark', combined=True)}"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, we subtract the dark from the flat and display the result."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "a_flat_reduced = ccdp.subtract_dark(a_flat_reduced, combined_darks[closest_dark], \n",
+ " exposure_time='exptime', exposure_unit=u.second, scale=False)\n",
+ "show_image(a_flat_reduced, cmap='gray')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There is not much change here; that is not surprising since the dark current in\n",
+ "this camera is low."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Calibrate all of the flats in the folder"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The cell below calibrates each of the flats in the folder, automatically\n",
+ "grabbing the correct combined dark for each flat."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for ccd, file_name in ifc_raw.ccds(imagetyp='FLATFIELD', # Just get the bias frames\n",
+ " ccd_kwargs={'unit': 'adu'}, # CCDData requires a unit for the image if \n",
+ " # it is not in the header\n",
+ " return_fname=True # Provide the file name too.\n",
+ " ): \n",
+ " # Subtract the overscan\n",
+ " ccd = ccdp.subtract_overscan(ccd, overscan=ccd[:, 2055:], median=True)\n",
+ " \n",
+ " # Trim the overscan\n",
+ " ccd = ccdp.trim_image(ccd[:, :2048])\n",
+ " \n",
+ " # Find the correct dark exposure\n",
+ " closest_dark = find_nearest_dark_exposure(ccd, actual_exposure_times)\n",
+ " \n",
+ " # Subtract the dark current \n",
+ " ccd = ccdp.subtract_dark(ccd, combined_darks[closest_dark],\n",
+ " exposure_time='exptime', exposure_unit=u.second)\n",
+ "\n",
+ " # Save the result; there are some duplicate file names so pre-pend \"flat\"\n",
+ " ccd.write(reduced_path / ('flat-' + file_name))"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 2: Dark frames are scaled\n",
+ "\n",
+ "The images in this example, like in the previous notebooks, is a\n",
+ "thermoelectrically-cooled CCD described in more detail in the\n",
+ "[overscan notebook](01-08-Overscan.ipynb#case-2-thermo-electrically-cooled-apogee-aspen-cg16m).\n",
+ "\n",
+ "We'll go through this example twice: once with a single image to explain each\n",
+ "step, and then again to process all of the flat frames in the directory of raw data.\n",
+ "\n",
+ "An image collection is defined below, along with a couple of settings useful for\n",
+ "this example."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "reduced_path = Path('example2-reduced')\n",
+ "\n",
+ "ifc_reduced = ccdp.ImageFileCollection(reduced_path)\n",
+ "\n",
+ "combined_dark_files = ifc_reduced.files_filtered(imagetyp='dark', combined=True)\n",
+ "\n",
+ "flat_image_type = 'FLAT'"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The raw data should be in the directory `example-thermo-electric`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "raw_data = Path('example-thermo-electric')\n",
+ "\n",
+ "ifc_raw = ccdp.ImageFileCollection(raw_data)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The cell below checks that the files needed are available."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n_combined_dark = len(combined_dark_files)\n",
+ "\n",
+ "n_dark_expected = 1\n",
+ "expected_exposure_times = set([90])\n",
+ "\n",
+ "if n_combined_dark < n_dark_expected:\n",
+ " raise RuntimeError('One or more combined dark is missing. Please re-run the dark notebook.')\n",
+ "elif n_combined_dark > n_dark_expected:\n",
+ " raise RuntimeError('There are more combined dark frames than expected.')\n",
+ " \n",
+ "actual_exposure_times = set(h['exptime'] for h in ifc_reduced.headers(imagetyp='dark', combined=True))\n",
+ "\n",
+ "if (expected_exposure_times - actual_exposure_times):\n",
+ " raise RuntimeError('Encountered unexpected exposure time in combined darks. '\n",
+ " 'The unexpected times are {}'.format(actual_exposure_times - expected_exposure_times))"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First, get one of the flat frames as a `CCDData` object and display it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "a_flat = CCDData.read(ifc_raw.files_filtered(imagetyp='flat', include_path=True)[0], unit='adu')\n",
+ "\n",
+ "show_image(a_flat, cmap='gray')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There is a much different pattern of variation across the sensor in this case\n",
+ "than in Example 1. The multiple \"donuts\" in the image are pieces of dust and\n",
+ "there is significant vignetting (darkening) in the top and bottom corners of the\n",
+ "image on the right side."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Subtract overscan and trim: only trim for this camera\n",
+ "\n",
+ "The overscan is not useful for this camera. The region to be retained after\n",
+ "trimming is the Python slice `[:, :4096]`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Trim the overscan\n",
+ "a_flat_reduced = ccdp.trim_image(a_flat[:, :4096])\n",
+ "\n",
+ "# Display the result so far\n",
+ "show_image(a_flat_reduced, cmap='gray')\n",
+ "plt.title('Single flat frame, overscan trimmed')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Trimming off the overscan did not make a big difference primarily because the\n",
+ "overscan region of this camera is not useful. A useful overscan would have had\n",
+ "values around the bias level for this camera, about 1200 counts. The image\n",
+ "stretch did change a bit; prior to trimming the lower end of the color scale was\n",
+ "38000 and now it is 40000."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Subtracting bias is necessary\n",
+ "\n",
+ "For this particular set of images there is a combined dark with exposure time 90\n",
+ "sec. The flat images have the exposure times listed below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "set(ifc_raw.summary['exptime'][ifc_raw.summary['imagetyp'] == 'FLAT'])"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "These are quite different than the exposure time of the dark frames, so the dark\n",
+ "will need to be scaled by exposure time, which means that the bias has been\n",
+ "removed from the combined dark.\n",
+ "\n",
+ "Because of that, the bias needs to be removed from the flat before subtracting the\n",
+ "dark."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combined_bias = list(ifc_reduced.ccds(combined=True, imagetyp='bias'))[0]\n",
+ "a_flat_reduced = ccdp.subtract_bias(a_flat_reduced, combined_bias)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Display the result so far\n",
+ "show_image(a_flat_reduced, cmap='gray')\n",
+ "plt.title('Single flat frame, overscan trimmed and bias subtracted');"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Except for a change in the image scale shown on the color bar there isn't much\n",
+ "visually different after subtracting the bias."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Subtract dark current, scaling as needed\n",
+ "\n",
+ "Here we will need to scale the dark from the 90 sec exposure time of the dark\n",
+ "frame to the exposure time of each flat image. The [ccdproc function\n",
+ "`subtract_dark`](https://ccdproc.readthedocs.io/en/latest/api/ccdproc.subtract_dark.html#ccdproc.subtract_dark) provides keywords for doing this scaling automatically."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "closest_dark = find_nearest_dark_exposure(a_flat_reduced, actual_exposure_times, tolerance=100)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "It will be convenient to be able to access the darks via a dictionary whose key\n",
+ "is the exposure time, so we set that up below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combined_darks = {ccd.header['exptime']: ccd for ccd in ifc_reduced.ccds(imagetyp='dark', combined=True)}\n",
+ "combined_darks"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, we subtract the dark from the flat and display the result."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "a_flat_reduced = ccdp.subtract_dark(a_flat_reduced, combined_darks[closest_dark], \n",
+ " exposure_time='exptime', exposure_unit=u.second, scale=True)\n",
+ "show_image(a_flat_reduced, cmap='gray')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There is not much change here; that is not surprising since the dark current in\n",
+ "this camera is low."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Calibrate all of the flats in the folder"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The cell below calibrates each of the flats in the folder, automatically\n",
+ "grabbing the correct combined dark for each flat."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for ccd, file_name in ifc_raw.ccds(imagetyp='FLAT', # Just get the bias frames\n",
+ " return_fname=True # Provide the file name too.\n",
+ " ):\n",
+ " \n",
+ " # Trim the overscan\n",
+ " ccd = ccdp.trim_image(ccd[:, :4096])\n",
+ " \n",
+ " # Find the correct dark exposure\n",
+ " closest_dark = find_nearest_dark_exposure(ccd, actual_exposure_times, tolerance=100)\n",
+ " \n",
+ " # Subtract the dark current \n",
+ " ccd = ccdp.subtract_dark(ccd, combined_darks[closest_dark],\n",
+ " exposure_time='exptime', exposure_unit=u.second, scale=True)\n",
+ "\n",
+ " # Save the result\n",
+ " ccd.write(reduced_path / file_name)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.11"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": true,
+ "sideBar": true,
+ "skip_h1_title": true,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": true,
+ "toc_position": {},
+ "toc_section_display": true,
+ "toc_window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/05-04-Combining-flats.ipynb b/v/pdev/_sources/notebooks/05-04-Combining-flats.ipynb
new file mode 100644
index 00000000..1933f3cc
--- /dev/null
+++ b/v/pdev/_sources/notebooks/05-04-Combining-flats.ipynb
@@ -0,0 +1,423 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Combining flats\n",
+ "\n",
+ "There is one step in combining flats that is different from most other image\n",
+ "combination: the flats should be scaled to a common value before combining them.\n",
+ "This is particularly important if the flats are twilight flats in which the\n",
+ "average image value typically changes significantly as the images are being\n",
+ "taken.\n",
+ "\n",
+ "Flats are typically grouped by filter when combining them. That is, one combined\n",
+ "flat is produced for each filter in which flats were taken.\n",
+ "\n",
+ "Combination will be done for each of the two examples in the previous notebook."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "import ccdproc as ccdp\n",
+ "from astropy.stats import mad_std\n",
+ "from astropy.visualization import hist\n",
+ "\n",
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 1\n",
+ "\n",
+ "We begin by setting up an image collection for the reduced data. This data is\n",
+ "from chip 0 of the cryogenically-cooled Large Format Camera at Palomar\n",
+ "Observatory."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_path = Path('example1-reduced')\n",
+ "\n",
+ "flat_imagetyp = 'flatfield'\n",
+ "\n",
+ "ifc = ccdp.ImageFileCollection(calibrated_path)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We'll first check what filters are present."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flat_filters = set(h['filter'] for h in ifc.headers(imagetyp=flat_imagetyp))\n",
+ "flat_filters"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "These flats are dome flats, essentially pictures of a screen in the dome\n",
+ "illuminated by a light source, so you would not expect there to be much variable\n",
+ "in the typical pixel value between different exposures. There is typically\n",
+ "*some* variation, though, so we graph it below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "median_count = [np.median(data) for data in ifc.data(imagetyp=flat_imagetyp)]\n",
+ "mean_count = [np.mean(data) for data in ifc.data(imagetyp=flat_imagetyp)]\n",
+ "plt.plot(median_count, label='median')\n",
+ "plt.plot(mean_count, label='mean')\n",
+ "plt.xlabel('Image number')\n",
+ "plt.ylabel('Count (ADU)')\n",
+ "plt.title('Pixel value in calibrated flat frames')\n",
+ "plt.legend()\n",
+ "print(median_count)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Although this is less frame-to-frame variation than we will see in Example 2, it\n",
+ "is about 5%. If we were to combine these without scaling the flats to a common\n",
+ "value then the images with higher counts would effectively get more weight than\n",
+ "the images.\n",
+ "\n",
+ "There is a substantial difference between the mean and median of this data.\n",
+ "Typically it is better to use the median because extreme values do not affect\n",
+ "the median as much as the mean.\n",
+ "\n",
+ "To scale the frames so that they have the same median value, we need to define a\n",
+ "function that can calculate the inverse of the median given the data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def inv_median(a):\n",
+ " return 1 / np.median(a)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This function is passed into the `scale` argument of `combine` below. One\n",
+ "combined flat is created for each filter in the data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for filt in flat_filters:\n",
+ " to_combine = ifc.files_filtered(imagetyp=flat_imagetyp, filter=filt, include_path=True)\n",
+ " combined_flat = ccdp.combine(to_combine,\n",
+ " method='average', scale=inv_median,\n",
+ " sigma_clip=True, sigma_clip_low_thresh=5, sigma_clip_high_thresh=5,\n",
+ " sigma_clip_func=np.ma.median, signma_clip_dev_func=mad_std,\n",
+ " mem_limit=350e6\n",
+ " )\n",
+ "\n",
+ " combined_flat.meta['combined'] = True\n",
+ " dark_file_name = 'combined_flat_filter_{}.fit'.format(filt.replace(\"''\", \"p\"))\n",
+ " combined_flat.write(calibrated_path / dark_file_name)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Discussion of Example 1"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We will begin by checking that the right number of combined flats have been\n",
+ "created. There were two filters, `g'` and `i'` in the raw data so there should\n",
+ "be two combined flats. We need to refresh the `ImageFileCollection` for the\n",
+ "reduced data because new files, our flats, have been added to them."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ifc.refresh()\n",
+ "ifc.files_filtered(imagetyp=flat_imagetyp, combined=True)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "That looks good. The two flats are displayed below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 20), tight_layout=True)\n",
+ "\n",
+ "for ccd, axis in zip(ifc.ccds(imagetyp=flat_imagetyp, combined=True), axes):\n",
+ " show_image(ccd.data, cmap='gray', fig=fig, ax=axis)\n",
+ " title = \"Filter {}\".format(ccd.header['filter'])\n",
+ " axis.set_title(title)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The first thing to notice is that the flats are different in these two filters.\n",
+ "That is expected because one of the elements in the optical path, the filter, is\n",
+ "different.\n",
+ "\n",
+ "The pattern of electronics in the flat images is because this is a\n",
+ "backside-illuminated CCD. The light-detecting pixels are on the under side of\n",
+ "the chip and the light needs to pass through the chip to reach the sensor. The\n",
+ "small dark spots are places where the chip wasn't thinned uniformly.\n",
+ "\n",
+ "Compare this with [Example 2](#discussion-of-example-2) below, which shows a flat\n",
+ "taken with a frontside-illuminated camera."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 2\n",
+ "\n",
+ "The data in this example is from a thermoelectrically-cooled Andor Aspen CG16M.\n",
+ "These flats are twilight flats, taken just after sunset."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "calibrated_path = Path('example2-reduced')\n",
+ "\n",
+ "flat_imagetyp = 'flat'\n",
+ "\n",
+ "ifc = ccdp.ImageFileCollection(calibrated_path)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flat_filters = set(h['filter'] for h in ifc.headers(imagetyp=flat_imagetyp))\n",
+ "flat_filters"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Twilight flats typically differ more frame-to-frame than dome flats, as\n",
+ "shown in the figure below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "median_count = [np.median(hdu.data) for hdu in ifc.hdus(imagetyp=flat_imagetyp)]\n",
+ "mean_count = [np.mean(data) for data in ifc.data(imagetyp=flat_imagetyp)]\n",
+ "plt.plot(median_count, label='median')\n",
+ "plt.plot(mean_count, label='mean')\n",
+ "plt.xlabel('Image number')\n",
+ "plt.ylabel('Count (ADU)')\n",
+ "plt.title('Pixel value in calibrated flat frames')\n",
+ "plt.legend()\n",
+ "print(median_count)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def inv_median(a):\n",
+ " return 1 / np.median(a)\n",
+ "\n",
+ "for filt in flat_filters:\n",
+ " to_combine = ifc.files_filtered(imagetyp=flat_imagetyp, filter=filt, include_path=True)\n",
+ " combined_flat = ccdp.combine(to_combine,\n",
+ " method='average', scale=inv_median,\n",
+ " sigma_clip=True, sigma_clip_low_thresh=5, sigma_clip_high_thresh=5,\n",
+ " sigma_clip_func=np.ma.median, signma_clip_dev_func=mad_std,\n",
+ " mem_limit=350e6\n",
+ " )\n",
+ "\n",
+ " combined_flat.meta['combined'] = True\n",
+ " dark_file_name = 'combined_flat_filter_{}.fit'.format(filt.replace(\"''\", \"p\"))\n",
+ " combined_flat.write(calibrated_path / dark_file_name)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Discussion of Example 2"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We expect only one combined flat because there was only one filter. The\n",
+ "`ImageFileCollection` is refreshed before we query it because the combined flats\n",
+ "were added after the collection was created."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ifc.refresh()\n",
+ "ifc.files_filtered(imagetyp=flat_imagetyp, combined=True)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The combined flat is displayed below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_image(combined_flat, cmap='gray', figsize=(10, 20))"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This flat looks very different than the one in [Example 1](#discussion-of-example-1)\n",
+ "because this CCD is frontside-illuminated and the previous one is backside-illuminated. \n",
+ "That means the sensor is on the top of the chip and the light does\n",
+ "not pass through the sensor chip itself to reach the sensors. Though only one\n",
+ "filter is shown here, the flat field looks slightly different through other\n",
+ "filters on this camera."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.11"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": true,
+ "sideBar": true,
+ "skip_h1_title": true,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": true,
+ "toc_position": {},
+ "toc_section_display": true,
+ "toc_window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/06-00-Reducing-science-images.ipynb b/v/pdev/_sources/notebooks/06-00-Reducing-science-images.ipynb
new file mode 100644
index 00000000..d41d2963
--- /dev/null
+++ b/v/pdev/_sources/notebooks/06-00-Reducing-science-images.ipynb
@@ -0,0 +1,39 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Reducing science images\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The material in the preceeding chapters was building towards calibrating science images. The combined bias, dark and flat images from the previous steps are used for calibrating the images of whatever object of interest is in the science images. "
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/06-03-science-images-calibration-examples.ipynb b/v/pdev/_sources/notebooks/06-03-science-images-calibration-examples.ipynb
new file mode 100644
index 00000000..6fe0c513
--- /dev/null
+++ b/v/pdev/_sources/notebooks/06-03-science-images-calibration-examples.ipynb
@@ -0,0 +1,471 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Two science image calibration examples\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "from matplotlib import pyplot as plt\n",
+ "import numpy as np\n",
+ "\n",
+ "from astropy import units as u\n",
+ "import ccdproc as ccdp\n",
+ "\n",
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Overview\n",
+ "\n",
+ "[ccdproc](https://ccdproc.readthedocs.io) provides a couple of ways to approach\n",
+ "calibration of the science images:\n",
+ "\n",
+ "+ Perform each of the individual steps manually using `subtract_bias`,\n",
+ "`subtract_dark`, and `flat_correct`.\n",
+ "+ Use the [`ccd_process` function](https://ccdproc.readthedocs.io/en/latest/api/ccdproc.ccd_process.html#ccdproc.ccd_process) to perform all of the reduction steps.\n",
+ "\n",
+ "This notebook will do each of these in separate sections below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def find_nearest_dark_exposure(image, dark_exposure_times, tolerance=0.5):\n",
+ " \"\"\"\n",
+ " Find the nearest exposure time of a dark frame to the exposure time of the image,\n",
+ " raising an error if the difference in exposure time is more than tolerance.\n",
+ " \n",
+ " Parameters\n",
+ " ----------\n",
+ " \n",
+ " image : astropy.nddata.CCDData\n",
+ " Image for which a matching dark is needed.\n",
+ " \n",
+ " dark_exposure_times : list\n",
+ " Exposure times for which there are darks.\n",
+ " \n",
+ " tolerance : float or ``None``, optional\n",
+ " Maximum difference, in seconds, between the image and the closest dark. Set\n",
+ " to ``None`` to skip the tolerance test.\n",
+ " \n",
+ " Returns\n",
+ " -------\n",
+ " \n",
+ " float\n",
+ " Closest dark exposure time to the image.\n",
+ " \"\"\"\n",
+ "\n",
+ " dark_exposures = np.array(list(dark_exposure_times))\n",
+ " idx = np.argmin(np.abs(dark_exposures - image.header['exptime']))\n",
+ " closest_dark_exposure = dark_exposures[idx]\n",
+ "\n",
+ " if (tolerance is not None and \n",
+ " np.abs(image.header['exptime'] - closest_dark_exposure) > tolerance):\n",
+ " \n",
+ " raise RuntimeError('Closest dark exposure time is {} for flat of exposure '\n",
+ " 'time {}.'.format(closest_dark_exposure, a_flat.header['exptime']))\n",
+ " \n",
+ " \n",
+ " return closest_dark_exposure"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 1\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The data in this example is from chip 0 of the Large Format Camera at Palomar\n",
+ "Observatory. In earlier notebooks we determined that:\n",
+ "\n",
+ "+ This CCD has a [useful overscan region](01-08-Overscan.ipynb#Case-1:-Cryogenically-cooled-Large-Format-Camera-(LFC)-at-Palomar).\n",
+ "+ There is [no need to scale dark frames](05-03-Calibrating-the-flats.ipynb#Subtracting-bias-is-not-necessary-in-this-example) in this data, and so no need to subtract bias.\n",
+ "\n",
+ "First, we create [ImageFileCollection](https://ccdproc.readthedocs.io/en/latest/ccdproc/image_management.html)s for the raw data and the calibrated\n",
+ "data, and define variables for the image type of the flat field and the science\n",
+ "images (there are, unfortunately, no standard names for these)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "reduced_path = Path('example1-reduced')\n",
+ "\n",
+ "science_imagetyp = 'object'\n",
+ "flat_imagetyp = 'flatfield'\n",
+ "exposure = 'exptime'\n",
+ "\n",
+ "ifc_reduced = ccdp.ImageFileCollection(reduced_path)\n",
+ "ifc_raw = ccdp.ImageFileCollection('example-cryo-LFC')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, a quick look at the science images in this data set."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "lights = ifc_raw.summary[ifc_raw.summary['imagetyp'] == science_imagetyp.upper()]\n",
+ "lights['date-obs', 'file', 'object', 'filter', exposure]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, a look at the calibrated combined images available to us."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combo_calibs = ifc_reduced.summary[ifc_reduced.summary['combined'].filled(False).astype('bool')]\n",
+ "combo_calibs['date-obs', 'file', 'imagetyp', 'object', 'filter', exposure]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Both of these images will use the 300 second dark exposure to remove dark\n",
+ "current and there is a flat field image for each of the two filters present in\n",
+ "the data.\n",
+ "\n",
+ "Though we could hard code which dark to use, it is possible to set up a\n",
+ "dictionary for accessing the darks, with a separate one for accessing the science\n",
+ "images. The goal is to write code that is more conveniently reused."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combined_darks = {ccd.header[exposure]: ccd for ccd in ifc_reduced.ccds(imagetyp='dark', combined=True)}\n",
+ "combined_flats = {ccd.header['filter']: ccd for ccd in ifc_reduced.ccds(imagetyp=flat_imagetyp, combined=True)}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The calibration steps for each of these science images are:\n",
+ "\n",
+ "+ Subtract the overscan from the science image.\n",
+ "+ Trim the overscan.\n",
+ "+ Subtract the dark current (which in this case also includes the bias).\n",
+ "+ Flat correct the image.\n",
+ "\n",
+ "These are the minimum steps needed to calibrate. Gain correcting the data and\n",
+ "identifying cosmic rays are additional steps that might often be done at this\n",
+ "stage. Identification of cosmic rays will be discussed in [this notebook](08-03-Cosmic-ray-removal.ipynb)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# These two lists are created so that we have copies of the raw and calibrated images\n",
+ "# to later in the notebook. They are not ordinarily required.\n",
+ "all_reds = []\n",
+ "light_ccds = []\n",
+ "for light, file_name in ifc_raw.ccds(imagetyp=science_imagetyp, return_fname=True, ccd_kwargs=dict(unit='adu')):\n",
+ " light_ccds.append(light)\n",
+ " \n",
+ " reduced = ccdp.subtract_overscan(light, overscan=light[:, 2055:], median=True)\n",
+ " \n",
+ " reduced = ccdp.trim_image(reduced[:, :2048])\n",
+ "\n",
+ " closest_dark = find_nearest_dark_exposure(reduced, combined_darks.keys())\n",
+ " reduced = ccdp.subtract_dark(reduced, combined_darks[closest_dark],\n",
+ " exposure_time=exposure, exposure_unit=u.second\n",
+ " )\n",
+ " good_flat = combined_flats[reduced.header['filter']]\n",
+ " reduced = ccdp.flat_correct(reduced, good_flat)\n",
+ " all_reds.append(reduced)\n",
+ " reduced.write(reduced_path / file_name)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Before and after for Example 1\n",
+ "\n",
+ "The cell below displays each science image before and after calibration."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, axes = plt.subplots(2, 2, figsize=(10, 20), tight_layout=True)\n",
+ "\n",
+ "for row, raw_science_image in enumerate(light_ccds):\n",
+ " filt = raw_science_image.header['filter']\n",
+ " axes[row, 0].set_title('Uncalibrated image {}'.format(filt))\n",
+ " show_image(raw_science_image, cmap='gray', ax=axes[row, 0], fig=fig, percl=90)\n",
+ "\n",
+ " axes[row, 1].set_title('Calibrated image {}'.format(filt))\n",
+ " show_image(all_reds[row].data, cmap='gray', ax=axes[row, 1], fig=fig, percl=90)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In each reduced image the overall pattern in the flat field has been removed,\n",
+ "and the sensor glow along the left edge and in the bottom left corner of the\n",
+ "uncalibrated images has been removed. The background in the calibrated images is\n",
+ "still not perfectly uniform, though that background can be removed using\n",
+ "photutils if desired. The partial bad column visible in both uncalibrated images\n",
+ "is also still present in the calibrated images, particularly in the `g'` filter.\n",
+ "\n",
+ "Those pixels will need to be masked, as described in the [notebook on bad\n",
+ "pixels](08-01-Identifying-hot-pixels.ipynb)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The camera in this example is a thermoelectrically-cooled Andor Aspen CG16M. In\n",
+ "previous notebooks we decided that:\n",
+ "\n",
+ "+ The overscan region of this camera is not useful.\n",
+ "+ For this data set the dark frames needed to be scaled, so we need to subtract\n",
+ "the bias frame from each science image.\n",
+ "\n",
+ "First, we create [ImageFileCollection](https://ccdproc.readthedocs.io/en/latest/ccdproc/image_management.html)s for the raw data and the\n",
+ "calibrated data, and define variables for the image type of the flat field and\n",
+ "the science images (there are, unfortunately, no standard names for these)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "reduced_path = Path('example2-reduced')\n",
+ "\n",
+ "science_imagetyp = 'light'\n",
+ "flat_imagetyp = 'flat'\n",
+ "exposure = 'exposure'\n",
+ "\n",
+ "ifc_reduced = ccdp.ImageFileCollection(reduced_path)\n",
+ "ifc_raw = ccdp.ImageFileCollection('example-thermo-electric')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, we check what science images are available."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "lights = ifc_raw.summary[ifc_raw.summary['imagetyp'] == science_imagetyp.upper()]\n",
+ "lights['date-obs', 'file', 'object', 'filter', exposure]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There are only two science images, both in the same filter and with the same\n",
+ "exposure time.\n",
+ "\n",
+ "The combined calibration images available are listed below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combo_calibs = ifc_reduced.summary[ifc_reduced.summary['combined'].filled(False).astype('bool')]\n",
+ "combo_calibs['date-obs', 'file', 'imagetyp', 'filter', exposure]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Although there is only one of each type of combined calibration image, the code\n",
+ "below sets up a dictionary for the flats and darks. That code will work on a\n",
+ "more typical night when there might be several filters and multiple exposure\n",
+ "times."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combined_darks = {ccd.header[exposure]: ccd for ccd in ifc_reduced.ccds(imagetyp='dark', combined=True)}\n",
+ "combined_flats = {ccd.header['filter']: ccd for ccd in ifc_reduced.ccds(imagetyp=flat_imagetyp, combined=True)}\n",
+ "\n",
+ "# There is only on bias, so no need to set up a dictionary.\n",
+ "combined_bias = [ccd for ccd in ifc_reduced.ccds(imagetyp='bias', combined=True)][0]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The calibration steps for each of these science images is:\n",
+ "\n",
+ "+ Trim the overscan.\n",
+ "+ Subtract bias, because bias was subtracted from the dark frames.\n",
+ "+ Subtract dark, using the dark closest in exposure time to the science image.\n",
+ "+ Flat correct the images, using the combined flat whose filter matches the\n",
+ "science image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "all_reds = []\n",
+ "light_ccds = []\n",
+ "for light, file_name in ifc_raw.ccds(imagetyp=science_imagetyp, return_fname=True):\n",
+ " light_ccds.append(light)\n",
+ "\n",
+ " reduced = ccdp.trim_image(light[:, :4096])\n",
+ " \n",
+ " # Note that the first argument in the remainder of the ccdproc calls is\n",
+ " # the *reduced* image, so that the calibration steps are cumulative.\n",
+ " reduced = ccdp.subtract_bias(reduced, combined_bias)\n",
+ " \n",
+ " closest_dark = find_nearest_dark_exposure(reduced, combined_darks.keys())\n",
+ "\n",
+ " reduced = ccdp.subtract_dark(reduced, combined_darks[closest_dark], \n",
+ " exposure_time='exptime', exposure_unit=u.second)\n",
+ " \n",
+ " good_flat = combined_flats[reduced.header['filter']]\n",
+ " reduced = ccdp.flat_correct(reduced, good_flat)\n",
+ " all_reds.append(reduced)\n",
+ " reduced.write(reduced_path / file_name)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, axes = plt.subplots(2, 2, figsize=(10, 10), tight_layout=True)\n",
+ "\n",
+ "for row, raw_science_image in enumerate(light_ccds):\n",
+ " filt = raw_science_image.header['filter']\n",
+ " axes[row, 0].set_title('Uncalibrated image {}'.format(filt))\n",
+ " show_image(raw_science_image, cmap='gray', ax=axes[row, 0], fig=fig, percl=90)\n",
+ "\n",
+ " axes[row, 1].set_title('Calibrated image {}'.format(filt))\n",
+ " show_image(all_reds[row].data, cmap='gray', ax=axes[row, 1], fig=fig, percl=90)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In both images, most of the nonunifomity in the background has been removed along\n",
+ "the bright column and the sensor glow on the left and right edges of the\n",
+ "uncalibrated images.\n",
+ "\n",
+ "The calibrated image in the top row looks like flat correction has led to a\n",
+ "fairly uniform background. The background in the image in the second row is not\n",
+ "that uniform. As it turns out the second image was taken much closer to dawn\n",
+ "than the first image, so the sky background really was not uniform when the\n",
+ "image was taken."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/07-00-Combining-images.ipynb b/v/pdev/_sources/notebooks/07-00-Combining-images.ipynb
new file mode 100644
index 00000000..e886818d
--- /dev/null
+++ b/v/pdev/_sources/notebooks/07-00-Combining-images.ipynb
@@ -0,0 +1,43 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Combining images\n",
+ "\n",
+ "An overview of combining images is in [Image combination](01-06-Image-combination.ipynb). The next\n",
+ "sections discuss a couple of common cases not covered in the earlier section:\n",
+ "\n",
+ "+ Combining science images without aligning them to generate *sky flats*. These\n",
+ "flats have the advantage that the light source exactly matches the spectrum of\n",
+ "the night sky. They have the big disadvantage that the sky counts are typically\n",
+ "very low so many, many images need to be combined to produce a reasonably low\n",
+ "noise flat frame.\n",
+ "+ Combining science images by aligning them using WCS information if it is\n",
+ "present in the headers of the images."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/07-01-Creating-a-sky-flat.ipynb b/v/pdev/_sources/notebooks/07-01-Creating-a-sky-flat.ipynb
new file mode 100644
index 00000000..1f226ef8
--- /dev/null
+++ b/v/pdev/_sources/notebooks/07-01-Creating-a-sky-flat.ipynb
@@ -0,0 +1,334 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Creating a sky flat\n",
+ "\n",
+ "One way of producing flat field images is to use the science images, combined in\n",
+ "a way that eliminates astronomical sources. This provides an exact match to the\n",
+ "spectrum of the night sky, since the night sky is the source of light. However,\n",
+ "the night sky is dark, so the counts in individual images is low. Many images\n",
+ "must be combined to generate a flat with low noise."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "import numpy as np\n",
+ "\n",
+ "from astropy.nddata import CCDData\n",
+ "from astropy import units as u\n",
+ "from astropy.stats import mad_std\n",
+ "\n",
+ "from photutils import detect_threshold, detect_sources, source_properties\n",
+ "\n",
+ "import ccdproc as ccdp"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## When is it impossible to produce a sky flat?\n",
+ "\n",
+ "There are a few circumstances in which producing a sky flat is difficult or\n",
+ "impossible:\n",
+ "\n",
+ "+ The telescope tracks very well so stars and other sources are always in\n",
+ "roughly the same pixels in all of the images. In this case, there is no way to\n",
+ "produce a good flat. If several fields of view are observed this should not be\n",
+ "an issue.\n",
+ "+ There is an extended source that covers an appreciable fraction of the field\n",
+ "of view. In this case there is likely to be overlap of the extended object\n",
+ "between images, so it cannot be removed from the flat.\n",
+ "+ The sky is really dark. In very dark sites the sky background might be low\n",
+ "enough that the sky flat is too noisy to be useful."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Producing a sky flat\n",
+ "\n",
+ "Producing a sky flat is much like producing any other flat. The images must have\n",
+ "bias and dark current subtracted (and overscan if it is being used) then\n",
+ "combined, rescaling each image to take into account different levels of\n",
+ "background illumination.\n",
+ "\n",
+ "It is important to scale the *median* of each image to the same value instead of\n",
+ "scaling the *mean* because the presence of bright sources will affect the mean\n",
+ "much more than the median.\n",
+ "\n",
+ "One down side of producing sky flats is the need to process the science images\n",
+ "twice. The first time all of the usual calibration steps except flat fielding\n",
+ "are done, then the flats are produced, then each science image is flat\n",
+ "corrected."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Partially calibrate science images\n",
+ "\n",
+ "The partially reduced images are saved in a different folder than the completely\n",
+ "reduced science images that were processed earlier.\n",
+ "\n",
+ "The images for this example were taken the same night as the other images in\n",
+ "\"Example 2\" in earlier notebooks.\n",
+ "\n",
+ "First, we set up some of the locations we will need."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex2_calibrated = Path('example2-reduced')\n",
+ "\n",
+ "sky_flat_bad_raw = Path('sky_flat_good_raw')\n",
+ "\n",
+ "sky_flat_bad_working = Path('sky_flat_good_working')\n",
+ "sky_flat_bad_working.mkdir(exist_ok=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, load the combined bias and combined dark for this night. Recall that the\n",
+ "combined dark for this night was bias-subtracted because it needed to be scaled\n",
+ "for the flat images (see [this notebook](03.05-Calibrate-dark-images.ipynb#Example-2:-Overscan-not-subtracted,-bias-is-removed) for more detail).\n",
+ "\n",
+ "All of the science exposures this night had the same exposure time, 90 sec."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combined_bias = CCDData.read(ex2_calibrated / 'combined_bias.fit')\n",
+ "combined_dark = CCDData.read(ex2_calibrated / 'combined_dark_90.000.fit')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The telescope tracking changed during this night. Tracking was excellent for\n",
+ "observations of Kelt 16b, making the images terrible for sky flats, but\n",
+ "excellent for illustrating the failure of sky flats under some circumstances."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": false
+ },
+ "outputs": [],
+ "source": [
+ "ifc_raw = ccdp.ImageFileCollection(sky_flat_bad_raw)\n",
+ "\n",
+ "for ccd, name in ifc_raw.ccds(imagetyp='light', object='wasp 10 b', filter=\"r\", return_fname=True):\n",
+ " reduced = ccdp.trim_image(ccd[:, :4096])\n",
+ " reduced = ccdp.subtract_bias(reduced, combined_bias)\n",
+ " reduced = ccdp.subtract_dark(reduced, combined_dark, exposure_time='exposure', exposure_unit=u.second)\n",
+ " thresh = detect_threshold(an_im, 2)\n",
+ " segm = detect_sources(an_im, thresh, 30)\n",
+ " reduced.data[segm.data > 0] = np.nan\n",
+ " reduced.data = reduced.data.astype('float32')\n",
+ " reduced.write(sky_flat_bad_working / name)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Combine the partially calibrated images\n",
+ "\n",
+ "The combination settings here are important. Either combine by averaging and\n",
+ "sigma clip or combine by median. Either should ensure that stars do not show up\n",
+ "in your final flat as long as there is enough offset between the images. Images\n",
+ "need to be scaled so that the median is the same for each image. Typically, a\n",
+ "value of one is chosen as the common value."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ifc_working = ccdp.ImageFileCollection(sky_flat_bad_working)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "to_combine = [ccd for ccd in ifc_working.ccds()]\n",
+ "\n",
+ "def inv_median(array):\n",
+ " return 1 / np.nanmedian(array)\n",
+ "\n",
+ "sky_flat = ccdp.combine(to_combine, scale=inv_median, \n",
+ " sigma_clip=True, sigma_clip_low_thresh=3, sigma_clip_high_thresh=3,\n",
+ " sigma_clip_func=np.nanmedian, sigma_clip_dev_func=mad_std, \n",
+ " mem_limit=2e9\n",
+ " )\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from convenience_functions import show_image"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "an_im = CCDData.read(sky_flat_bad_working / 'wasp-10-b-S001-R001-C050-r.fit')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_image(an_im, cmap='gray')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from photutils import detect_threshold, detect_sources, source_properties"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "foo = detect_threshold(an_im, 2)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "arf = detect_sources(an_im, foo, 30)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_image(arf.data > 0, cmap='gray', is_mask=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "(arf.data > 0).sum()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "moo = source_properties(an_im.data, arf)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "moo.to_table()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_image(sky_flat, cmap='gray')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "sky_flat.write('supposed_to_be_good_but_has_streaks.fits')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/07-02-Combination-with-alignment-via-WCS.ipynb b/v/pdev/_sources/notebooks/07-02-Combination-with-alignment-via-WCS.ipynb
new file mode 100644
index 00000000..5e85eef9
--- /dev/null
+++ b/v/pdev/_sources/notebooks/07-02-Combination-with-alignment-via-WCS.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Combination with alignment via WCS\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/07-03-Combination-with-alignment-based-on-star-positions-in-the-image.ipynb b/v/pdev/_sources/notebooks/07-03-Combination-with-alignment-based-on-star-positions-in-the-image.ipynb
new file mode 100644
index 00000000..3efef64a
--- /dev/null
+++ b/v/pdev/_sources/notebooks/07-03-Combination-with-alignment-based-on-star-positions-in-the-image.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Combination with alignment based on star positions in the image\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/08-00-Image-masking.ipynb b/v/pdev/_sources/notebooks/08-00-Image-masking.ipynb
new file mode 100644
index 00000000..31a9d26d
--- /dev/null
+++ b/v/pdev/_sources/notebooks/08-00-Image-masking.ipynb
@@ -0,0 +1,44 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Image masking\n",
+ "\n",
+ "There are several reasons a particular pixel in a specific image might not be\n",
+ "useful:\n",
+ "\n",
+ "+ The pixel could be \"hot,\" meaning it isn't possible to remove the dark current\n",
+ "from the pixel.\n",
+ "+ The pixel could be bad in the sense that it does not respond to light the way\n",
+ "the other pixels do.\n",
+ "+ A cosmic ray can hit the pixel during the imaging.\n",
+ "\n",
+ "The following notebooks walk through identifying each of those types of bad\n",
+ "pixels and how to create a mask for them."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/08-01-Identifying-hot-pixels.ipynb b/v/pdev/_sources/notebooks/08-01-Identifying-hot-pixels.ipynb
new file mode 100644
index 00000000..d79468ba
--- /dev/null
+++ b/v/pdev/_sources/notebooks/08-01-Identifying-hot-pixels.ipynb
@@ -0,0 +1,319 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Identifying hot pixels\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Some pixels are too hot\n",
+ "\n",
+ "Recall from the [notebook about dark current](03-02-Real-dark-current-noise-and-other-artifacts.ipynb) that even a\n",
+ "cryogenically-cooled camera with low dark current has some pixels with much\n",
+ "higher dark current. In the [discussion of \"ideal\" dark current](03-01-Dark-current-The-ideal-case.ipynb) we noted that the\n",
+ "counts in a dark image should be proportional to the exposure time.\n",
+ "\n",
+ "For some hot pixels that is not the case, unfortunately, which means that those\n",
+ "pixels cannot conveniently be corrected by subtracting a combined dark. Those pixels\n",
+ "can be identified by taking darks with two different (but long) exposure times\n",
+ "and comparing the dark current derived from each of the images. The dark\n",
+ "current, measured in electron/sec, should be the same in both images if the dark\n",
+ "current is really constant.\n",
+ "\n",
+ "Fortunately, the pixels whose response is not proportional to exposure time are\n",
+ "usually also pixels with high dark current. It is possible to identify\n",
+ "those pixels and create a mask to exclude them when processing images. If this\n",
+ "weren't the case it might be necessary to take some dark frames with much longer\n",
+ "exposure times than otherwise needed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "from matplotlib import pyplot as plt\n",
+ "import numpy as np\n",
+ "\n",
+ "from astropy.visualization import hist\n",
+ "from astropy import units as u\n",
+ "from astropy.nddata import CCDData\n",
+ "\n",
+ "import ccdproc as ccdp"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example\n",
+ "\n",
+ "There are two combined dark images available for the thermoelectrically-cooled\n",
+ "Andor Aspen CG16M discussed as \"Example 2\" in previous notebooks. One is an\n",
+ "average of ten 90 second exposures taken during observations of the transiting\n",
+ "exoplanet KELT-16 b. The other is an average of twenty 1,000 second exposures\n",
+ "taken during commission of the camera. Typically you will not have a single dark\n",
+ "of with an exposure time that long, let alone several of them."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We begin by reading each combined dark and calculating the dark current from the\n",
+ "counts in the image using\n",
+ "\n",
+ "$$\n",
+ "\\text{dark current} = \\text{gain} \\times \\text{dark counts}~/ \\text{ exposure time }.\n",
+ "$$\n",
+ "\n",
+ "The gain for this camera is 1.5 $e^-$/adu. The 1,000 second exposure also needs\n",
+ "to be trimmed to remove the overscan region."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex2_path = Path('example2-reduced')\n",
+ "\n",
+ "dark_90 = CCDData.read(ex2_path / 'combined_dark_90.000.fit')\n",
+ "dark_1000 = CCDData.read('combined_dark_exposure_1000.0.fit.bz2')\n",
+ "dark_1000 = ccdp.trim_image(dark_1000[:, :4096])\n",
+ "\n",
+ "dark_90 = dark_90.multiply(1.5 * u.electron / u.adu).divide(90 * u.second)\n",
+ "dark_1000 = dark_1000.multiply(1.5 * u.electron / u.adu).divide(1000 * u.second)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The histogram below shows the distribution of dark current values in each image.\n",
+ "There are some differences we should *expect* to see between the two images.\n",
+ "\n",
+ "Small values of dark current are much more accurately measured in the long\n",
+ "exposure. The exposure time in that image was chosen to be as short as possible\n",
+ "while still measuring the nominal dark current of 0.01 $e^-$/sec from the\n",
+ "manufacturer given that the camera's read noise is 10$e^-$.\n",
+ "\n",
+ "For the average of ten 90 second exposures, that read noise will be reduced to\n",
+ "10$e^-/\\sqrt{10} \\approx$3.2$e^-$. After dividng by exposure time, this is\n",
+ "equivalent to a \"dark current\" of 0.035$e^-$/sec. Roughly twice that is the\n",
+ "smallest dark current that can be accurately measured in the 90 second dark.\n",
+ "\n",
+ "For large values of dark current, the shorter exposure is more accurate. Some of\n",
+ "the pixels saturate (i.e., reach the maximum value the chip can read out, roughly\n",
+ "65,000) in under 90 sec and more of them saturate at some time between 90\n",
+ "seconds and 1,000 seconds. None of those pixels are accurately measured by the\n",
+ "long 1,000 second exposure time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(20, 10))\n",
+ "\n",
+ "hist(dark_90.data.flatten(), bins=5000, density=False, label='90 sec dark', alpha=0.4)\n",
+ "hist(dark_1000.data.flatten(), bins=5000, density=False, label='1000 sec dark', alpha=0.4)\n",
+ "plt.xlabel('dark current, $e^-$/sec')\n",
+ "plt.ylabel('Number of pixels')\n",
+ "plt.loglog()\n",
+ "plt.legend();"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Overall, there appear to be more hot pixels in the 90 sec exposure than in the\n",
+ "1,000 sec exposure. For dark current under 0.1 $e^-$/sec, that is certainly\n",
+ "affected by the read noise in the 90 sec exposure.\n",
+ "\n",
+ "To get a better idea of how consistent the dark current measurements are, we\n",
+ "construct a scatter plot with the measured dark current from each image for\n",
+ "those pixels in which the dark current is larger than 1$e^-$/sec as measured in\n",
+ "the longer exposure."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "hot_pixels = (dark_1000.data > 1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(10, 10))\n",
+ "plt.plot(dark_90.data[hot_pixels].flatten(), dark_1000.data[hot_pixels].flatten(), '.', alpha=0.2, label='Data')\n",
+ "plt.xlabel(\"dark current ($e^-$/sec), 90 sec exposure time\")\n",
+ "plt.ylabel(\"dark current ($e^-$/sec), 1000 sec exposure time\")\n",
+ "plt.plot([0, 100], [0, 100], label='Ideal relationship')\n",
+ "plt.grid()\n",
+ "plt.legend();"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The upper limit on dark current that can be measured with the long exposure time\n",
+ "can be clearly seen in the plot above; there is a ceiling at roughly 95$e^-$/sec\n",
+ "above which the dark current in the long exposure does not go.\n",
+ "\n",
+ "It looks like the dark current as measured in each frame is equal for lower\n",
+ "values of the dark current, so we replot to get a better look at that region."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(10, 10))\n",
+ "plt.plot(dark_90.data[hot_pixels].flatten(), dark_1000.data[hot_pixels].flatten(), '.', alpha=0.2, label='Data')\n",
+ "plt.xlabel(\"dark current ($e^-$/sec), 90 sec exposure time\")\n",
+ "plt.ylabel(\"dark current ($e^-$/sec), 1000 sec exposure time\")\n",
+ "plt.plot([0, 100], [0, 100], label='Ideal relationship')\n",
+ "plt.grid()\n",
+ "plt.xlim(0.5, 10)\n",
+ "plt.ylim(0.5, 10)\n",
+ "plt.legend();"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Here it looks like the measured dark currents are consistent until around\n",
+ "4$e^-$/sec. Beyond that the dark current is consistently larger in the short\n",
+ "exposure than in the long exposure.\n",
+ "\n",
+ "Because of this, we will mark all pixels with dark current larger than\n",
+ "4$e^-$/sec as bad. Below that the dark current seems to be measured consistently\n",
+ "in both exposures so it ought to be possible to remove the dark current by\n",
+ "subtracting a combined dark frame."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bad_hot_pixels = (dark_90.data > 4)\n",
+ "bad_hot_pixels.sum()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This amounts to 4302 pixels, or 0.026% of the pixels in the camera."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Rule of thumb\n",
+ "\n",
+ "The example in this notebook is a little unusual in that a very long exposure\n",
+ "dark is available. In deciding what pixels to mask because of high dark current\n",
+ "you have a few options:\n",
+ "\n",
+ "+ Use whatever exposure time range you have. The images in \"Example 1\" from the\n",
+ "previous notebook have darks with exposure times 7, 70, and 300 seconds. The 7\n",
+ "second dark is primarily measuring read noise, so is not likely to be useful.\n",
+ "The current in the 70 second and 300 second darks could be compared, though, as\n",
+ "it was in this case.\n",
+ "+ If that is not an option, as was the case with the data in \"Example 2,\" then you\n",
+ "may need to pick a cutoff. Keep in mind that the typical camera has very, very\n",
+ "low dark current, so a limit like 1$e^-$/sec is not unreasonable.\n",
+ "+ Ask other users of the instrument what they do. Large observatories may\n",
+ "provide a mask image of bad pixels for the camera you are using.\n",
+ "+ Do not do any masking. This is a more reasonable option than you might think.\n",
+ "Most pixels are measuring light from the nighttime sky. If one of these hot\n",
+ "pixels happens to be in that background, and outlying pixels are rejected by a\n",
+ "method like sigma clipping, then these hot pixels will be excluded from your\n",
+ "analysis. If the object of interest happens to fall on one the bad pixels you\n",
+ "may well notice it. In time series photometry this sometimes manifests as a\n",
+ "single data point that is well outside the trend in the rest of the data."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Saving the mask\n",
+ "\n",
+ "The mask can be saved as in a FITS file. We will see in the notebook [incorporating masks in science images](08-05-incorporating-masks-into-calibrated-science-images.ipynb) how\n",
+ "to include this mask. Casting the mask as an unsigned integer is necessary\n",
+ "because FITS cannot store boolean data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mask_as_ccd = CCDData(data=bad_hot_pixels.astype('uint8'), unit=u.dimensionless_unscaled)\n",
+ "mask_as_ccd.header['imagetyp'] = 'dark mask'\n",
+ "mask_as_ccd.write(ex2_path / 'mask_from_dark_current.fits')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/08-02-Creating-a-mask.ipynb b/v/pdev/_sources/notebooks/08-02-Creating-a-mask.ipynb
new file mode 100644
index 00000000..a196b3f2
--- /dev/null
+++ b/v/pdev/_sources/notebooks/08-02-Creating-a-mask.ipynb
@@ -0,0 +1,444 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Creating an image mask\n",
+ "\n",
+ "Calibration cannot compensate for every defect in a CCD. Some examples (a\n",
+ "non-exhaustive list):\n",
+ "\n",
+ "+ Some hot pixels are not actually linear with exposure time.\n",
+ "+ Some pixels in the CCD may respond less to light than others in a way that\n",
+ "flat frames cannot compensate for.\n",
+ "+ There may be defects in all or part of a row or column of the chip.\n",
+ "+ Cosmic rays strike the CCD during every exposure. While those are eliminated\n",
+ "in the combined calibrated frames with the proper choice of combination\n",
+ "parameters, they are not removed from science images.\n",
+ "\n",
+ "The first three points are discussed in this notebook. Removal of cosmic rays from\n",
+ "science images is discussed [in the cosmic ray notebook](08-03-Cosmic-ray-removal.ipynb)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "from astropy import units as u\n",
+ "from astropy.nddata import CCDData\n",
+ "\n",
+ "import ccdproc as ccdp\n",
+ "from photutils import detect_sources\n",
+ "\n",
+ "from convenience_functions import show_image, image_snippet"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Detecting bad pixels with `ccdmask`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `ccdproc` function [ccdmask](https://ccdproc.readthedocs.io/en/latest/api/ccdproc.ccdmask.html#ccdproc.ccdmask) uses a method that is\n",
+ "based on the [IRAF task ccdmask](http://stsdas.stsci.edu/cgi-bin/gethelp.cgi?ccdmask). The method works best when the\n",
+ "input image used to detect flaws in the CCD is the ratio of two flat frames with\n",
+ "different counts. That may or may not be available depending on what images are\n",
+ "collected.\n",
+ "\n",
+ "In the example below, which uses images from Example 2 in the reduction\n",
+ "notebooks, the two extreme exposure times are 1 sec and 1.2 sec, but the average\n",
+ "counts in the images differ by 10,000. These were twilight flats taken just\n",
+ "after sunset.\n",
+ "\n",
+ "Even with dome flats where the illumination is supposed to be constant, the\n",
+ "counts may actually vary. If they do not, use a single flat for identifying bad\n",
+ "pixels instead of a ratio.\n",
+ "\n",
+ "We begin by creating an image collection and then the information for all of the\n",
+ "calibrated, uncombined, flat images."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex2_path = Path('example2-reduced')\n",
+ "\n",
+ "ifc = ccdp.ImageFileCollection(ex2_path)\n",
+ "\n",
+ "for long_values in ['history', 'comment']:\n",
+ " try:\n",
+ " ifc.summary.remove_column(long_values)\n",
+ " except KeyError:\n",
+ " # These two columns were not present, so removing them failed.\n",
+ " # Just keep going.\n",
+ " pass"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flats = (ifc.summary['imagetyp'] == 'FLAT') & (ifc.summary['combined'] != True)\n",
+ "ifc.summary[flats]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The best we can do here is the ratio of the first and last of the flat images\n",
+ "listed above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "first = ifc.summary['file'][flats][0]\n",
+ "last = ifc.summary['file'][flats][-1]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccd1 = CCDData.read(ex2_path / first)\n",
+ "ccd2 = CCDData.read(ex2_path / last)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ratio = ccd2.divide(ccd1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The ratio is roughly 0.85:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ratio.data.mean()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Running `ccdmask` takes a little time but only needs to be done once, not once\n",
+ "for each image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%time\n",
+ "maskr = ccdp.ccdmask(ratio)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The result of `ccdmask` is one where there is a defect and zero where the chip\n",
+ "is good, which matches the format of the mask NumPy expects.\n",
+ "\n",
+ "The input image and derived mask are shown below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, axes = plt.subplots(1, 2, figsize=(20, 10))\n",
+ "\n",
+ "show_image(ratio, cmap='gray', fig=fig, ax=axes[0], show_colorbar=False)\n",
+ "axes[0].set_title('Ratio of two flats')\n",
+ "\n",
+ "show_image(maskr, cmap='gray', fig=fig, ax=axes[1], show_colorbar=False, percl=99.95)\n",
+ "axes[1].set_title('Derived mask')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Two comments are in order:\n",
+ "\n",
+ "+ The \"starfish\" pattern in the first image is an artifact of the camera\n",
+ "shutter. Ideally, a longer exposure time would be used for the flats to avoid\n",
+ "this.\n",
+ "+ It appear at first glance that there were no pixels masked. The problem is\n",
+ "that the masked regions are very small and, at the scale shown, happen to not be\n",
+ "visible."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Two defects in this CCD are shown below. The first is a small patch of pixels\n",
+ "that are vastly less sensitive than the rest. The second is a column on the left\n",
+ "edge of the CCD. It turns out this column is not actually exposed to light.\n",
+ "`ccdmask` correctly identifies both patches as bad."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, axes = plt.subplots(2, 2, figsize=(10, 10))\n",
+ "\n",
+ "width = 100\n",
+ "center = (3823, 2446)\n",
+ "plot_row = 0\n",
+ "\n",
+ "image_snippet(ccd1, center, width=width, fig=fig, axis=axes[plot_row, 0])\n",
+ "axes[plot_row, 0].set_title('Flat, camera defect')\n",
+ "\n",
+ "image_snippet(maskr, center, width=width, fig=fig, axis=axes[plot_row, 1], is_mask=True)\n",
+ "axes[plot_row, 1].set_title('Mask, same center')\n",
+ "\n",
+ "center = (0, 2048)\n",
+ "plot_row = 1\n",
+ "\n",
+ "image_snippet(ccd1, center, width=width, fig=fig, axis=axes[plot_row, 0], percu=99.9, percl=70)\n",
+ "axes[plot_row, 0].set_title('Flat, bad column')\n",
+ "\n",
+ "image_snippet(maskr, center, width=width, fig=fig, axis=axes[plot_row, 1], is_mask=True)\n",
+ "axes[plot_row, 1].set_title('Mask, same center')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Saving the mask\n",
+ "\n",
+ "The mask can be saved in a FITS file as an image. We will see in [the summary\n",
+ "notebook on masking](08-05-incorporating-masks-into-calibrated-science-images.ipynb) how to combine the mask generated here with a mask\n",
+ "generated from the dark current and with a cosmic ray mask for each science\n",
+ "image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mask_as_ccd = CCDData(data=maskr.astype('uint8'), unit=u.dimensionless_unscaled)\n",
+ "mask_as_ccd.header['imagetyp'] = 'flat mask'\n",
+ "mask_as_ccd.write(ex2_path / 'mask_from_ccdmask.fits')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Making the mask with a single flat"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The flats we used in Example 1, taken with the Large Format Camera at Palomar,\n",
+ "are dome flats taken with nearly constant illumination. In that case the best we\n",
+ "can do is run `ccdmask` on a single flat image. As we will see, this still\n",
+ "allows the identification of several clearly bad areas of the chip.\n",
+ "\n",
+ "First, a look at the calibratted, but not combined, flat images."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex1_path = Path('example1-reduced')\n",
+ "\n",
+ "ifc1 = ccdp.ImageFileCollection(ex1_path)\n",
+ "\n",
+ "flats = (ifc1.summary['imagetyp'] == 'FLATFIELD') & (ifc1.summary['combined'] != True)\n",
+ "ifc1.summary[flats]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can double check that a ratio of flats will not be useful by calculating the\n",
+ "mean counts in each flat image:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccs = []\n",
+ "\n",
+ "for c in ifc1.ccds(imagetyp='flatfield', filter=\"g'\"):\n",
+ " if 'combined' in c.header:\n",
+ " continue\n",
+ " print(c.data.mean())\n",
+ " ccs.append(c)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The variation in counts is so small that the ratio of two flats will not be\n",
+ "useful.\n",
+ "\n",
+ "Instead, we run `ccdmask` on the first flat. There is nothing special about that\n",
+ "one. The kind of defects that `ccdmask` tries to identify are in the CCD sensor\n",
+ "itself and should be the same for all filters."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%time\n",
+ "ccs1_mask = ccdp.ccdmask(ccs[0])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Displaying the flat we used and the mask side by side demonstrates that the\n",
+ "defects which are clear in the flat are picked up in the mask."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, axes = plt.subplots(1, 2, figsize=(15, 10))\n",
+ "\n",
+ "show_image(ccs[0], cmap='gray', fig=fig, ax=axes[0])\n",
+ "axes[0].set_title('Single calibrated flat')\n",
+ "\n",
+ "show_image(ccs1_mask, cmap='gray', fig=fig, ax=axes[1], is_mask=False)\n",
+ "axes[1].set_title('Derived mask');"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "A couple of cutouts are shown below illustrating some of the individual defects\n",
+ "identified."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, axes = plt.subplots(2, 2, figsize=(10, 10))\n",
+ "\n",
+ "width = 300\n",
+ "center = (512, 3976)\n",
+ "plot_row = 0\n",
+ "\n",
+ "image_snippet(ccs[0], center, width=width, fig=fig, axis=axes[plot_row, 0])\n",
+ "axes[plot_row, 0].set_title('Flat, partial bad column')\n",
+ "\n",
+ "image_snippet(ccs1_mask, center, width=width, fig=fig, axis=axes[plot_row, 1], is_mask=True)\n",
+ "axes[plot_row, 1].set_title('Mask, same center')\n",
+ "\n",
+ "center = (420, 3250)\n",
+ "width = 100\n",
+ "plot_row = 1\n",
+ "\n",
+ "image_snippet(ccs[0], center, width=width, fig=fig, axis=axes[plot_row, 0])\n",
+ "axes[plot_row, 0].set_title('Flat, bad patch')\n",
+ "\n",
+ "image_snippet(ccs1_mask, center, width=width, fig=fig, axis=axes[plot_row, 1], is_mask=True)\n",
+ "axes[plot_row, 1].set_title('Mask, same center')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/08-03-Cosmic-ray-removal.ipynb b/v/pdev/_sources/notebooks/08-03-Cosmic-ray-removal.ipynb
new file mode 100644
index 00000000..168b6e8f
--- /dev/null
+++ b/v/pdev/_sources/notebooks/08-03-Cosmic-ray-removal.ipynb
@@ -0,0 +1,658 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Cosmic ray removal\n",
+ "\n",
+ "Almost all images from a CCD will include some number of cosmic rays, charged\n",
+ "particles which bombard the Earth's upper atmosphere. Some of those will make it\n",
+ "through the atmosphere and into your detector (the rate of cosmic rays will be\n",
+ "much higher for cameras in space). Although the number of cosmic rays is roughly\n",
+ "proportional to exposure time, there will be cosmic rays even in bias frames in\n",
+ "which the chip is immediately read out.\n",
+ "\n",
+ "This notebook explains how to remove cosmic rays from calibration images and\n",
+ "science images."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Removal from calibration images\n",
+ "\n",
+ "The most convenient way to remove cosmic rays from calibration images (bias, dark, and\n",
+ "flat images) is to combine them properly. Cosmic rays are, by their nature,\n",
+ "random events that will affect different parts of each of the calibration\n",
+ "images. A pixel affected by a cosmic ray in one of the dark images, for example,\n",
+ "will almost certainly *not* be affected by a cosmic ray in any of the other dark\n",
+ "images.\n",
+ "\n",
+ "Combining those images by averaging (to reduce noise as much as possible) and\n",
+ "sigma clipping (to exclude extreme pixels in individual images like the one with\n",
+ "a cosmic ray) will eliminate the cosmic ray from the combined dark image. An\n",
+ "alternative would be to combine the images using a median. A detailed\n",
+ "description of each option is discussed in the [section on image combination](01-06-Image-combination.ipynb).\n",
+ "\n",
+ "The method described below for removing cosmic rays from science images will not\n",
+ "work well for removing them from calibration images and is unnecessary because\n",
+ "they can be removed by properly combining the images."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Removal from science images\n",
+ "\n",
+ "One good technique for removing cosmic rays from an image is the\n",
+ "[LACosmic method](http://www.astro.yale.edu/dokkum/lacosmic/) originally developed and implemented for IRAF\n",
+ "by [Pieter G. van Dokkum](https://www.pietervandokkum.com/). The original paper describing the method,\n",
+ "which uses the sharp edges of cosmic rays to distinguish them from other sources\n",
+ "in the image, is [here](http://adsabs.harvard.edu/abs/2001PASP..113.1420V).\n",
+ "\n",
+ "\n",
+ "The specific implementation of LACosmic used here is the astropy affiliated\n",
+ "package [Astro-SCRAPPY](https://github.com/astropy/astroscrappy). If you use this code to remove cosmic\n",
+ "rays you should cite both the original paper and\n",
+ "[Astro-SCRAPPY](https://github.com/astropy/astroscrappy) (citation details are on its web site). The\n",
+ "code below never directly imports [Astro-SCRAPPY](https://github.com/astropy/astroscrappy) because\n",
+ "`ccdproc` provides a wrapper for it, so we called attention to it here."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Very important notes about using LACosmic\n",
+ "\n",
+ "There are a few things to be aware of before using the LACosmic technique. These\n",
+ "are drawn from the advice van Dokkum provides under\n",
+ "[Notes for Users](http://www.astro.yale.edu/dokkum/lacosmic/) and the original paper.\n",
+ "\n",
+ "1. The images must be bias and dark subtracted.\n",
+ "2. The images should be flat fielded, though the technique can be applied\n",
+ "without flat fielding.\n",
+ "3. The images should **not** have the sky subtracted before detecting the cosmic\n",
+ "rays.\n",
+ "4. The noise level in the image needs to be accurately measured.\n",
+ "5. The image and the noise have to be in the same units, typically electrons.\n",
+ "1. If there are pixels that are known to be bad (e.g., hot pixels, pixels\n",
+ "identified by `ccdmask`) they should be masked out before detecting cosmic rays."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "import numpy as np\n",
+ "from matplotlib import pyplot as plt\n",
+ "\n",
+ "from astropy.nddata import CCDData\n",
+ "from astropy.nddata import block_replicate\n",
+ "from astropy import units as u\n",
+ "import ccdproc as ccdp\n",
+ "from photutils import detect_sources\n",
+ "\n",
+ "from convenience_functions import show_image, display_cosmic_rays"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use custom style for larger fonts and figures\n",
+ "plt.style.use('guide.mplstyle')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Input image\n",
+ "\n",
+ "The image we will use in this notebook is one of the reduced images from Example\n",
+ "2 in the previous notebooks. It is an image of the field of the exoplanet\n",
+ "KELT-16 b, taken with a thermoelectrically-cooled CCD with read noise of 10$e^-$\n",
+ "and gain of $1.5~e^-$/ADU."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex2_path = Path('example2-reduced')\n",
+ "\n",
+ "ccd = CCDData.read(ex2_path / 'kelt-16-b-S001-R001-C084-r.fit')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_image(ccd, cmap='gray')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The unit of this image is ADU, so we need to multiply by the gain to convert to\n",
+ "electrons."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccd = ccdp.gain_correct(ccd, 1.5 * u.electron / u.adu)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Reading in and applying masks"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Two masks were calculated earlier. Hot pixels, whose dark current cannot be\n",
+ "corrected, were [identified by comparing dark frames of different exposure time](08-01-Identifying-hot-pixels.ipynb). The\n",
+ "[function `ccdmask` was used](08-02-Creating-a-mask) to identify other bad pixels; one example was a\n",
+ "column of pixels on the left side of the image.\n",
+ "\n",
+ "We read in both of the masks, which are the same for all images, and combine\n",
+ "using logical \"OR.\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "dark_mask = CCDData.read(ex2_path / 'mask_from_dark_current.fits', unit=u.dimensionless_unscaled)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccdmask_mask = CCDData.read(ex2_path / 'mask_from_ccdmask.fits', unit=u.dimensionless_unscaled)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combined_mask = dark_mask.data | ccdmask_mask.data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_image(combined_mask, cmap='gray')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Excluding these pixels from cosmic ray detection ensures that only cosmic rays\n",
+ "are identified.\n",
+ "\n",
+ "The mask is now applied to the image of KELT-16 b."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccd.mask = combined_mask"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Running LACosmic\n",
+ "\n",
+ "The actual invocation of LACosmic is fairly convenient. The key parameters\n",
+ "are `readnoise`, the read noise, and `sigclip`, which determines how far above\n",
+ "the background a pixel needs to be to consider it a cosmic ray. There is no\n",
+ "hard-and-fast rule for selecting the proper value of `sigclip`. In the original\n",
+ "paper a value of 5 is recommended, but for this image it finds several thousand\n",
+ "pixels contaminated by cosmic rays. That is not plausible for an image taken\n",
+ "with a camera 1,000 feet above sea level.\n",
+ "\n",
+ "Higher values of `sigclip` reduce the number of cosmic rays found. The value\n",
+ "used below, 7, seemed to work well for this image, finding a total of roughly 70\n",
+ "pixels that are cosmic rays, and a couple dozen candidate cosmic rays that\n",
+ "extend across multiple pixels.\n",
+ "\n",
+ "The function [`cosmicray_lacosmic`](https://ccdproc.readthedocs.io/en/latest/api/ccdproc.cosmicray_lacosmic.html#ccdproc.cosmicray_lacosmic) from `ccdproc` returns a new image in which\n",
+ "the mask is `True` for pixels in which a cosmic ray was detected and `False`\n",
+ "otherwise. The data in the new image has values in the pixels in which cosmic\n",
+ "rays were identified replaced by interpolating the neighboring pixels.\n",
+ "\n",
+ "We will take a look at the cosmic rays identified by LACosmic in a moment.\n",
+ "\n",
+ "Expect the code below to take at least a few tens of seconds to run."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%time\n",
+ "new_ccd = ccdp.cosmicray_lacosmic(ccd, readnoise=10, sigclip=7, verbose=True)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The mask of `new_ccd` includes both cosmic rays identified by\n",
+ "`cosmicray_lacosmic` and the mask we applied to `ccd` above. To get only the\n",
+ "cosmic rays, we set the mask to `False` for all of those pixels that were masked\n",
+ "before LACosmic ran."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cr_mask = new_ccd.mask\n",
+ "cr_mask[ccd.mask] = False"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The sum of the mask indicates how many pixels have been identified as cosmic\n",
+ "rays."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "new_ccd.mask.sum() "
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Examining the cosmic rays identified by LACosmic\n",
+ "\n",
+ "There are 70 pixels that have been flagged as cosmic rays. Looking through each\n",
+ "of them individually would be tedious, at best. It would also presumably be\n",
+ "difficult to decide visually if a single pixel tagged as a cosmic ray was\n",
+ "actually a cosmic ray, but it would be helpful to look at the larger cosmic\n",
+ "rays (i.e., those that span multiple pixels).\n",
+ "\n",
+ "To identify those larger cosmic rays we will use the function `detect_sources`\n",
+ "from the package [photutils](https://photutils.readthedocs.io), which identifies contiguous\n",
+ "pixels in an image via image segmentation. Though\n",
+ "[`detect_sources`](https://photutils.readthedocs.io/en/stable/api/photutils.segmentation.detect_sources.html#photutils.segmentation.detect_sources) is intended for detecting extended or stellar sources in an image it happens to work very well for identifying extended cosmic rays in the mask generated by [`cosmicray_lacosmic`](https://ccdproc.readthedocs.io/en/latest/api/ccdproc.cosmicray_lacosmic.html#ccdproc.cosmicray_lacosmic).\n",
+ "\n",
+ "The threshold below should be something less than 1 to ensure that only the\n",
+ "masked pixels (i.e., those whose values are 1) are included as sources. The\n",
+ "number of pixels is the number which must be adjacent (either by edge or by\n",
+ "corner) to be considered a source."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "threshold = 0.5\n",
+ "n_pixels = 3\n",
+ "crs = detect_sources(new_ccd.mask, threshold, n_pixels)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We first check to see how many of the pixels identified by LACosmic are part of\n",
+ "these extended cosmic rays."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "crs.areas"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "It looks like about 50% of the pixels marked as cosmic rays are extended over\n",
+ "multiple adjacent pixels."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In this particular science image there are three things being identified as\n",
+ "cosmic rays:\n",
+ "\n",
+ "+ Actual cosmic rays.\n",
+ "+ Single hot pixels (i.e., pixels with unusually high dark current).\n",
+ "\n",
+ "These conclusions are not at all clear from what we have discussed in this\n",
+ "notebook so far. They are based on a detailed examination of the images after\n",
+ "looking at thumbnail views of the cosmic ray mask, the science image in which\n",
+ "the cosmic rays were detected and the combined dark frame that was used to\n",
+ "calibrate this science image.\n",
+ "\n",
+ "That thumbnail view turned out to be a useful enough comparison that a function\n",
+ "called `display_cosmic_rays` is provided in `convenience_functions.py` that will\n",
+ "display the cosmic ray mask and as many additional comparison images as you\n",
+ "would like.\n",
+ "\n",
+ "Since one of the images that turns out to be useful to look at in this example is\n",
+ "the combined dark used in calibrating the science image, we load it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccd_dark = CCDData.read(ex2_path / 'combined_dark_90.000.fit')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "One note about the argument `only_display_rays` below, which restricts the\n",
+ "cosmic rays that are displayed to that list. The list was chosen by first\n",
+ "viewing *all* of the cosmic rays and choosing a representative sample for\n",
+ "inclusion in this discussion. Display them all by setting\n",
+ "`only_display_rays=None` in the call to `display_cosmic_rays`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "images_to_display = [new_ccd.mask, ccd, ccd_dark]\n",
+ "image_titles = ['Mask', 'Science image', 'Combined dark']\n",
+ "display_cosmic_rays(crs, images_to_display, titles=image_titles,\n",
+ " only_display_rays=[0, 1, 14, 18]\n",
+ " )"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Discussion of sample cosmic rays\n",
+ "\n",
+ "The first three examples above, labeled \"Cosmic ray 0,\" \"Cosmic ray 1,\" and\n",
+ "\"Cosmic ray 14,\" are clear-cut; each is actually a cosmic ray.\n",
+ "\n",
+ "The fourth, \"Cosmic ray 18,\" is not a cosmic ray, though it does correspond to a\n",
+ "defect in the CCD. It is caused by a single hot pixel (dark current around\n",
+ "2$e^-$/sec) that has a high value in the combined dark frame. When that\n",
+ "combined dark is subtracted from the science image it causes a large *negative*\n",
+ "value in the value of the science image which ends up being identified as a\n",
+ "cosmic ray."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Saving the mask with the image\n",
+ "\n",
+ "To save the full mask, including cosmic rays, hot pixels, and pixels identified\n",
+ "by `ccdmask`, set the mask of `ccd` to the mask of `new_ccd`. In some use cases\n",
+ "you might prefer to save `new_ccd` itself. The difference between the two is\n",
+ "that the pixel values in which there are cosmic rays have been replaced in\n",
+ "`new_ccd` by values representative of the surrounding pixels."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccd.mask = new_ccd.mask\n",
+ "\n",
+ "# This saves both the image and the mask\n",
+ "ccd.write('example-with-cosmic-rays.fits')"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## What happens if you do not mask?"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In the example above we masked the pixels known to be bad in all of the images\n",
+ "before detecting cosmic rays. It is possible to do cosmic ray detection without\n",
+ "prior masking. The downside of not masking is that many features that are not\n",
+ "cosmic rays are identified as cosmic rays, and some real cosmic rays are not\n",
+ "detected.\n",
+ "\n",
+ "We begin with a fresh copy of the input image and run cosmic ray detection with\n",
+ "the same parameters as above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccd = CCDData.read(ex2_path / 'kelt-16-b-S001-R001-C084-r.fit')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%time\n",
+ "new_ccd_no_premask = ccdp.cosmicray_lacosmic(ccd, readnoise=10, sigclip=7, verbose=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(\"Pre-masking detects {} cosmic ray pixels.\\nNo pre-masking detects {} cosmic ray pixels.\".format(new_ccd.mask.sum(), new_ccd_no_premask.mask.sum()))"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Many of the additional pixels detected as cosmic rays are in the leftmost column\n",
+ "of the image. The column is actually bad (it is covered on the CCD and receives\n",
+ "no light)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "crs_no_premask = detect_sources(new_ccd_no_premask.mask, threshold, n_pixels)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "crs_no_premask.areas"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Some of these are not cosmic rays\n",
+ "\n",
+ "The display below shows a few examples of areas identified by\n",
+ "`cosmicray_lacosmic` that are not actually cosmic rays. These false positives\n",
+ "are harmless because they reflect real problems with the detector and should be\n",
+ "masked out anyway.\n",
+ "\n",
+ "More problematic are the cosmic rays that are undetected if the image is not\n",
+ "masked first. As an example, the cosmic ray labeled \"Cosmic ray 0\" in the\n",
+ "[example above in which a mask was applied before detecting cosmic rays](#discussion-of-sample-cosmic-rays) is not detected at all when no masking is done."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "images_to_display = [new_ccd_no_premask.mask, ccd, ccd_dark]\n",
+ "image_titles = ['Mask', 'Science image', 'Combined dark']\n",
+ "display_cosmic_rays(crs_no_premask, images_to_display, titles=image_titles,\n",
+ " only_display_rays=[0, 1, 2]\n",
+ " )"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Discussion"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The first example above is the bad column on the left side of the image.\n",
+ "\n",
+ "The one label \"Cosmic ray 1\" is caused by a single hot pixel with a large count\n",
+ "in the dark frames, which leads to a large negative value in the calibrated\n",
+ "science image. LACosmic tags that pixel and many around it as cosmic rays. While\n",
+ "the individual hot pixel should be masked, the ones around it do not need to be\n",
+ "masked.\n",
+ "\n",
+ "The final example, \"Cosmic ray 2,\" looks a lot like a star. It is, in fact, the\n",
+ "after-image of one of the bright stars in the field of KELT-16 b. Bright stars in\n",
+ "the field of view can deposit enough charge that it does not dissipate between\n",
+ "images. Typically the effect can be avoided altogether by \"pre-flashing\" the\n",
+ "CCD.\n",
+ "\n",
+ "Since you cannot pre-flash after the images have been taken, we are stuck doing\n",
+ "the next best thing: masking out this part of the images. The masking should be\n",
+ "done during the stage at which hot pixels are being identified because all of these\n",
+ "pixels will be identified as hot."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/08-05-incorporating-masks-into-calibrated-science-images.ipynb b/v/pdev/_sources/notebooks/08-05-incorporating-masks-into-calibrated-science-images.ipynb
new file mode 100644
index 00000000..5e63b226
--- /dev/null
+++ b/v/pdev/_sources/notebooks/08-05-incorporating-masks-into-calibrated-science-images.ipynb
@@ -0,0 +1,196 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Incorporating masks into calibrated science images"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There are three ways of determining which pixels in a CCD image may need to be\n",
+ "masked (this is in addition to whatever mask or bit fields the observatory at\n",
+ "which you are taking images may provide).\n",
+ "\n",
+ "Two of them are the same for all of the science images:\n",
+ "\n",
+ "+ Hot pixels unlikely to be properly calibrated by subtracting dark current,\n",
+ "discussed in [Identifying hot pixels](08-01-Identifying-hot-pixels.ipynb).\n",
+ "+ Bad pixels identified by `ccdproc.ccdmask` from flat field images, discussed\n",
+ "in [Creating a mask with `ccdmask`](08-02-Creating-a-mask.ipynb).\n",
+ "\n",
+ "The third, identifying cosmic rays, discussed in\n",
+ "[Cosmic ray removal](08-03-Cosmic-ray-removal.ipynb), will by its nature be different for each\n",
+ "science image.\n",
+ "\n",
+ "The first two masks could be added to science images at the time the science\n",
+ "images are calibrated, if desired. They are added to the science images here, as\n",
+ "a separate step, because in many situations it is fine to omit masking entirely\n",
+ "and there is no particular advantage to introducing it earlier."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We begin, as usual, with a couple of imports."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "from astropy import units as u\n",
+ "from astropy.nddata import CCDData\n",
+ "\n",
+ "import ccdproc as ccdp"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Read masks that are the same for all of the science images\n",
+ "\n",
+ "In previous notebooks we constructed a mask based on the dark current and a mask\n",
+ "created by `ccdmask` from a flat image. Displaying the summary of the the\n",
+ "information about the reduced images is a handy way to determine which files are\n",
+ "the masks."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ex2_path = Path('example2-reduced')\n",
+ "\n",
+ "ifc = ccdp.ImageFileCollection(ex2_path)\n",
+ "ifc.summary['file', 'imagetyp']"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We read each of those in below, converting the mask to boolean after we read it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mask_ccdmask = CCDData.read(ex2_path / 'mask_from_ccdmask.fits', unit=u.dimensionless_unscaled)\n",
+ "mask_ccdmask.data = mask_ccdmask.data.astype('bool')\n",
+ "\n",
+ "mask_hot_pix = CCDData.read(ex2_path / 'mask_from_dark_current.fits', unit=u.dimensionless_unscaled)\n",
+ "mask_hot_pix.data = mask_hot_pix.data.astype('bool')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Combining the masks\n",
+ "\n",
+ "We combine the masks using a logical \"OR\" since we want to mask out pixels that are\n",
+ "bad for any reason."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combined_mask = mask_ccdmask.data | mask_hot_pix.data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "It turns out we are masking roughly 0.056% of the pixels so far."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "combined_mask.sum()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Detect cosmic rays\n",
+ "\n",
+ "Cosmic ray detection was discussed in detail in an\n",
+ "[earlier section](08-03-Cosmic-ray-removal.ipynb). Here we loop over all of the calibrated\n",
+ "science images and:\n",
+ "\n",
+ "+ detect cosmic rays in them,\n",
+ "+ combine the cosmic ray mask with the mask that applies to all images,\n",
+ "+ set the mask of the image to the overall mask, and\n",
+ "+ save the image, overwriting the calibrated science image without the mask.\n",
+ "\n",
+ "Since the cosmic ray detection takes a while, a status message is displayed\n",
+ "before each image is processed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ifc.files_filtered()\n",
+ "for ccd, file_name in ifc.ccds(imagetyp='light', return_fname=True):\n",
+ " print('Working on file {}'.format(file_name))\n",
+ " new_ccd = ccdp.cosmicray_lacosmic(ccd, readnoise=10, sigclip=8, verbose=True)\n",
+ " overall_mask = new_ccd.mask | combined_mask\n",
+ " # If there was already a mask, keep it.\n",
+ " if ccd.mask is not None:\n",
+ " ccd.mask = ccd.mask | overall_mask\n",
+ " else:\n",
+ " ccd.mask = overall_mask\n",
+ " # Files can be overwritten only with an explicit option\n",
+ " ccd.write(ifc.location / file_name, overwrite=True)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/README.md b/v/pdev/_sources/notebooks/README.md
new file mode 100644
index 00000000..13f58299
--- /dev/null
+++ b/v/pdev/_sources/notebooks/README.md
@@ -0,0 +1,144 @@
+# CCD guide: preparation for publishing
+
+Want to try to get all of the processing steps in going from notebooks to book in one place.
+
+## Remove old outputs (in python)
+
+```python
+from process_for_book import clean
+clean()
+```
+
+or
+
+```shell
+python -c "from process_for_book import clean; clean()"
+```
+
+## Generate list of notebooks to process (fish version):
+
+```shell
+# The sort below is important because later notebooks depend on
+# output of earlier ones.
+set to_conv (find . -depth 1 -name 0[01234568]-\?\?-\*.ipynb | sort -)
+```
+
+## Run the notebooks to generate output (fish version)
+
+``shell
+for conv in $to_conv
+ jupyter nbconvert --to notebook --execute --ExecutePreprocessor.timeout=-1 $conv
+end
+```
+
+## Move the generated notebooks to separate folder (fish shown)
+
+```shell
+# This can be refactored easily.
+python process_for_book.py
+```
+
+## Replace links to notebooks with links to html
+
+**Modifies notebooks in the directory with the *converted* notebooks**
+
+```python
+from pathlib import Path
+import os
+
+from process_for_book import replace_links_in_notebook
+
+os.chdir('converted')
+
+p = Path('.')
+
+notebooks = p.glob('*.ipynb')
+for notebook in notebooks:
+ replace_links_in_notebook(str(notebook))
+
+os.chdir('..')
+```
+
+## Set GitHub token
+
+
+```shell
+set -x GITHUB_TOKEN your_token_here
+```
+
+## Clean up old review rounds on GitHub, if any
+
+**Set `GITHUB_TOKEN` first**
+
+```python
+from add_github_links import delete_branches_prs, get_github_repo
+repo = get_github_repo('astropy', 'ccd-reduction-and-photometry-guide')
+# Replace the name review-8e187b6 with the actual name you want
+# to eliminate, of course.
+delete_branches_prs('review-8036850', repo)
+```
+
+
+## Copy content from the working directory to content
+
+This is silly, but right now it needs a copy/paste. DO NOT MOVE because
+the logic in the link-adding code below is a little janky.
+
+```shell
+cp converted/* /Users/mcraig/Documents/Research/ccd-as-book/content
+```
+
+## Add links for commenting on each section
+
+**Set `GITHUB_TOKEN` first**
+
+```python
+from add_github_links import commentify_all_notebooks
+converted_for_book = '/Users/mattcraig/development/ccd-as-book/content/'
+path_to_original = '.'
+commentify_all_notebooks(converted_for_book,
+ path_to_original,
+ comment_group='review-8036850')
+```
+
+## Build the book markdown locally
+
+```shell
+# Change to root directory of book
+jupyter-book build .
+```
+
+## Build/serve to check locally
+
+Make ruby not suck: `set -x CONDA_BUILD_SYSROOT /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/`
+
+```shell
+# Change to root directory of book
+make serve
+```
+
+## CCD guide message for reviewers
+
+Dear X,
+
+Thanks for agreeing to take a look at the draft guide to reducing CCD data using astropy.
+
+The most straightforward way to provide feedback does not require you to run any of the code on your computer (though that is an option if you prefer it).
+
+You will need a free account on GitHub.com to make comments, and you will need to log into GitHub.
+
+Please go to the book at https://mwcraig.github.io/ccd-as-book/00-00-Preface.html
+
+Below each section heading is a link that says "Click here to comment on this section in GitHub".
+
+Clicking on any of those links as you read through the guide will take you to the location of that section on GitHub so that you can make comments.
+
+To make a comment:
+
++ When you move your mouse over a line, a blue "plus" sign will be visible at the beginning of the line (if you are logged in to GitHub).
++ Click that blue plus and a box for making a comment will appear.
++ When you are done writing your comment, click either "Add single comment" or "Start a review".
++ If you click "Start a review" then you will need to complete the review by clicking on "Finish your review" in the upper right hand corner of the screen.
+
+Thanks,
+Matt Craig
diff --git a/v/pdev/_sources/notebooks/add_style_cell.ipynb b/v/pdev/_sources/notebooks/add_style_cell.ipynb
new file mode 100644
index 00000000..e2382950
--- /dev/null
+++ b/v/pdev/_sources/notebooks/add_style_cell.ipynb
@@ -0,0 +1,110 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Examining 03-01-Dark-current-The-ideal-case.ipynb\n",
+ "\tInserting style cell in 03-01-Dark-current-The-ideal-case.ipynb\n",
+ "Examining 01-00-Understanding-an-astronomical-CCD-image.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 02-00-Handling-overscan-trimming-and-bias-subtraction.ipynb\n",
+ "\tInserting style cell in 02-00-Handling-overscan-trimming-and-bias-subtraction.ipynb\n",
+ "Examining 02-01-Calibrating-bias-images.ipynb\n",
+ "\tInserting style cell in 02-01-Calibrating-bias-images.ipynb\n",
+ "Examining 08-05-incorporating-masks-into-calibrated-science-images.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 00-00-Preface.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 07-01-Creating-a-sky-flat.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 01-03-Construction-of-an-artificial-but-realistic-image.ipynb\n",
+ "\tInserting style cell in 01-03-Construction-of-an-artificial-but-realistic-image.ipynb\n",
+ "Examining 03-06-Combine-darks-for-use-in-later-calibration-steps.ipynb\n",
+ "\tInserting style cell in 03-06-Combine-darks-for-use-in-later-calibration-steps.ipynb\n",
+ "Examining 03-04-Handling-overscan-and-bias-for-dark-frames.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 08-02-Creating-a-mask.ipynb\n",
+ "\tInserting style cell in 08-02-Creating-a-mask.ipynb\n",
+ "Examining 05-04-Combining-flats.ipynb\n",
+ "\tInserting style cell in 05-04-Combining-flats.ipynb\n",
+ "Examining 08-01-Identifying-hot-pixels.ipynb\n",
+ "\tInserting style cell in 08-01-Identifying-hot-pixels.ipynb\n",
+ "Examining 03-00-Dark-current-and-hot-pixels.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 01-11-reading-images.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 03-02-Real-dark-current-noise-and-other-artifacts.ipynb\n",
+ "\tInserting style cell in 03-02-Real-dark-current-noise-and-other-artifacts.ipynb\n",
+ "Examining 08-03-Cosmic-ray-removal.ipynb\n",
+ "\tInserting style cell in 08-03-Cosmic-ray-removal.ipynb\n",
+ "Examining 01-04-Nonuniform-sensitivity.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 01-09-Calibration-choices-you-need-to-make.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 05-03-Calibrating-the-flats.ipynb\n",
+ "\tInserting style cell in 05-03-Calibrating-the-flats.ipynb\n",
+ "Examining 08-00-Image-masking.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 01-08-Overscan.ipynb\n",
+ "\tInserting style cell in 01-08-Overscan.ipynb\n",
+ "Examining 06-00-Reducing-science-images.ipynb\n",
+ "\tInserting style cell in 06-00-Reducing-science-images.ipynb\n",
+ "Examining 01-06-Image-combination.ipynb\n",
+ "\tInserting style cell in 01-06-Image-combination.ipynb\n",
+ "Examining 02-04-Combine-bias-images-to-make-master.ipynb\n",
+ "\tInserting style cell in 02-04-Combine-bias-images-to-make-master.ipynb\n",
+ "Examining 07-00-Combining-images.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 03-05-Calibrate-dark-images.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 01-05-Calibration-overview.ipynb\n",
+ "\tInserting style cell in 01-05-Calibration-overview.ipynb\n",
+ "Examining 07-03-Combination-with-alignment-based-on-star-positions-in-the-image.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 07-02-Combination-with-alignment-via-WCS.ipynb\n",
+ "\tNo insertion needed\n",
+ "Examining 05-00-Flat-corrections.ipynb\n",
+ "\tNo insertion needed\n"
+ ]
+ }
+ ],
+ "source": [
+ "%run add_matplotlib_style.py"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/photometry/00.00-Preface.ipynb b/v/pdev/_sources/notebooks/photometry/00.00-Preface.ipynb
new file mode 100644
index 00000000..3991afda
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/00.00-Preface.ipynb
@@ -0,0 +1,61 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Preface\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Photometry is the measurement of the amount of light from an image. There are, broadly speaking, two types of photometry. *Aperture photometry* measures the amount of light inside a region (the aperture) of fixed size. This is the kind of photometry done by photoelectric photometers.\n",
+ "\n",
+ "*PSF photometry* fits the image of an object like a star to the point spread function (PSF) of the camera. It is essential to doing photometry in crowded fields.\n",
+ "\n",
+ "```{note}\n",
+ "The notebooks in this part of the guide draw heavily from the excellent [photutils documentation](https://photutils.readthedocs.io/en/stable/)\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "Several notebooks in this part of the guide were origninally developed by Lauren CHambers and Erik Tollerud with support from the Community Software Initiative at the Space Telescope Science Institute, the Astropy Project and ScienceBetter Consulting. Those notebooks are:\n",
+ "\n",
+ "+ A notebook\n",
+ "+ Another notebook \n",
+ "\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/photometry/01.00-Source-detection.ipynb b/v/pdev/_sources/notebooks/photometry/01.00-Source-detection.ipynb
new file mode 100644
index 00000000..ef547f44
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/01.00-Source-detection.ipynb
@@ -0,0 +1,54 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Source detection\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The first step in doing photometry is deciding which objects you want photometry on. There are a few approaches to take:\n",
+ "\n",
+ "+ Find sources in your image and perform photometry on every source you can detect in the image.\n",
+ "+ Perform photometry on objects from a catalog (either one you have created or from some other source); this presumes there is WCS information in the images so that catalog RA/Dec can be translated to pixel position on the image.\n",
+ "+ Stack your images, detect the sources in that much deeper stacked image, treat those sources as your catalog and use that catlog to erform photometry."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The next series of notebooks will describe how to do source detection.\n",
+ "\n",
+ "```{note}\n",
+ "The notebooks in this part of the guide draw heavily from the excellent [photutils documentation](https://photutils.readthedocs.io/en/stable/)\n",
+ "```"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/photometry/01.01-Background-removal.ipynb b/v/pdev/_sources/notebooks/photometry/01.01-Background-removal.ipynb
new file mode 100644
index 00000000..13ed6be4
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/01.01-Background-removal.ipynb
@@ -0,0 +1,43 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Background removal for source detection"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The first step in detecting sources is to remove any background from the image. There are a number of ways to do this, ranging from subtracting a single value (typically the median or mean) from every pixel to more sophisticated methods either fit a slowly-varying shape to the background or that smooth the input image in some way.\n",
+ "\n",
+ "There is probably not a single \"best\" way to do this; what matters is whether you are able to detect the sources you need to detect in the images you have.\n",
+ "\n",
+ "Note that the way you do background removal for source detection does *not* need to be the same way you do it for photometry. It is perfectly fine to use the same method for both, of course, but it is not required."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/photometry/01.01.01-Background-removal-simulated-data.ipynb b/v/pdev/_sources/notebooks/photometry/01.01.01-Background-removal-simulated-data.ipynb
new file mode 100644
index 00000000..fac2bad7
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/01.01.01-Background-removal-simulated-data.ipynb
@@ -0,0 +1,445 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Overview of background subtraction using simulated data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The first step in detecting sources is to remove any background from the image. There are a number of ways to do this, ranging from subtracting a single value (typically the median or mean) from every pixel to more sophisticated methods either fit a slowly-varying shape to the background or that smooth the input image in some way.\n",
+ "\n",
+ "There is probably not a single \"best\" way to do this; what matters is whether you are able to detect the sources you need to detect in the images you have.\n",
+ "\n",
+ "Note that the way you do background removal for source detection does *not* need to be the same way you do it for photometry. It is perfectly fine to use the same method for both, of course, but it is not required."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Simple approach: median or other scalar estimator of background \n",
+ "\n",
+ "The simplest way to estimate the background is with a single number, perhaps the mean or median of the data. The problem with that approach is that the distribution of pixel value in an astronomical image is not symmetric. Most of the pixels have values near zero while a small number have significantly higher values.\n",
+ "\n",
+ "The [photutils](https://photutils.readthedocs.io/en/stable/index.html) documentation on [scalar background and noise estimation](https://photutils.readthedocs.io/en/stable/background.html#scalar-background-and-noise-estimation) walk through this step-by-step, demonstrating the result you get with a variety of background estimators.\n",
+ "\n",
+ "The upshot is that the best estimate of the background is obtained by first masking the sources in the image and then sigma clipping the unmasked parts of the data. The mean and median of the unmasked parts of the image give almost exactly the same value, and standard deviation recovered from the data matches that of the input."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Pixel distribution in simulated image\n",
+ "\n",
+ "We begin by constructing a histogram of pixel values in a sample image. The code below illustrates a few useful things from astropy and photutils:\n",
+ "\n",
+ "+ photutils comes with useful simulated images.\n",
+ "+ astropy comes with a histogram function that will automatically bin the data in a way that brings out features in your data that uniform-width bins might miss.\n",
+ "+ astropy has several stretches and normalizations to bring out features in astronomical images.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "%matplotlib inline\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "from photutils.background import Background2D, MeanBackground, MADStdBackgroundRMS\n",
+ "from photutils.datasets import make_100gaussians_image\n",
+ "from photutils.segmentation import detect_threshold, detect_sources\n",
+ "from photutils.utils import circular_footprint\n",
+ "\n",
+ "from astropy.visualization import SqrtStretch\n",
+ "from astropy.visualization.mpl_normalize import ImageNormalize\n",
+ "from astropy.visualization import hist\n",
+ "from astropy.stats import sigma_clipped_stats, SigmaClip\n",
+ "\n",
+ "data = make_100gaussians_image()\n",
+ "\n",
+ "norm = ImageNormalize(stretch=SqrtStretch())\n",
+ "plt.imshow(data, norm=norm, origin='lower', cmap='Greys_r')\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There are a couple of things to note about this distrubtion. \n",
+ "\n",
+ "The bulk of the pixel values are noise. The function [`make_100gaussians_image`](https://photutils.readthedocs.io/en/stable/api/photutils.datasets.make_100gaussians_image.html#photutils.datasets.make_100gaussians_image) adds a background with a Gaussian distribution centered at 5 with a standard deviation of 2. The long tail at higher pixel values are from the sources in the image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(10, 5))\n",
+ "\n",
+ "# IMPORTANT NOTE: flattening the data to one dimension vastly reduces the\n",
+ "# time it takes to histogram the data.\n",
+ "\n",
+ "h, bins, patches = hist(data.flatten(), bins='freedman', log=True,\n",
+ " alpha=0.5, label='Pixel values')\n",
+ "plt.xlabel('Pixel value')\n",
+ "plt.ylabel('Number of pixels')\n",
+ "# The semi-colon supresses a display of the grid object representation\n",
+ "plt.grid();"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Estimating the background value\n",
+ "\n",
+ "The plot below shows the result of several ways of estimating the background from the data. None of these straightforward estimates give a good measure of the true background level, indicated by the green line."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(10, 5))\n",
+ "hist(data.flatten(), bins='freedman', log=True,\n",
+ " alpha=0.5, label='Pixel values')\n",
+ "plt.xlim(0, 10)\n",
+ "\n",
+ "true_mean = 5\n",
+ "avg_all_pixels = data.mean()\n",
+ "median_all_pixels = np.median(data)\n",
+ "plt.axvline(true_mean, label='True mean of background', color='green')\n",
+ "plt.axvline(avg_all_pixels, label='Mean of all values', color='red')\n",
+ "plt.axvline(median_all_pixels, label='Median of all values', color='cyan')\n",
+ "\n",
+ "# Try sigma clipping\n",
+ "clipped_mean, clipped_med, clipped_std = sigma_clipped_stats(data, sigma=3)\n",
+ "\n",
+ "# Add line to chart\n",
+ "plt.axvline(clipped_med, label='Sigma-clipped median', color='yellow')\n",
+ "plt.xlabel('Pixel value')\n",
+ "plt.ylabel('Number of pixels')\n",
+ "plt.legend();"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "None of the estimates above do a particularly good job of estimating the true background level. They also do not do a great job of estimating the error or noise in that background; the input value was 2, but the value produced by sigma clipping is 5% larger:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "clipped_std"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Best approach for single value\n",
+ "\n",
+ "The best approach to deriving a single value to use for background is to first mask the sources. There are a few steps to doing that.\n",
+ "\n",
+ "First, define the sigma clip parameters for deticting the threshold for sources"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "sigma_clip = SigmaClip(sigma=3.0, maxiters=10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, find pixels more that 2$\\sigma$ above the background using [`detect_threshold`](https://photutils.readthedocs.io/en/stable/api/photutils.segmentation.detect_threshold.html#photutils.segmentation.detect_threshold)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "threshold = detect_threshold(data, nsigma=2.0, sigma_clip=sigma_clip)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "These pixels may be isolated or disconnected. The function [`detect_sources`](https://photutils.readthedocs.io/en/stable/api/photutils.segmentation.detect_sources.html#photutils.segmentation.detect_sources)\n",
+ " creates a segmentation image, in which only connected sets of `npixels` or more pixels are identified as sources."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "segment_img = detect_sources(data, threshold, npixels=10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Finally, we use [`make_source_mask`](https://photutils.readthedocs.io/en/stable/api/photutils.segmentation.SegmentationImage.html#photutils.segmentation.SegmentationImage.make_source_mask) to create a mask that is `True` where there are sources. It masks out a circular region 10 pixels in size at each source."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "footprint = circular_footprint(radius=10)\n",
+ "\n",
+ "source_mask = segment_img.make_source_mask(footprint=footprint)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mask_clip_mean, mask_clip_med, mask_clip_std = sigma_clipped_stats(data, sigma=3, mask=source_mask)\n",
+ "\n",
+ "plt.figure(figsize=(10, 5))\n",
+ "hist(data.flatten(), bins='freedman', log=True,\n",
+ " alpha=0.5, label='Pixel values')\n",
+ "plt.xlim(0, 10)\n",
+ "\n",
+ "plt.axvline(true_mean, label='True mean of background', color='green')\n",
+ "plt.axvline(avg_all_pixels, label='Mean of all values', color='red')\n",
+ "plt.axvline(median_all_pixels, label='Median of all values', color='cyan')\n",
+ "plt.axvline(clipped_med, label='Sigma-clipped median', color='yellow')\n",
+ "plt.axvline(mask_clip_med, label='Sigma-clipped, sources masked',\n",
+ " color='violet', linestyle='dashed')\n",
+ "plt.legend();"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### A caveat about the \"best\" approach\n",
+ "\n",
+ "Note that the difference in value between the estimates of the background displayed above are not large. The one that is furthest off, the mean of all of the values, differs from the true value by half a count. Whether that is significant or not depends on what science you are trying to do, what sources of error are present in your data and how large they are, and how you will treat the data in later steps. \n",
+ "\n",
+ "That is not to say there is anything wrong about the approach described in this section; it is just a reminder that the right choice for your data and science is not necessary what is described here as the best choice."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Two dimensional background \n",
+ "\n",
+ "If the background is not uniform (as happens, for example, on a night with moonlight) then using a single value to represent that background will not be effective. Instead, one constructs a background array. It is constructed by breaking the image into pieces, calcuating a scalar background estimate as above, and building those back into a two-dimensional image. \n",
+ "\n",
+ "To do this with photutils requires making a few choices:\n",
+ "\n",
+ "+ *What should the mesh size be?* This is the size, in pixels, of the chunks into which the image will be split for generating the background. It should be larger than the sources in the image, but small enough to represent variation in background across the image.\n",
+ "+ *How should the background in each mesh cell be calculated?* The full suite of options is listed in the [photutils documentation for 2D backgrounds](https://photutils.readthedocs.io/en/stable/background.html#d-background-and-noise-estimation). The example below uses the mean because the sources will be masked as in the scalar case. Other options include the median and `SExtractorBackground` for using the background method used by [Source Extractor](https://astromatic.github.io/sextractor/).\n",
+ "+ *Do you want to also estimate the noise in the background, i.e. the RMS of the background?* Again, several options are available.\n",
+ "+ *How do you want the background image interpolated when it is scaled back up to the size of the input image?* By default spline interpolation is used, but it can be changed.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Add some background to the input image\n",
+ "\n",
+ "As with the rest of the examples in this notebook, we closely follow the example from the photutils documentation. The gradient added here might resemble what you would ee on a moonlit night near the moon."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ny, nx = data.shape\n",
+ "y, x = np.mgrid[:ny, :nx]\n",
+ "\n",
+ "gradient = x / 50.\n",
+ "gradient_median = np.median(gradient)\n",
+ "data2 = data + gradient\n",
+ "\n",
+ "plt.figure(figsize=(10, 5))\n",
+ "plt.imshow(data2, norm=norm, origin='lower', cmap='Greys_r')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Choose your options for constructing the background \n",
+ "\n",
+ "For most of the choices in the list above, one creates an instance of the *class* corresponing to the choice."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Choose sigma clip parameters\n",
+ "\n",
+ "# Parameters below match those used in sigma_clipped_stats above. The\n",
+ "# default in photutils is sigma=3, iters=10; this default is used unless\n",
+ "# you override it as shown below.\n",
+ "sigma_clip = SigmaClip(sigma=3., maxiters=5)\n",
+ "\n",
+ "# Choose your background calculation method\n",
+ "bg_estimator = MeanBackground()\n",
+ "\n",
+ "# Choose your mesh size...ideally, an integer number of these fits across\n",
+ "# the image. Here we use a size such that 10 meshes fit across the image\n",
+ "# in each directory.\n",
+ "mesh_size = (ny // 10, nx // 10)\n",
+ "\n",
+ "# Choose an estimator for the noise in the background (i.e. RMS). The\n",
+ "# default is to use standard deviation using StdBackgroundRMS. This\n",
+ "# example uses the median absolute deviation for the purposes of\n",
+ "# illustration.\n",
+ "bg_rms_estimator = MADStdBackgroundRMS()\n",
+ "\n",
+ "# We could, in principle, specify an interpolator for going from the small\n",
+ "# mesh to the full size image. By not specifying that we get the default,\n",
+ "# which is a spline interpolation; photutils calls this BgZoom\n",
+ "\n",
+ "# Finally, construct the background. Each of the choices above are fed into\n",
+ "# the initialization of the background object.\n",
+ "bgd = Background2D(data2, mesh_size,\n",
+ " sigma_clip=sigma_clip,\n",
+ " bkg_estimator=bg_estimator,\n",
+ " bkgrms_estimator=bg_rms_estimator,\n",
+ " )\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Display the background \n",
+ "\n",
+ "Though this is not required, we may as well check to see whether the inferred background matches the gradient in the x-direction that we added to the image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(10, 5))\n",
+ "plt.imshow(bgd.background, norm=norm, origin='lower', cmap='Greys_r')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Check: compare background-subtracted data to expected values\n",
+ "\n",
+ "If the background has been properly substracted, then the median of the subtracted image should be zero (if we mask out the sources) and the rms should be around 2, the value that was used to generate the Gaussian noise in `make_100gaussians_image`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "back_sub_data = data2 - bgd.background\n",
+ "\n",
+ "sub_mean, sub_med, sub_std = sigma_clipped_stats(back_sub_data,\n",
+ " sigma=3)\n",
+ "\n",
+ "print(sub_mean, sub_med, sub_std)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Background-subtracted image\n",
+ "\n",
+ "Finally, we display the background-subtracted image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.figure(figsize=(10, 5))\n",
+ "plt.imshow(data2 - bgd.background, norm=norm, origin='lower', cmap='Greys_r')\n",
+ "plt.title('Background subtracted data')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Summary\n",
+ "\n",
+ "An image needs to be background-subtracted before you detect the sources in the image. That background can be represented by either a single number (e.g. the mean or median) or by a two-dimensional image. In both cases the best estimate of the background is obtained when sources are masked out before estimating the background. Though this means doing two rounds of source detection, one to estimate the background and one for the actual source detection, the method of finding sources used above is reasonably fast.\n",
+ "\n",
+ "Next, we will look at how to do background estimation for a couple of actual science images to get a more nuanced understanding of the choices in background subtraction."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/photometry/01.01.02-Background-estimation-XDF.ipynb b/v/pdev/_sources/notebooks/photometry/01.01.02-Background-estimation-XDF.ipynb
new file mode 100644
index 00000000..b00520a6
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/01.01.02-Background-estimation-XDF.ipynb
@@ -0,0 +1,598 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Handling masking before background removal"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Data for this notebook \n",
+ "\n",
+ "We will be manipulating Hubble eXtreme Deep Field (XDF) data, which was collected using the Advanced Camera for Surveys (ACS) on Hubble between 2002 and 2012. The image we use here is the result of 1.8 million seconds (500 hours!) of exposure time, and includes some of the faintest and most distant galaxies that had ever been observed. \n",
+ "\n",
+ "Background subtraction is essential for accurate photometric analysis of astronomical data like the XDF.\n",
+ "\n",
+ "*The methods demonstrated here are available in narrative form within the `photutils.background` [documentation](http://photutils.readthedocs.io/en/stable/background.html).*"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Import necessary packages"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First, let's import packages that we will use to perform arithmetic functions and visualize data:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "from astropy.io import fits\n",
+ "from astropy.nddata import CCDData\n",
+ "import astropy.units as u\n",
+ "from astropy.stats import sigma_clipped_stats, SigmaClip\n",
+ "from astropy.visualization import ImageNormalize, LogStretch\n",
+ "import matplotlib.pyplot as plt\n",
+ "from matplotlib.ticker import LogLocator\n",
+ "\n",
+ "from photutils.segmentation import detect_threshold, detect_sources\n",
+ "from photutils.utils import circular_footprint\n",
+ "\n",
+ "# Show plots in the notebook\n",
+ "%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's also define some `matplotlib` parameters, such as title font size and the dpi, to make sure our plots look nice. To make it quick, we'll do this by loading a [style file shared with the other photutils tutorials](../photutils_notebook_style.mplstyle) into `pyplot`. We will use this style file for all the notebook tutorials. (See [here](https://matplotlib.org/users/customizing.html) to learn more about customizing `matplotlib`.)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.style.use('../photutils_notebook_style.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Data representation\n",
+ "\n",
+ "Throughout this notebook, we are going to store our images in Python using a `CCDData` object (see [Astropy documentation](http://docs.astropy.org/en/stable/nddata/index.html#ccddata-class-for-images)), which contains a `numpy` array in addition to metadata such as uncertainty, masks, or units. In this case, each image has units electrons (counts) per second.\n",
+ "\n",
+ "Note that you could create the `CCDData` object directly from the URL where this image is taken from:\n",
+ "\n",
+ "```python\n",
+ "url = 'https://archive.stsci.edu/pub/hlsp/xdf hlsp_xdf_hst_acswfc-60mas_hudf_f435w_v1_sci.fits'\n",
+ "xdf_image = CCDData.read(url)\n",
+ "```\n",
+ "\n",
+ "Since the data for the guide is meant to be downloaded in bulk before going through the guide, we read the image from disk here.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "xdf_image = CCDData.read('hlsp_xdf_hst_acswfc-60mas_hudf_f435w_v1_sci.fits')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's look at the data, including the background we've added. The cell below sets up an image normaliztaion we will use for all of our later views of the image. It also defines a small function, `format_colorbar` that is used to set up the colorbar in the subsequent plots."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, ax1 = plt.subplots(1, 1, figsize=(8, 8))\n",
+ "\n",
+ "# Set up the normalization and colormap. The values of vmin and vmax are specific to this image\n",
+ "norm_image = ImageNormalize(vmin=1e-4, vmax=5e-2, stretch=LogStretch())\n",
+ "cmap = plt.get_cmap('viridis')\n",
+ "\n",
+ "# Plot the data, with pixels masked as appropriate\n",
+ "fitsplot = ax1.imshow(np.ma.masked_where(xdf_image.mask, xdf_image),\n",
+ " norm=norm_image, cmap=cmap)\n",
+ "\n",
+ "# Define the colorbar\n",
+ "cbar = plt.colorbar(fitsplot, fraction=0.046, pad=0.04, ticks=LogLocator())\n",
+ "\n",
+ "def format_colorbar(bar):\n",
+ " # Add minor tickmarks\n",
+ " bar.ax.yaxis.set_minor_locator(LogLocator(subs=range(1, 10)))\n",
+ "\n",
+ " # Force the labels to be displayed as powers of ten and only at exact powers of ten\n",
+ " bar.ax.set_yticks([1e-4, 1e-3, 1e-2])\n",
+ " labels = [f'$10^{{{pow:.0f}}}$' for pow in np.log10(bar.ax.get_yticks())]\n",
+ " bar.ax.set_yticklabels(labels)\n",
+ "\n",
+ "format_colorbar(cbar)\n",
+ "\n",
+ "# Define labels\n",
+ "cbar.set_label(r'Flux Count Rate ({})'.format(xdf_image.unit.to_string('latex')),\n",
+ " rotation=270, labelpad=30)\n",
+ "ax1.set_xlabel('X (pixels)')\n",
+ "ax1.set_ylabel('Y (pixels)');"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Mask non-data portions of array"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You probably noticed that a large portion of the data is equal to zero. The data we are using is a reduced mosaic that combines many different exposures, and that has been rotated such that not all of the array holds data. \n",
+ "\n",
+ "We want to **mask** out the non-data portions of the image array, so all of those pixels that have a value of zero don't interfere with our statistics and analyses of the data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Define the mask\n",
+ "xdf_image.mask = xdf_image.data == 0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(12, 6), sharey=True)\n",
+ "plt.tight_layout()\n",
+ "\n",
+ "# Plot the mask\n",
+ "ax1.imshow(xdf_image.mask, cmap='Greys')\n",
+ "ax1.set_xlabel('X (pixels)')\n",
+ "ax1.set_ylabel('Y (pixels)')\n",
+ "ax1.set_title('Mask')\n",
+ "\n",
+ "# Plot the masked data\n",
+ "fitsplot = ax2.imshow(np.ma.masked_where(xdf_image.mask, xdf_image),\n",
+ " norm=norm_image, cmap=cmap)\n",
+ "\n",
+ "# Define the colorbar and fix the labels\n",
+ "cbar_ax = fig.add_axes([1, 0.09, 0.03, 0.87])\n",
+ "cbar = fig.colorbar(fitsplot, cbar_ax, ticks=LogLocator())\n",
+ "\n",
+ "format_colorbar(cbar)\n",
+ "\n",
+ "cbar.set_label(r'Flux Count Rate ({})'.format(xdf_image.unit.to_string('latex')),\n",
+ " rotation=270, labelpad=30)\n",
+ "ax2.set_xlabel('X (pixels)')\n",
+ "ax2.set_title('Masked Data');"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On the left we have plotted this mask, which has a value of 1 (or True) shown in black where the data is bad, and 0 (or False) shown in white where the data is good. \n",
+ "\n",
+ "After the mask is applied to the data (on the right above) the data values \"behind\" the masked values are shown in white."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Perform scalar background estimation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now that the data are properly masked, we can calculate some basic statistical values to do a scalar estimation of the image background. \n",
+ "\n",
+ "Following the example in the previous section, we will estimate the background several ways.\n",
+ "\n",
+ "By \"scalar estimation\", we mean the calculation of a single value (such as the mean or median) to represent the value of the background for our entire two-dimensional dataset. This is in contrast to a two-dimensional background, where the estimated background is represented as an array of values that can vary spatially with the dataset. We will calculate a 2D background in the upcoming section.\n",
+ "\n",
+ "### Calculate scalar background value with sigma clipping\n",
+ "\n",
+ "Here we will calculate the mean, median, and mode of the dataset using sigma clipping. With sigma clipping, the data is iteratively clipped to exclude data points outside of a certain sigma (standard deviation), thus removing some of the noise from the data before determining statistical values.\n",
+ "\n",
+ "In the first case we account for the mask while sigma clipping and in the second case we do not. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Calculate statistics with masking\n",
+ "mean, median, std = sigma_clipped_stats(xdf_image.data, sigma=3.0, maxiters=5, mask=xdf_image.mask)\n",
+ "\n",
+ "# Calculate statistics without masking\n",
+ "stats_nomask = sigma_clipped_stats(xdf_image.data, sigma=3.0, maxiters=5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Calculate scalar background value without sigma clipping\n",
+ "\n",
+ "Here we will calculate the mean, median, and mode of the dataset without using sigma clipping. \n",
+ "\n",
+ "As above, we do this first taking into account the mask and then ignoring the mask."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mean_noclip_mask = np.mean(xdf_image.data[~xdf_image.mask])\n",
+ "median_noclip_mask = np.median(xdf_image.data[~xdf_image.mask])\n",
+ "\n",
+ "mean_noclip_nomask = np.mean(xdf_image.data)\n",
+ "median_noclip_nomask = np.median(xdf_image.data)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Mask sources, then calculate scalar background\n",
+ "\n",
+ "As discussed in [FILL IN LINK](FILL IN LINK), the most accurate estimate of the scalar background is obtained when the sources are masked first. The cell below does that procedure."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up sigma clipping\n",
+ "sigma_clip = SigmaClip(sigma=3.0, maxiters=10)\n",
+ "\n",
+ "threshold = detect_threshold(xdf_image.data, nsigma=2.0, sigma_clip=sigma_clip, mask=xdf_image.mask)\n",
+ "segment_img = detect_sources(xdf_image.data, threshold, npixels=10, mask=xdf_image.mask)\n",
+ "footprint = circular_footprint(radius=10)\n",
+ "\n",
+ "# Make the source mask a circle of radius 10 around each detected source\n",
+ "source_mask = segment_img.make_source_mask(footprint=footprint)\n",
+ "\n",
+ "# Combine the source mask with the data mask\n",
+ "full_mask = source_mask | xdf_image.mask\n",
+ "\n",
+ "# Compute the stats\n",
+ "source_mask_clip_mean, source_mask_clip_med, source_mask_clip_std = sigma_clipped_stats(xdf_image.data, sigma=3, mask=full_mask)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Compare the background estimates\n",
+ "\n",
+ "But what difference does this sigma clipping make? And how important is masking, anyway? Let's visualize these statistics to get an idea:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\n",
+ "\n",
+ "# Plot histograms of the data\n",
+ "flux_range = (-.5e-3, 1.5e-3)\n",
+ "ax1.hist(xdf_image.data[~xdf_image.mask], bins=100, range=flux_range)\n",
+ "ax2.hist(xdf_image.data[~xdf_image.mask], bins=100, range=flux_range)\n",
+ "\n",
+ "# Plot lines for each kind of mean\n",
+ "ax1.axvline(mean, label='Non-data masked and Clipped', c='C1', ls='-.', lw=3)\n",
+ "ax1.axvline(mean_noclip_mask, label='Masked', c='C2', lw=3)\n",
+ "ax1.axvline(stats_nomask[0], label='Clipped', c='C3', ls=':', lw=5)\n",
+ "ax1.axvline(source_mask_clip_mean, label='Sources masked and clipped', c='C4', lw=3)\n",
+ "ax1.axvline(mean_noclip_nomask, label='Neither', c='C5', ls='--', lw=3)\n",
+ "\n",
+ "ax1.set_xlim(flux_range)\n",
+ "ax1.set_xlabel(r'Flux Count Rate ({})'.format(xdf_image.unit.to_string('latex')), fontsize=14)\n",
+ "ax1.set_ylabel('Frequency', fontsize=14)\n",
+ "ax1.set_title('Effect of Sigma-Clipping \\n and Masking on Mean', fontsize=16)\n",
+ "\n",
+ "# Plot lines for each kind of median\n",
+ "# Note: use np.ma.median rather than np.median for masked arrays\n",
+ "ax2.axvline(median, label='Non-data masked and Clipped', c='C1', ls='-.', lw=3)\n",
+ "ax2.axvline(median_noclip_mask, label='Masked', c='C2', lw=3)\n",
+ "ax2.axvline(source_mask_clip_med, label='Sources masked and clipped', c='C4', lw=3)\n",
+ "ax2.axvline(stats_nomask[1], label='Clipped', c='C3', ls=':', lw=5)\n",
+ "ax2.axvline(median_noclip_nomask, label='Neither', c='C5', ls='--', lw=3)\n",
+ "\n",
+ "\n",
+ "ax2.set_xlim(flux_range)\n",
+ "ax2.set_xlabel(r'Flux Count Rate ({})'.format(xdf_image.unit.to_string('latex')), fontsize=14)\n",
+ "ax2.set_title('Effect of Sigma-Clipping \\n and Masking on Median', fontsize=16)\n",
+ "\n",
+ "# Add legend\n",
+ "ax1.legend(fontsize=11, loc='lower center', bbox_to_anchor=(1.1, -0.55), ncol=2, handlelength=6);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Masking makes a big difference to the mean in this case because so many of the pixels in the original image contain no data. Sigma clipping also makes some difference, moving the mean value closer to zero. Masking out the sources moves the mean below zero, which is not surprising in this case. The distribution of pixels is clearly near zero, and masking out the sources means removing the largest positive values.\n",
+ "\n",
+ "Note that the median is about the same no matter which approach you use.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Subtract scalar background value\n",
+ "\n",
+ "But enough looking at numbers, let's actually remove the background from the data. By using the `subtract()` method of the `CCDData` class, we can subtract the mean background while maintaining the metadata and mask of our original CCDData object:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Calculate the scalar background subtraction, maintaining metadata, unit, and mask\n",
+ "xdf_scalar_bkgdsub = xdf_image.subtract(mean * u.electron / u.s)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(12, 6), sharey=True)\n",
+ "plt.tight_layout()\n",
+ "\n",
+ "# Plot the original data\n",
+ "fitsplot = ax1.imshow(np.ma.masked_where(xdf_image.mask, xdf_image), norm=norm_image)\n",
+ "ax1.set_xlabel('X (pixels)')\n",
+ "ax1.set_ylabel('Y (pixels)')\n",
+ "ax1.set_title('Original Data')\n",
+ "\n",
+ "# Plot the subtracted data\n",
+ "fitsplot = ax2.imshow(np.ma.masked_where(xdf_scalar_bkgdsub.mask, xdf_scalar_bkgdsub), norm=norm_image)\n",
+ "ax2.set_xlabel('X (pixels)')\n",
+ "ax2.set_title('Scalar Background-Subtracted Data')\n",
+ "\n",
+ "# Define the colorbar...\n",
+ "cbar_ax = fig.add_axes([1, 0.09, 0.03, 0.87])\n",
+ "\n",
+ "cbar = fig.colorbar(fitsplot, cbar_ax, ticks=LogLocator())\n",
+ "\n",
+ "format_colorbar(cbar)\n",
+ "\n",
+ "cbar.set_label(r'Flux Count Rate ({})'.format(xdf_image.unit.to_string('latex')),\n",
+ " rotation=270, labelpad=30)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that both plots above use the same normalization scheme, represented by the colorbar on the right. That is to say, if two pixels have the same color in both arrays, they have the same value.\n",
+ "\n",
+ "Subtracting the scalar background does not have much effect here because the background is so close to zero.\n",
+ "\n",
+ "One note about keeping the color scale the same, as we do in this example: if there is a significant scalar background then the effect of scalar background subtraction will be to simply make the entire image look darker. That will be illustrated in the example in the next section of the guide."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Perform 2-D background estimation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `Background2D` class in `photutils` allows users to model 2-dimensional backgrounds, by calculating the mean or median in small boxes, and smoothing these boxes to reconstruct a continuous 2D background. The class includes the following arguments/attributes:\n",
+ "* **`box_size`** — the size of the boxes used to calculate the background. This should be larger than individual sources, yet still small enough to encompass changes in the background.\n",
+ "* **`filter_size`** — the size of the median filter used to smooth the final 2D background. The dimension should be odd along both axes.\n",
+ "* **`filter_threshold`** — threshold below which the smoothing median filter will not be applied.\n",
+ "* **`sigma_clip`** — an ` astropy.stats.SigmaClip` object that is used to specify the sigma and number of iterations used to sigma-clip the data before background calculations are performed.\n",
+ "* **`bkg_estimator`** — the method used to perform the background calculation in each box (mean, median, SExtractor algorithm, etc.).\n",
+ "\n",
+ "For this example, we will use the `MeanBackground` estimator. Note though, that there is little or no spatial variation apparent in the background of this image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from photutils.background import Background2D, MeanBackground"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "sigma_clip = SigmaClip(sigma=3., maxiters=5)\n",
+ "bkg_estimator = MeanBackground()\n",
+ "bkg = Background2D(xdf_image, box_size=200, filter_size=(9, 9), mask=xdf_image.mask,\n",
+ " sigma_clip=sigma_clip, bkg_estimator=bkg_estimator)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "So, what does this 2D background look like? Where were the boxes placed?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, ax1 = plt.subplots(1, 1, figsize=(8, 8))\n",
+ "\n",
+ "# Plot the background\n",
+ "fitsplot = ax1.imshow(np.ma.masked_where(xdf_image.mask, bkg.background.data ), norm=norm_image)\n",
+ "\n",
+ "# Plot the meshes\n",
+ "bkg.plot_meshes(outlines=True, color='lightgrey')\n",
+ "\n",
+ "# Define the colorbar\n",
+ "cbar = plt.colorbar(fitsplot, fraction=0.046, pad=0.04, ticks=LogLocator())\n",
+ "\n",
+ "format_colorbar(cbar)\n",
+ "\n",
+ "# Define labels\n",
+ "cbar.set_label(r'Flux Count Rate ({})'.format(xdf_image.unit.to_string('latex')),\n",
+ " rotation=270, labelpad=30)\n",
+ "ax1.set_xlabel('X (pixels)')\n",
+ "ax1.set_ylabel('Y (pixels)')\n",
+ "ax1.set_title('2D Estimated Background');"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You might notice that not all areas of the background array have mesh boxes over them (look for those boxes that do not have a `+`). If you compare this background array with the original data, you'll see that these un-boxed areas contain particularly bright sources, and thus are not being included in the background estimate.\n",
+ "\n",
+ "The background is also, as we expected in this case, close to uniform.\n",
+ "\n",
+ "And how does the data look if we use this background subtraction method (again maintaining the attributes of the CCDData object)?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Calculate the 2D background subtraction, maintaining metadata, unit, and mask\n",
+ "xdf_2d_bkgdsub = xdf_image.subtract(bkg.background)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(12, 6), sharey=True)\n",
+ "plt.tight_layout()\n",
+ "\n",
+ "# Plot the scalar-subtracted data\n",
+ "fitsplot = ax1.imshow(np.ma.masked_where(xdf_scalar_bkgdsub.mask, xdf_scalar_bkgdsub), norm=norm_image)\n",
+ "cbar.set_label(r'Flux Count Rate ({})'.format(xdf_image.unit.to_string('latex')),\n",
+ " rotation=270, labelpad=30)\n",
+ "ax1.set_ylabel('Y (pixels)')\n",
+ "ax1.set_xlabel('X (pixels)')\n",
+ "ax1.set_title('Scalar Background-Subtracted Data')\n",
+ "\n",
+ "# Plot the 2D-subtracted data\n",
+ "fitsplot = ax2.imshow(np.ma.masked_where(xdf_2d_bkgdsub.mask, xdf_2d_bkgdsub), norm=norm_image)\n",
+ "ax2.set_xlabel('X (pixels)')\n",
+ "ax2.set_title('2D Background-Subtracted Data')\n",
+ "\n",
+ "# Define the colorbar...\n",
+ "cbar_ax = fig.add_axes([1, 0.09, 0.03, 0.87])\n",
+ "\n",
+ "cbar = fig.colorbar(fitsplot, cbar_ax, ticks=LogLocator())\n",
+ "cbar.ax.yaxis.set_minor_locator(LogLocator(subs=range(1, 10)))\n",
+ "\n",
+ "# ...and force the labels to be displayed as powers of ten\n",
+ "cbar.ax.set_yticks([1e-4, 1e-3, 1e-2])\n",
+ "labels = [f'$10^{{{pow:.0f}}}$' for pow in np.log10(cbar.ax.get_yticks())]\n",
+ "cbar.ax.set_yticklabels(labels)\n",
+ "\n",
+ "cbar.set_label(r'Flux Count Rate ({})'.format(xdf_image.unit.to_string('latex')),\n",
+ " rotation=270, labelpad=30)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In this case 2D background subtraction was not necessary, as you might expect from an image like the XDF that has already been processed.\n",
+ "\n",
+ "In the next section we look at an image that does have a gradient in the background from moonlight."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/photometry/01.01.03-Background-estimation-FEDER.ipynb b/v/pdev/_sources/notebooks/photometry/01.01.03-Background-estimation-FEDER.ipynb
new file mode 100644
index 00000000..cae66a71
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/01.01.03-Background-estimation-FEDER.ipynb
@@ -0,0 +1,513 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Removing a background gradient"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Which data are used in this notebook?\n",
+ "\n",
+ "The image in this notebook is of the field of the star [TIC 125489084](https://exofop.ipac.caltech.edu/tess/target.php?id=125489084) on a night when the moon was nearly full. The moonlight caused a smooth gradient across the background of the image."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Import necessary packages"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First, let's import packages that we will use to perform arithmetic functions and visualize data:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "from astropy.io import fits\n",
+ "from astropy.nddata import CCDData\n",
+ "import astropy.units as u\n",
+ "from astropy.stats import sigma_clipped_stats, SigmaClip\n",
+ "from astropy.visualization import ImageNormalize, LogStretch, AsymmetricPercentileInterval\n",
+ "import matplotlib.pyplot as plt\n",
+ "from matplotlib.ticker import LogLocator\n",
+ "\n",
+ "from photutils.segmentation import detect_threshold, detect_sources\n",
+ "from photutils.utils import circular_footprint\n",
+ "\n",
+ "# Show plots in the notebook\n",
+ "%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's also define some `matplotlib` parameters, such as title font size and the dpi, to make sure our plots look nice. To make it quick, we'll do this by loading a [style file shared with the other photutils tutorials](../photutils_notebook_style.mplstyle) into `pyplot`. We will use this style file for all the notebook tutorials. (See [here](https://matplotlib.org/users/customizing.html) to learn more about customizing `matplotlib`.)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.style.use('../photutils_notebook_style.mplstyle')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Load the data\n",
+ "\n",
+ "Throughout this notebook, we are going to store our images in Python using a `CCDData` object (see [Astropy documentation](http://docs.astropy.org/en/stable/nddata/index.html#ccddata-class-for-images)), which contains a `numpy` array in addition to metadata such as uncertainty, masks, and units."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccd_image = CCDData.read('TIC_125489084.01-S001-R055-C001-ip.fit.bz2')\n",
+ "data = ccd_image.data\n",
+ "header = ccd_image.header\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's look at the data below. The image normalization we use here is one that is often convenient to use for images with stellar sources."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, ax1 = plt.subplots(1, 1, figsize=(8, 8))\n",
+ "\n",
+ "# Set up the normalization and colormap\n",
+ "norm_image = ImageNormalize(ccd_image, interval=AsymmetricPercentileInterval(30, 99.5))\n",
+ "cmap = plt.get_cmap('viridis')\n",
+ "\n",
+ "# Plot the data\n",
+ "fitsplot = ax1.imshow(np.ma.masked_where(ccd_image.mask, ccd_image),\n",
+ " norm=norm_image, cmap=cmap)\n",
+ "\n",
+ "# Define the colorbar\n",
+ "cbar = plt.colorbar(fitsplot, fraction=0.046, pad=0.04)\n",
+ "\n",
+ "# Define labels\n",
+ "cbar.set_label(r'Counts ({})'.format(ccd_image.unit.to_string('latex')),\n",
+ " rotation=270, labelpad=30)\n",
+ "ax1.set_xlabel('X (pixels)')\n",
+ "ax1.set_ylabel('Y (pixels)');"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Unlike the XDF image, there are not any large sectregionsions of the image that contain no data. For consistency with the prior example, we create a mask for this image too."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccd_image.mask = ccd_image.data == 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Perform scalar background estimation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now that the data are properly masked, we can calculate some basic statistical values to do a scalar estimation of the image background. \n",
+ "\n",
+ "By \"scalar estimation\", we mean the calculation of a single value (such as the mean or median) to represent the value of the background for our entire two-dimensional dataset. This is in contrast to a two-dimensional background, where the estimated background is represented as an array of values that can vary spatially with the dataset. We will calculate a 2D background in the upcoming section.\n",
+ "\n",
+ "### Calculate scalar background value\n",
+ "\n",
+ "Here we will calculate the mean, median, and mode of the dataset using sigma clipping. With sigma clipping, the data is iteratively clipped to exclude data points outside of a certain sigma (standard deviation), thus removing some of the noise from the data before determining statistical values.\n",
+ "\n",
+ "In the first case we account for the mask while sigma clipping and in the second case we do not. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Calculate statistics with masking\n",
+ "mean, median, std = sigma_clipped_stats(ccd_image.data, sigma=3.0, maxiters=5, mask=ccd_image.mask)\n",
+ "\n",
+ "# Calculate statistics without masking\n",
+ "stats_nomask = sigma_clipped_stats(ccd_image.data, sigma=3.0, maxiters=5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Calculate scalar background value without sigma clipping\n",
+ "\n",
+ "Here we will calculate the mean, median, and mode of the dataset without using sigma clipping. \n",
+ "\n",
+ "As above, we do this first taking into account the mask and then ignoring the mask."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mean_noclip_mask = np.mean(ccd_image.data[~ccd_image.mask])\n",
+ "median_noclip_mask = np.median(ccd_image.data[~ccd_image.mask])\n",
+ "\n",
+ "mean_noclip_nomask = np.mean(ccd_image.data)\n",
+ "median_noclip_nomask = np.median(ccd_image.data)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Mask sources, then calculate scalar background\n",
+ "\n",
+ "As discussed in [FILL IN LINK](FILL IN LINK), the most accurate estimate of the scalar background is obtained when the sources are masked first. The cell below does that procedure."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up sigma clipping\n",
+ "sigma_clip = SigmaClip(sigma=3.0, maxiters=10)\n",
+ "\n",
+ "threshold = detect_threshold(ccd_image.data, nsigma=2.0, sigma_clip=sigma_clip, mask=ccd_image.mask)\n",
+ "segment_img = detect_sources(ccd_image.data, threshold, npixels=10, mask=ccd_image.mask)\n",
+ "footprint = circular_footprint(radius=10)\n",
+ "\n",
+ "# Make the source mask a circle of radius 10 around each detected source\n",
+ "source_mask = segment_img.make_source_mask(footprint=footprint)\n",
+ "\n",
+ "# Compute the stats\n",
+ "source_mask_clip_mean, source_mask_clip_med, source_mask_clip_std = sigma_clipped_stats(data, sigma=3, mask=source_mask)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "But what difference does this sigma clipping make? And how important is masking, anyway? Let's visualize these statistics to get an idea:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\n",
+ "\n",
+ "# Plot histograms of the data\n",
+ "flux_range = (100, 500)\n",
+ "ax1.hist(ccd_image.data[~ccd_image.mask], bins=500, range=flux_range)\n",
+ "ax2.hist(ccd_image.data[~ccd_image.mask], bins=500, range=flux_range)\n",
+ "\n",
+ "# Plot lines for each kind of mean\n",
+ "ax1.axvline(mean, label='Non-data masked and Clipped', c='C1', ls='-.', ms=10, lw=3)\n",
+ "ax1.axvline(mean_noclip_mask, label='Masked', c='C2', lw=3)\n",
+ "ax1.axvline(stats_nomask[0], label='Clipped', c='C3', ls=':', lw=3)\n",
+ "ax1.axvline(source_mask_clip_mean, label='Sources masked and clipped', c='C4', lw=3)\n",
+ "ax1.axvline(mean_noclip_nomask, label='Neither', c='C5', ls='--', lw=3)\n",
+ "\n",
+ "ax1.set_xlim(150, 220)\n",
+ "ax1.set_xlabel(r'Counts ({})'.format(ccd_image.unit.to_string('latex')), fontsize=14)\n",
+ "ax1.set_ylabel('Frequency', fontsize=14)\n",
+ "ax1.set_title('Mean pixel value', fontsize=16)\n",
+ "\n",
+ "# Plot lines for each kind of median\n",
+ "# Note: use np.ma.median rather than np.median for masked arrays\n",
+ "ax2.axvline(median, label='Non-data masked and Clipped', c='C1', ls='-.', lw=3)\n",
+ "ax2.axvline(median_noclip_mask, label='Masked', c='C2', lw=3)\n",
+ "ax2.axvline(source_mask_clip_med, label='Sources masked and clipped', c='C4', lw=3)\n",
+ "ax2.axvline(stats_nomask[1], label='Clipped', c='C3', ls=':', lw=3)\n",
+ "ax2.axvline(median_noclip_nomask, label='Neither', c='C5', ls='--', lw=3)\n",
+ "\n",
+ "\n",
+ "ax2.set_xlim(160, 170) # flux_range)\n",
+ "ax2.set_xlabel(r'Flux Count Rate ({})'.format(ccd_image.unit.to_string('latex')), fontsize=14)\n",
+ "ax2.set_title('Median pixel value', fontsize=16)\n",
+ "\n",
+ "# Add legend\n",
+ "ax1.legend(fontsize=11, loc='lower center', bbox_to_anchor=(1.1, -0.55), ncol=2, handlelength=6);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The contrast with the previous section that used XDF image is striking. In this case msakng makes no difference because there were no non-data areas to mask. Signma clipping makes a much larger difference than in the XDF case because of the presence of a number of relatively bright stars in this image.\n",
+ "\n",
+ "Once sigma clipping has been done it makes little difference whether sources are also masked. \n",
+ "\n",
+ "The median value of the image is essentially the same no matter how it is calculated. Though the stars that are present are bright, they are a small fraction of the image. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Subtract scalar background value\n",
+ "\n",
+ "We subtract the mean value of the scalar background below then discuss the resulting image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Calculate the scalar background subtraction, maintaining metadata, unit, and mask\n",
+ "ccd_scalar_bkgdsub = ccd_image.subtract(mean * u.adu)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(12, 6), sharey=True)\n",
+ "plt.tight_layout()\n",
+ "\n",
+ "# Plot the original data\n",
+ "fitsplot = ax1.imshow(np.ma.masked_where(ccd_image.mask, ccd_image), norm=norm_image)\n",
+ "ax1.set_xlabel('X (pixels)')\n",
+ "ax1.set_ylabel('Y (pixels)')\n",
+ "ax1.set_title('Original Data')\n",
+ "\n",
+ "# Plot the subtracted data\n",
+ "fitsplot = ax2.imshow(ccd_scalar_bkgdsub, norm=norm_image)\n",
+ "ax2.set_xlabel('X (pixels)')\n",
+ "ax2.set_title('Scalar Background-Subtracted Data')\n",
+ "\n",
+ "# Define the colorbar...\n",
+ "cbar_ax = fig.add_axes([1, 0.09, 0.03, 0.87])\n",
+ "\n",
+ "cbar = fig.colorbar(fitsplot, cbar_ax)\n",
+ "\n",
+ "cbar.set_label(r'Flux Count Rate ({})'.format(ccd_image.unit.to_string('latex')),\n",
+ " rotation=270, labelpad=30)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The result here may look surprising at first: background subtraction seems to have made everything much fainter. That is an artifact of using the same image normalization for both images even though the range of their data values is now very different.\n",
+ "\n",
+ "In the image on the left the average data value is roughly `165 adu`. In the image on the right the average value is roughly `0 adu` by design, since we subtracted the mean of the first image from every pixel.\n",
+ "\n",
+ "If we redo the plot but use a different image normalization for each it becomes clearer that the scalar background subtraction has one effect: moving the mean pixel value in the image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(12, 6), sharey=True)\n",
+ "plt.tight_layout()\n",
+ "\n",
+ "# Plot the original data\n",
+ "original_image = ax1.imshow(np.ma.masked_where(ccd_image.mask, ccd_image), norm=norm_image)\n",
+ "ax1.set_xlabel('X (pixels)')\n",
+ "ax1.set_ylabel('Y (pixels)')\n",
+ "ax1.set_title('Original Data')\n",
+ "cb_orig = fig.colorbar(original_image, ax=ax1, shrink=0.8)\n",
+ "cb_orig.set_label(f'Counts ({ccd_image.unit})')\n",
+ "\n",
+ "# Plot the subtracted data on its own color scale\n",
+ "norm_image_sub = ImageNormalize(ccd_scalar_bkgdsub, interval=AsymmetricPercentileInterval(30, 99.5))\n",
+ "\n",
+ "subtracted_image = ax2.imshow(ccd_scalar_bkgdsub, norm=norm_image_sub)\n",
+ "ax2.set_xlabel('X (pixels)')\n",
+ "ax2.set_title('Scalar Background-Subtracted Data')\n",
+ "cb_sub = fig.colorbar(subtracted_image, ax=ax2, shrink=0.8)\n",
+ "cb_sub.set_label(f'Counts ({ccd_image.unit})')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Perform 2-D background estimation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `Background2D` class allows users to model 2-dimensional backgrounds, by calculating the mean or median in small boxes, and smoothing these boxes to reconstruct a continuous 2D background. The class includes the following arguments/attributes:\n",
+ "* **`box_size`** — the size of the boxes used to calculate the background. This should be larger than individual sources, yet still small enough to encompass changes in the background.\n",
+ "* **`filter_size`** — the size of the median filter used to smooth the final 2D background. The dimension should be odd along both axes.\n",
+ "* **`filter_threshold`** — threshold below which the smoothing median filter will not be applied.\n",
+ "* **`sigma_clip`** — an ` astropy.stats.SigmaClip` object that is used to specify the sigma and number of iterations used to sigma-clip the data before background calculations are performed.\n",
+ "* **`bkg_estimator`** — the method used to perform the background calculation in each box (mean, median, SExtractor algorithm, etc.).\n",
+ "\n",
+ "For this example, we will use the `MeanBackground` estimator."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from photutils.background import Background2D, MeanBackground"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "sigma_clip = SigmaClip(sigma=3., maxiters=5)\n",
+ "bkg_estimator = MeanBackground()\n",
+ "bkg = Background2D(ccd_image, box_size=200, filter_size=(9, 9), mask=ccd_image.mask,\n",
+ " sigma_clip=sigma_clip, bkg_estimator=bkg_estimator)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, ax1 = plt.subplots(1, 1, figsize=(8, 8))\n",
+ "\n",
+ "# Plot the background\n",
+ "fitsplot = ax1.imshow(np.ma.masked_where(ccd_image.mask, bkg.background.data), norm=norm_image)\n",
+ "\n",
+ "# Define the colorbar\n",
+ "cbar = plt.colorbar(fitsplot, fraction=0.046, pad=0.04)\n",
+ "\n",
+ "# Define labels\n",
+ "cbar.set_label(r'Flux Count Rate ({})'.format(ccd_image.unit.to_string('latex')),\n",
+ " rotation=270, labelpad=30)\n",
+ "ax1.set_xlabel('X (pixels)')\n",
+ "ax1.set_ylabel('Y (pixels)')\n",
+ "ax1.set_title('2D Estimated Background');"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Calculate the 2D background subtraction, maintaining metadata, unit, and mask\n",
+ "ccd_2d_bkgdsub = ccd_image.subtract(bkg.background)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set up the figure with subplots\n",
+ "fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(12, 6), sharey=True)\n",
+ "plt.tight_layout()\n",
+ "\n",
+ "fitsplot = ax1.imshow(ccd_scalar_bkgdsub, norm=norm_image_sub)\n",
+ "\n",
+ "ax1.set_ylabel('Y (pixels)')\n",
+ "ax1.set_xlabel('X (pixels)')\n",
+ "ax1.set_title('Scalar Background-Subtracted Data')\n",
+ "\n",
+ "# Plot the 2D-subtracted data\n",
+ "fitsplot = ax2.imshow(np.ma.masked_where(ccd_2d_bkgdsub.mask, ccd_2d_bkgdsub), norm=norm_image_sub)\n",
+ "ax2.set_xlabel('X (pixels)')\n",
+ "ax2.set_title('2D Background-Subtracted Data')\n",
+ "\n",
+ "# Define the colorbar...\n",
+ "cbar_ax = fig.add_axes([1, 0.09, 0.03, 0.87])\n",
+ "\n",
+ "cbar = fig.colorbar(fitsplot, cbar_ax)\n",
+ "\n",
+ "cbar.set_label(r'Counts ({})'.format(ccd_image.unit.to_string('latex')),\n",
+ " rotation=270, labelpad=30)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note how much more even the 2D background-subtracted image looks; especially the difference between these two images in the bottom left and top right corners. This makes sense, as the background that `Background2D` identified was a gradient from the bottom left corner to the top right corner."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/v/pdev/_sources/notebooks/photometry/01.02-IRAF-like-photutils.ipynb b/v/pdev/_sources/notebooks/photometry/01.02-IRAF-like-photutils.ipynb
new file mode 100644
index 00000000..03fbe0fb
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/01.02-IRAF-like-photutils.ipynb
@@ -0,0 +1,45 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# IRAF-like: photutils\n",
+ "\n",
+ "[photutils]() provides a couple of options for stellar source detection that will be familiar to users of IRAF. One is DAOFIND and the other is IRAF's starfind. The recommendation is to use DAOFIND because it is more general than starfind (e.g. it allows elliptical sources) and detects more sources. This notebook will focus on DAOFIND, implemented in photutils by the class `DAOStarFinder`.\n",
+ "\n",
+ "Both methods find sources above a threshold that is specified as a multiple of the background noise level, and both require that the background be subtracted from the image.\n",
+ "\n",
+ "You can use any of the background subtraction methods that you like; often simply subtracting the median will be adequate, which is what we will do in this notebook. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## DAOPHOT"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/01.03-SExtractor-like-sep-and-photutils.ipynb b/v/pdev/_sources/notebooks/photometry/01.03-SExtractor-like-sep-and-photutils.ipynb
new file mode 100644
index 00000000..b7f45a47
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/01.03-SExtractor-like-sep-and-photutils.ipynb
@@ -0,0 +1,30 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# SExtractor-like: sep and photutils\n",
+ "+ [sep: Python-wrapped innards of SExtractor](#sep:-Python-wrapped-innards-of-SExtractor)\n",
+ "+ [photutils: image segmentation](#photutils:-image-segmentation)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## sep: Python-wrapped innards of SExtractor"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## photutils: image segmentation"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/02.00-Background-removal-for-photometry.ipynb b/v/pdev/_sources/notebooks/photometry/02.00-Background-removal-for-photometry.ipynb
new file mode 100644
index 00000000..eeb6baf7
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/02.00-Background-removal-for-photometry.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Background removal for photometry\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/02.01-Remove-smoothed-background.ipynb b/v/pdev/_sources/notebooks/photometry/02.01-Remove-smoothed-background.ipynb
new file mode 100644
index 00000000..cef78168
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/02.01-Remove-smoothed-background.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Remove smoothed background\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/02.02-Remove-local-background.ipynb b/v/pdev/_sources/notebooks/photometry/02.02-Remove-local-background.ipynb
new file mode 100644
index 00000000..7132ce79
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/02.02-Remove-local-background.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Remove local background\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/02.03-Both.ipynb b/v/pdev/_sources/notebooks/photometry/02.03-Both.ipynb
new file mode 100644
index 00000000..5d1d6c52
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/02.03-Both.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Both\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/03.00-Aperture-Photometry.ipynb b/v/pdev/_sources/notebooks/photometry/03.00-Aperture-Photometry.ipynb
new file mode 100644
index 00000000..992842e3
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/03.00-Aperture-Photometry.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Aperture Photometry\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/03.01-When-NOT-to-use-aperture-photometry.ipynb b/v/pdev/_sources/notebooks/photometry/03.01-When-NOT-to-use-aperture-photometry.ipynb
new file mode 100644
index 00000000..1e6de6d4
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/03.01-When-NOT-to-use-aperture-photometry.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# When NOT to use aperture photometry\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/03.02-Choosing-the-aperture-size.ipynb b/v/pdev/_sources/notebooks/photometry/03.02-Choosing-the-aperture-size.ipynb
new file mode 100644
index 00000000..6b006038
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/03.02-Choosing-the-aperture-size.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Choosing the aperture size\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/03.03-Background-subtraction.ipynb b/v/pdev/_sources/notebooks/photometry/03.03-Background-subtraction.ipynb
new file mode 100644
index 00000000..fa676e54
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/03.03-Background-subtraction.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Background subtraction\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/03.04-Performing-the-photometry.ipynb b/v/pdev/_sources/notebooks/photometry/03.04-Performing-the-photometry.ipynb
new file mode 100644
index 00000000..8f1483ab
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/03.04-Performing-the-photometry.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Performing the photometry\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/03.05-Error-estimation.ipynb b/v/pdev/_sources/notebooks/photometry/03.05-Error-estimation.ipynb
new file mode 100644
index 00000000..c6ba1779
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/03.05-Error-estimation.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Error estimation\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/03.06-Instrumental-magnitudes.ipynb b/v/pdev/_sources/notebooks/photometry/03.06-Instrumental-magnitudes.ipynb
new file mode 100644
index 00000000..1184f42a
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/03.06-Instrumental-magnitudes.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Instrumental magnitudes\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/04.00-PSF-Photometry.ipynb b/v/pdev/_sources/notebooks/photometry/04.00-PSF-Photometry.ipynb
new file mode 100644
index 00000000..c42c7838
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/04.00-PSF-Photometry.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# PSF Photometry\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/04.01-When-NOT-to-use-PSF-photometry.ipynb b/v/pdev/_sources/notebooks/photometry/04.01-When-NOT-to-use-PSF-photometry.ipynb
new file mode 100644
index 00000000..dd8276f2
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/04.01-When-NOT-to-use-PSF-photometry.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# When NOT to use PSF photometry\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/04.02-Determining-an-instrument's-PSF.ipynb b/v/pdev/_sources/notebooks/photometry/04.02-Determining-an-instrument's-PSF.ipynb
new file mode 100644
index 00000000..6291608d
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/04.02-Determining-an-instrument's-PSF.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Determining an instrument's PSF\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/04.03-Background-subtraction.ipynb b/v/pdev/_sources/notebooks/photometry/04.03-Background-subtraction.ipynb
new file mode 100644
index 00000000..fa676e54
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/04.03-Background-subtraction.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Background subtraction\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/04.04-Performing-the-photometry.ipynb b/v/pdev/_sources/notebooks/photometry/04.04-Performing-the-photometry.ipynb
new file mode 100644
index 00000000..8f1483ab
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/04.04-Performing-the-photometry.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Performing the photometry\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/04.05-Error-estimation.ipynb b/v/pdev/_sources/notebooks/photometry/04.05-Error-estimation.ipynb
new file mode 100644
index 00000000..c6ba1779
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/04.05-Error-estimation.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Error estimation\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/04.06-Instrumental-magnitudes.ipynb b/v/pdev/_sources/notebooks/photometry/04.06-Instrumental-magnitudes.ipynb
new file mode 100644
index 00000000..1184f42a
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/04.06-Instrumental-magnitudes.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Instrumental magnitudes\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/05.00-Transforming-to-the-standard-magnitude-system.ipynb b/v/pdev/_sources/notebooks/photometry/05.00-Transforming-to-the-standard-magnitude-system.ipynb
new file mode 100644
index 00000000..4d68c3c0
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/05.00-Transforming-to-the-standard-magnitude-system.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Transforming to the standard magnitude system\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/05.01-Removing-atmospheric-and-instrumental-effects-in-one-step.ipynb b/v/pdev/_sources/notebooks/photometry/05.01-Removing-atmospheric-and-instrumental-effects-in-one-step.ipynb
new file mode 100644
index 00000000..045cc876
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/05.01-Removing-atmospheric-and-instrumental-effects-in-one-step.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Removing atmospheric and instrumental effects in one step\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photometry/05.02-Evaluating-the-quality-of-the-transforms.ipynb b/v/pdev/_sources/notebooks/photometry/05.02-Evaluating-the-quality-of-the-transforms.ipynb
new file mode 100644
index 00000000..22552763
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photometry/05.02-Evaluating-the-quality-of-the-transforms.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Evaluating the quality of the transforms\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/v/pdev/_sources/notebooks/photutils/02_source_detection/02_source_detection.ipynb b/v/pdev/_sources/notebooks/photutils/02_source_detection/02_source_detection.ipynb
new file mode 100644
index 00000000..636ecbc7
--- /dev/null
+++ b/v/pdev/_sources/notebooks/photutils/02_source_detection/02_source_detection.ipynb
@@ -0,0 +1,1080 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
astropy
, matplotlib
, and photutils
, or this notebook may not work properly. Or, if you don't want to handle packages individually, you can always use (and keep updated!) the AstroConda distribution.\n",
+ " \n",
+ "DAOStarFinder
algorithm with a smaller threshold (like 5σ), and plot the sources that it finds. Do the same, but with a larger threshold (like 100σ). How did changing the threshold affect the results?\n",
+ "\n",
+ "IRAFStarFinder
algorithm with a smaller full-width-half-max (FWHM) – try 3 pixels – and plot the sources that it finds. Do the same, but with a larger FWHM (like 10 pixels). How did changing the FWHM affect the results? What astronomical objects might be better captures by smaller FWHM? Larger?\n",
+ "\n",
+ "SegmentationImage
, but alter the threshold and the minimum number of pixels in a source. How does changing the threshold affect the results? What about changing the number of pixels?\n",
+ "\n",
+ "r
). Observe how changing this value affects the apertures that are created.\n",
+ "\n",
+ "astropy
, matplotlib
, and photutils
, or this notebook may not work properly. Or, if you don't want to handle packages individually, you can always use (and keep updated!) the AstroConda distribution.\n",
+ " \n",
+ "subpixel
aperture placement method instead of the default exact
method. How does this affect the count sum calculated for those apertures?\n",
+ "\n",
+ "Short
+ */ + .o-tooltip--left { + position: relative; + } + + .o-tooltip--left:after { + opacity: 0; + visibility: hidden; + position: absolute; + content: attr(data-tooltip); + padding: .2em; + font-size: .8em; + left: -.2em; + background: grey; + color: white; + white-space: nowrap; + z-index: 2; + border-radius: 2px; + transform: translateX(-102%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); +} + +.o-tooltip--left:hover:after { + display: block; + opacity: 1; + visibility: visible; + transform: translateX(-100%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); + transition-delay: .5s; +} + +/* By default the copy button shouldn't show up when printing a page */ +@media print { + button.copybtn { + display: none; + } +} diff --git a/v/pdev/_static/copybutton.js b/v/pdev/_static/copybutton.js new file mode 100644 index 00000000..2ea7ff3e --- /dev/null +++ b/v/pdev/_static/copybutton.js @@ -0,0 +1,248 @@ +// Localization support +const messages = { + 'en': { + 'copy': 'Copy', + 'copy_to_clipboard': 'Copy to clipboard', + 'copy_success': 'Copied!', + 'copy_failure': 'Failed to copy', + }, + 'es' : { + 'copy': 'Copiar', + 'copy_to_clipboard': 'Copiar al portapapeles', + 'copy_success': '¡Copiado!', + 'copy_failure': 'Error al copiar', + }, + 'de' : { + 'copy': 'Kopieren', + 'copy_to_clipboard': 'In die Zwischenablage kopieren', + 'copy_success': 'Kopiert!', + 'copy_failure': 'Fehler beim Kopieren', + }, + 'fr' : { + 'copy': 'Copier', + 'copy_to_clipboard': 'Copier dans le presse-papier', + 'copy_success': 'Copié !', + 'copy_failure': 'Échec de la copie', + }, + 'ru': { + 'copy': 'Скопировать', + 'copy_to_clipboard': 'Скопировать в буфер', + 'copy_success': 'Скопировано!', + 'copy_failure': 'Не удалось скопировать', + }, + 'zh-CN': { + 'copy': '复制', + 'copy_to_clipboard': '复制到剪贴板', + 'copy_success': '复制成功!', + 'copy_failure': '复制失败', + }, + 'it' : { + 'copy': 'Copiare', + 'copy_to_clipboard': 'Copiato negli appunti', + 'copy_success': 'Copiato!', + 'copy_failure': 'Errore durante la copia', + } +} + +let locale = 'en' +if( document.documentElement.lang !== undefined + && messages[document.documentElement.lang] !== undefined ) { + locale = document.documentElement.lang +} + +let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; +if (doc_url_root == '#') { + doc_url_root = ''; +} + +/** + * SVG files for our copy buttons + */ +let iconCheck = `` + +// If the user specified their own SVG use that, otherwise use the default +let iconCopy = ``; +if (!iconCopy) { + iconCopy = `` +} + +/** + * Set up copy/paste for code blocks + */ + +const runWhenDOMLoaded = cb => { + if (document.readyState != 'loading') { + cb() + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', cb) + } else { + document.attachEvent('onreadystatechange', function() { + if (document.readyState == 'complete') cb() + }) + } +} + +const codeCellId = index => `codecell${index}` + +// Clears selected text since ClipboardJS will select the text when copying +const clearSelection = () => { + if (window.getSelection) { + window.getSelection().removeAllRanges() + } else if (document.selection) { + document.selection.empty() + } +} + +// Changes tooltip text for a moment, then changes it back +// We want the timeout of our `success` class to be a bit shorter than the +// tooltip and icon change, so that we can hide the icon before changing back. +var timeoutIcon = 2000; +var timeoutSuccessClass = 1500; + +const temporarilyChangeTooltip = (el, oldText, newText) => { + el.setAttribute('data-tooltip', newText) + el.classList.add('success') + // Remove success a little bit sooner than we change the tooltip + // So that we can use CSS to hide the copybutton first + setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) + setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) +} + +// Changes the copy button icon for two seconds, then changes it back +const temporarilyChangeIcon = (el) => { + el.innerHTML = iconCheck; + setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) +} + +const addCopyButtonToCodeCells = () => { + // If ClipboardJS hasn't loaded, wait a bit and try again. This + // happens because we load ClipboardJS asynchronously. + if (window.ClipboardJS === undefined) { + setTimeout(addCopyButtonToCodeCells, 250) + return + } + + // Add copybuttons to all of our code cells + const COPYBUTTON_SELECTOR = 'div.highlight pre'; + const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) + codeCells.forEach((codeCell, index) => { + const id = codeCellId(index) + codeCell.setAttribute('id', id) + + const clipboardButton = id => + `` + codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) + }) + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} + + +var copyTargetText = (trigger) => { + var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); + + // get filtered text + let exclude = '.linenos'; + + let text = filterText(target, exclude); + return formatCopyText(text, '', false, true, true, true, '', '') +} + + // Initialize with a callback so we can modify the text before copy + const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) + + // Update UI with error/success messages + clipboard.on('success', event => { + clearSelection() + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) + temporarilyChangeIcon(event.trigger) + }) + + clipboard.on('error', event => { + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) + }) +} + +runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/v/pdev/_static/copybutton_funcs.js b/v/pdev/_static/copybutton_funcs.js new file mode 100644 index 00000000..dbe1aaad --- /dev/null +++ b/v/pdev/_static/copybutton_funcs.js @@ -0,0 +1,73 @@ +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +export function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} diff --git a/v/pdev/_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css b/v/pdev/_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css new file mode 100644 index 00000000..eb19f698 --- /dev/null +++ b/v/pdev/_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css @@ -0,0 +1 @@ +.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative}details.sd-dropdown .sd-summary-title{font-weight:700;padding-right:3em !important;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary{list-style:none;padding:1em}details.sd-dropdown summary .sd-octicon.no-title{vertical-align:middle}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown summary::-webkit-details-marker{display:none}details.sd-dropdown summary:focus{outline:none}details.sd-dropdown .sd-summary-icon{margin-right:.5em}details.sd-dropdown .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary:hover .sd-summary-up svg,details.sd-dropdown summary:hover .sd-summary-down svg{opacity:1;transform:scale(1.1)}details.sd-dropdown .sd-summary-up svg,details.sd-dropdown .sd-summary-down svg{display:block;opacity:.6}details.sd-dropdown .sd-summary-up,details.sd-dropdown .sd-summary-down{pointer-events:none;position:absolute;right:1em;top:1em}details.sd-dropdown[open]>.sd-summary-title .sd-summary-down{visibility:hidden}details.sd-dropdown:not([open])>.sd-summary-title .sd-summary-up{visibility:hidden}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #0071bc;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0060a0;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem} diff --git a/v/pdev/_static/design-tabs.js b/v/pdev/_static/design-tabs.js new file mode 100644 index 00000000..36b38cf0 --- /dev/null +++ b/v/pdev/_static/design-tabs.js @@ -0,0 +1,27 @@ +var sd_labels_by_text = {}; + +function ready() { + const li = document.getElementsByClassName("sd-tab-label"); + for (const label of li) { + syncId = label.getAttribute("data-sync-id"); + if (syncId) { + label.onclick = onLabelClick; + if (!sd_labels_by_text[syncId]) { + sd_labels_by_text[syncId] = []; + } + sd_labels_by_text[syncId].push(label); + } + } +} + +function onLabelClick() { + // Activate other inputs with the same sync id. + syncId = this.getAttribute("data-sync-id"); + for (label of sd_labels_by_text[syncId]) { + if (label === this) continue; + label.previousElementSibling.checked = true; + } + window.localStorage.setItem("sphinx-design-last-tab", syncId); +} + +document.addEventListener("DOMContentLoaded", ready, false); diff --git a/v/pdev/_static/doctools.js b/v/pdev/_static/doctools.js new file mode 100644 index 00000000..d06a71d7 --- /dev/null +++ b/v/pdev/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/v/pdev/_static/documentation_options.js b/v/pdev/_static/documentation_options.js new file mode 100644 index 00000000..dab586c0 --- /dev/null +++ b/v/pdev/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/v/pdev/_static/favicon.ico b/v/pdev/_static/favicon.ico new file mode 100644 index 00000000..bc84e1e6 Binary files /dev/null and b/v/pdev/_static/favicon.ico differ diff --git a/v/pdev/_static/file.png b/v/pdev/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/v/pdev/_static/file.png differ diff --git a/v/pdev/_static/images/logo_binder.svg b/v/pdev/_static/images/logo_binder.svg new file mode 100644 index 00000000..45fecf75 --- /dev/null +++ b/v/pdev/_static/images/logo_binder.svg @@ -0,0 +1,19 @@ + + + diff --git a/v/pdev/_static/images/logo_colab.png b/v/pdev/_static/images/logo_colab.png new file mode 100644 index 00000000..b7560ec2 Binary files /dev/null and b/v/pdev/_static/images/logo_colab.png differ diff --git a/v/pdev/_static/images/logo_deepnote.svg b/v/pdev/_static/images/logo_deepnote.svg new file mode 100644 index 00000000..fa77ebfc --- /dev/null +++ b/v/pdev/_static/images/logo_deepnote.svg @@ -0,0 +1 @@ + diff --git a/v/pdev/_static/images/logo_jupyterhub.svg b/v/pdev/_static/images/logo_jupyterhub.svg new file mode 100644 index 00000000..60cfe9f2 --- /dev/null +++ b/v/pdev/_static/images/logo_jupyterhub.svg @@ -0,0 +1 @@ + diff --git a/v/pdev/_static/language_data.js b/v/pdev/_static/language_data.js new file mode 100644 index 00000000..250f5665 --- /dev/null +++ b/v/pdev/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, is available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/v/pdev/_static/locales/ar/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/ar/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..15541a6a Binary files /dev/null and b/v/pdev/_static/locales/ar/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/ar/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/ar/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..edae2ec4 --- /dev/null +++ b/v/pdev/_static/locales/ar/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ar\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "موضوع بواسطة" + +msgid "Open an issue" +msgstr "افتح قضية" + +msgid "Contents" +msgstr "محتويات" + +msgid "Download notebook file" +msgstr "تنزيل ملف دفتر الملاحظات" + +msgid "Sphinx Book Theme" +msgstr "موضوع كتاب أبو الهول" + +msgid "Fullscreen mode" +msgstr "وضع ملء الشاشة" + +msgid "Edit this page" +msgstr "قم بتحرير هذه الصفحة" + +msgid "By" +msgstr "بواسطة" + +msgid "Copyright" +msgstr "حقوق النشر" + +msgid "Source repository" +msgstr "مستودع المصدر" + +msgid "previous page" +msgstr "الصفحة السابقة" + +msgid "next page" +msgstr "الصفحة التالية" + +msgid "Toggle navigation" +msgstr "تبديل التنقل" + +msgid "repository" +msgstr "مخزن" + +msgid "suggest edit" +msgstr "أقترح تحرير" + +msgid "open issue" +msgstr "قضية مفتوحة" + +msgid "Launch" +msgstr "إطلاق" + +msgid "Print to PDF" +msgstr "طباعة إلى PDF" + +msgid "By the" +msgstr "بواسطة" + +msgid "Last updated on" +msgstr "آخر تحديث في" + +msgid "Download source file" +msgstr "تنزيل ملف المصدر" + +msgid "Download this page" +msgstr "قم بتنزيل هذه الصفحة" diff --git a/v/pdev/_static/locales/bg/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/bg/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..da951200 Binary files /dev/null and b/v/pdev/_static/locales/bg/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/bg/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/bg/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..1f363b9d --- /dev/null +++ b/v/pdev/_static/locales/bg/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: bg\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Тема от" + +msgid "Open an issue" +msgstr "Отворете проблем" + +msgid "Contents" +msgstr "Съдържание" + +msgid "Download notebook file" +msgstr "Изтеглете файла на бележника" + +msgid "Sphinx Book Theme" +msgstr "Тема на книгата Sphinx" + +msgid "Fullscreen mode" +msgstr "Режим на цял екран" + +msgid "Edit this page" +msgstr "Редактирайте тази страница" + +msgid "By" +msgstr "От" + +msgid "Copyright" +msgstr "Авторско право" + +msgid "Source repository" +msgstr "Хранилище на източника" + +msgid "previous page" +msgstr "предишна страница" + +msgid "next page" +msgstr "Следваща страница" + +msgid "Toggle navigation" +msgstr "Превключване на навигацията" + +msgid "repository" +msgstr "хранилище" + +msgid "suggest edit" +msgstr "предложи редактиране" + +msgid "open issue" +msgstr "отворен брой" + +msgid "Launch" +msgstr "Стартиране" + +msgid "Print to PDF" +msgstr "Печат в PDF" + +msgid "By the" +msgstr "По" + +msgid "Last updated on" +msgstr "Последна актуализация на" + +msgid "Download source file" +msgstr "Изтеглете изходния файл" + +msgid "Download this page" +msgstr "Изтеглете тази страница" diff --git a/v/pdev/_static/locales/bn/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/bn/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..6b96639b Binary files /dev/null and b/v/pdev/_static/locales/bn/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/bn/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/bn/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..fa543728 --- /dev/null +++ b/v/pdev/_static/locales/bn/LC_MESSAGES/booktheme.po @@ -0,0 +1,63 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: bn\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "থিম দ্বারা" + +msgid "Open an issue" +msgstr "একটি সমস্যা খুলুন" + +msgid "Download notebook file" +msgstr "নোটবুক ফাইল ডাউনলোড করুন" + +msgid "Sphinx Book Theme" +msgstr "স্পিনিক্স বুক থিম" + +msgid "Edit this page" +msgstr "এই পৃষ্ঠাটি সম্পাদনা করুন" + +msgid "By" +msgstr "দ্বারা" + +msgid "Copyright" +msgstr "কপিরাইট" + +msgid "Source repository" +msgstr "উত্স সংগ্রহস্থল" + +msgid "previous page" +msgstr "আগের পৃষ্ঠা" + +msgid "next page" +msgstr "পরবর্তী পৃষ্ঠা" + +msgid "Toggle navigation" +msgstr "নেভিগেশন টগল করুন" + +msgid "open issue" +msgstr "খোলা সমস্যা" + +msgid "Launch" +msgstr "শুরু করা" + +msgid "Print to PDF" +msgstr "পিডিএফ প্রিন্ট করুন" + +msgid "By the" +msgstr "দ্বারা" + +msgid "Last updated on" +msgstr "সর্বশেষ আপডেট" + +msgid "Download source file" +msgstr "উত্স ফাইল ডাউনলোড করুন" + +msgid "Download this page" +msgstr "এই পৃষ্ঠাটি ডাউনলোড করুন" diff --git a/v/pdev/_static/locales/ca/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/ca/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..a4dd30e9 Binary files /dev/null and b/v/pdev/_static/locales/ca/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/ca/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/ca/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..22f1569a --- /dev/null +++ b/v/pdev/_static/locales/ca/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ca\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema del" + +msgid "Open an issue" +msgstr "Obriu un número" + +msgid "Download notebook file" +msgstr "Descarregar fitxer de quadern" + +msgid "Sphinx Book Theme" +msgstr "Tema del llibre Esfinx" + +msgid "Edit this page" +msgstr "Editeu aquesta pàgina" + +msgid "By" +msgstr "Per" + +msgid "Copyright" +msgstr "Copyright" + +msgid "Source repository" +msgstr "Dipòsit de fonts" + +msgid "previous page" +msgstr "Pàgina anterior" + +msgid "next page" +msgstr "pàgina següent" + +msgid "Toggle navigation" +msgstr "Commuta la navegació" + +msgid "suggest edit" +msgstr "suggerir edició" + +msgid "open issue" +msgstr "número obert" + +msgid "Launch" +msgstr "Llançament" + +msgid "Print to PDF" +msgstr "Imprimeix a PDF" + +msgid "By the" +msgstr "Per la" + +msgid "Last updated on" +msgstr "Darrera actualització el" + +msgid "Download source file" +msgstr "Baixeu el fitxer font" + +msgid "Download this page" +msgstr "Descarregueu aquesta pàgina" diff --git a/v/pdev/_static/locales/cs/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/cs/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..c39e01a6 Binary files /dev/null and b/v/pdev/_static/locales/cs/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/cs/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/cs/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..afecd9e7 --- /dev/null +++ b/v/pdev/_static/locales/cs/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: cs\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Téma od" + +msgid "Open an issue" +msgstr "Otevřete problém" + +msgid "Contents" +msgstr "Obsah" + +msgid "Download notebook file" +msgstr "Stáhnout soubor poznámkového bloku" + +msgid "Sphinx Book Theme" +msgstr "Téma knihy Sfinga" + +msgid "Fullscreen mode" +msgstr "Režim celé obrazovky" + +msgid "Edit this page" +msgstr "Upravit tuto stránku" + +msgid "By" +msgstr "Podle" + +msgid "Copyright" +msgstr "autorská práva" + +msgid "Source repository" +msgstr "Zdrojové úložiště" + +msgid "previous page" +msgstr "předchozí stránka" + +msgid "next page" +msgstr "další strana" + +msgid "Toggle navigation" +msgstr "Přepnout navigaci" + +msgid "repository" +msgstr "úložiště" + +msgid "suggest edit" +msgstr "navrhnout úpravy" + +msgid "open issue" +msgstr "otevřené číslo" + +msgid "Launch" +msgstr "Zahájení" + +msgid "Print to PDF" +msgstr "Tisk do PDF" + +msgid "By the" +msgstr "Podle" + +msgid "Last updated on" +msgstr "Naposledy aktualizováno" + +msgid "Download source file" +msgstr "Stáhněte si zdrojový soubor" + +msgid "Download this page" +msgstr "Stáhněte si tuto stránku" diff --git a/v/pdev/_static/locales/da/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/da/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..f43157d7 Binary files /dev/null and b/v/pdev/_static/locales/da/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/da/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/da/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..649c78a8 --- /dev/null +++ b/v/pdev/_static/locales/da/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: da\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema af" + +msgid "Open an issue" +msgstr "Åbn et problem" + +msgid "Contents" +msgstr "Indhold" + +msgid "Download notebook file" +msgstr "Download notesbog-fil" + +msgid "Sphinx Book Theme" +msgstr "Sphinx bogtema" + +msgid "Fullscreen mode" +msgstr "Fuldskærmstilstand" + +msgid "Edit this page" +msgstr "Rediger denne side" + +msgid "By" +msgstr "Ved" + +msgid "Copyright" +msgstr "ophavsret" + +msgid "Source repository" +msgstr "Kildelager" + +msgid "previous page" +msgstr "forrige side" + +msgid "next page" +msgstr "Næste side" + +msgid "Toggle navigation" +msgstr "Skift navigation" + +msgid "repository" +msgstr "lager" + +msgid "suggest edit" +msgstr "foreslå redigering" + +msgid "open issue" +msgstr "åbent nummer" + +msgid "Launch" +msgstr "Start" + +msgid "Print to PDF" +msgstr "Udskriv til PDF" + +msgid "By the" +msgstr "Ved" + +msgid "Last updated on" +msgstr "Sidst opdateret den" + +msgid "Download source file" +msgstr "Download kildefil" + +msgid "Download this page" +msgstr "Download denne side" diff --git a/v/pdev/_static/locales/de/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/de/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..648b565c Binary files /dev/null and b/v/pdev/_static/locales/de/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/de/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/de/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..f51d2ecc --- /dev/null +++ b/v/pdev/_static/locales/de/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Thema von der" + +msgid "Open an issue" +msgstr "Öffnen Sie ein Problem" + +msgid "Contents" +msgstr "Inhalt" + +msgid "Download notebook file" +msgstr "Notebook-Datei herunterladen" + +msgid "Sphinx Book Theme" +msgstr "Sphinx-Buch-Thema" + +msgid "Fullscreen mode" +msgstr "Vollbildmodus" + +msgid "Edit this page" +msgstr "Bearbeite diese Seite" + +msgid "By" +msgstr "Durch" + +msgid "Copyright" +msgstr "Urheberrechte ©" + +msgid "Source repository" +msgstr "Quell-Repository" + +msgid "previous page" +msgstr "vorherige Seite" + +msgid "next page" +msgstr "Nächste Seite" + +msgid "Toggle navigation" +msgstr "Navigation umschalten" + +msgid "repository" +msgstr "Repository" + +msgid "suggest edit" +msgstr "vorschlagen zu bearbeiten" + +msgid "open issue" +msgstr "offenes Thema" + +msgid "Launch" +msgstr "Starten" + +msgid "Print to PDF" +msgstr "In PDF drucken" + +msgid "By the" +msgstr "Bis zum" + +msgid "Last updated on" +msgstr "Zuletzt aktualisiert am" + +msgid "Download source file" +msgstr "Quelldatei herunterladen" + +msgid "Download this page" +msgstr "Laden Sie diese Seite herunter" diff --git a/v/pdev/_static/locales/el/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/el/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..fca6e935 Binary files /dev/null and b/v/pdev/_static/locales/el/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/el/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/el/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..8bec7905 --- /dev/null +++ b/v/pdev/_static/locales/el/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: el\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Θέμα από το" + +msgid "Open an issue" +msgstr "Ανοίξτε ένα ζήτημα" + +msgid "Contents" +msgstr "Περιεχόμενα" + +msgid "Download notebook file" +msgstr "Λήψη αρχείου σημειωματάριου" + +msgid "Sphinx Book Theme" +msgstr "Θέμα βιβλίου Sphinx" + +msgid "Fullscreen mode" +msgstr "ΛΕΙΤΟΥΡΓΙΑ ΠΛΗΡΟΥΣ ΟΘΟΝΗΣ" + +msgid "Edit this page" +msgstr "Επεξεργαστείτε αυτήν τη σελίδα" + +msgid "By" +msgstr "Με" + +msgid "Copyright" +msgstr "Πνευματική ιδιοκτησία" + +msgid "Source repository" +msgstr "Αποθήκη πηγής" + +msgid "previous page" +msgstr "προηγούμενη σελίδα" + +msgid "next page" +msgstr "επόμενη σελίδα" + +msgid "Toggle navigation" +msgstr "Εναλλαγή πλοήγησης" + +msgid "repository" +msgstr "αποθήκη" + +msgid "suggest edit" +msgstr "προτείνω επεξεργασία" + +msgid "open issue" +msgstr "ανοιχτό ζήτημα" + +msgid "Launch" +msgstr "Εκτόξευση" + +msgid "Print to PDF" +msgstr "Εκτύπωση σε PDF" + +msgid "By the" +msgstr "Από το" + +msgid "Last updated on" +msgstr "Τελευταία ενημέρωση στις" + +msgid "Download source file" +msgstr "Λήψη αρχείου προέλευσης" + +msgid "Download this page" +msgstr "Λήψη αυτής της σελίδας" diff --git a/v/pdev/_static/locales/eo/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/eo/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..d1072bbe Binary files /dev/null and b/v/pdev/_static/locales/eo/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/eo/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/eo/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..d72a0481 --- /dev/null +++ b/v/pdev/_static/locales/eo/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: eo\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Temo de la" + +msgid "Open an issue" +msgstr "Malfermu numeron" + +msgid "Contents" +msgstr "Enhavo" + +msgid "Download notebook file" +msgstr "Elŝutu kajeran dosieron" + +msgid "Sphinx Book Theme" +msgstr "Sfinksa Libro-Temo" + +msgid "Fullscreen mode" +msgstr "Plenekrana reĝimo" + +msgid "Edit this page" +msgstr "Redaktu ĉi tiun paĝon" + +msgid "By" +msgstr "De" + +msgid "Copyright" +msgstr "Kopirajto" + +msgid "Source repository" +msgstr "Fonto-deponejo" + +msgid "previous page" +msgstr "antaŭa paĝo" + +msgid "next page" +msgstr "sekva paĝo" + +msgid "Toggle navigation" +msgstr "Ŝalti navigadon" + +msgid "repository" +msgstr "deponejo" + +msgid "suggest edit" +msgstr "sugesti redaktadon" + +msgid "open issue" +msgstr "malferma numero" + +msgid "Launch" +msgstr "Lanĉo" + +msgid "Print to PDF" +msgstr "Presi al PDF" + +msgid "By the" +msgstr "Per la" + +msgid "Last updated on" +msgstr "Laste ĝisdatigita la" + +msgid "Download source file" +msgstr "Elŝutu fontodosieron" + +msgid "Download this page" +msgstr "Elŝutu ĉi tiun paĝon" diff --git a/v/pdev/_static/locales/es/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/es/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..ba2ee4dc Binary files /dev/null and b/v/pdev/_static/locales/es/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/es/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/es/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..611834b2 --- /dev/null +++ b/v/pdev/_static/locales/es/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema por el" + +msgid "Open an issue" +msgstr "Abrir un problema" + +msgid "Contents" +msgstr "Contenido" + +msgid "Download notebook file" +msgstr "Descargar archivo de cuaderno" + +msgid "Sphinx Book Theme" +msgstr "Tema del libro de la esfinge" + +msgid "Fullscreen mode" +msgstr "Modo de pantalla completa" + +msgid "Edit this page" +msgstr "Edita esta página" + +msgid "By" +msgstr "Por" + +msgid "Copyright" +msgstr "Derechos de autor" + +msgid "Source repository" +msgstr "Repositorio de origen" + +msgid "previous page" +msgstr "pagina anterior" + +msgid "next page" +msgstr "siguiente página" + +msgid "Toggle navigation" +msgstr "Navegación de palanca" + +msgid "repository" +msgstr "repositorio" + +msgid "suggest edit" +msgstr "sugerir editar" + +msgid "open issue" +msgstr "Tema abierto" + +msgid "Launch" +msgstr "Lanzamiento" + +msgid "Print to PDF" +msgstr "Imprimir en PDF" + +msgid "By the" +msgstr "Por el" + +msgid "Last updated on" +msgstr "Ultima actualización en" + +msgid "Download source file" +msgstr "Descargar archivo fuente" + +msgid "Download this page" +msgstr "Descarga esta pagina" diff --git a/v/pdev/_static/locales/et/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/et/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..983b8239 Binary files /dev/null and b/v/pdev/_static/locales/et/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/et/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/et/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..345088f0 --- /dev/null +++ b/v/pdev/_static/locales/et/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: et\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Teema" + +msgid "Open an issue" +msgstr "Avage probleem" + +msgid "Contents" +msgstr "Sisu" + +msgid "Download notebook file" +msgstr "Laadige sülearvuti fail alla" + +msgid "Sphinx Book Theme" +msgstr "Sfinksiraamatu teema" + +msgid "Fullscreen mode" +msgstr "Täisekraanirežiim" + +msgid "Edit this page" +msgstr "Muutke seda lehte" + +msgid "By" +msgstr "Kõrval" + +msgid "Copyright" +msgstr "Autoriõigus" + +msgid "Source repository" +msgstr "Allikahoidla" + +msgid "previous page" +msgstr "eelmine leht" + +msgid "next page" +msgstr "järgmine leht" + +msgid "Toggle navigation" +msgstr "Lülita navigeerimine sisse" + +msgid "repository" +msgstr "hoidla" + +msgid "suggest edit" +msgstr "soovita muuta" + +msgid "open issue" +msgstr "avatud küsimus" + +msgid "Launch" +msgstr "Käivitage" + +msgid "Print to PDF" +msgstr "Prindi PDF-i" + +msgid "By the" +msgstr "Autor" + +msgid "Last updated on" +msgstr "Viimati uuendatud" + +msgid "Download source file" +msgstr "Laadige alla lähtefail" + +msgid "Download this page" +msgstr "Laadige see leht alla" diff --git a/v/pdev/_static/locales/fi/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/fi/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..d8ac0545 Binary files /dev/null and b/v/pdev/_static/locales/fi/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/fi/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/fi/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..d97a08dc --- /dev/null +++ b/v/pdev/_static/locales/fi/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Teeman tekijä" + +msgid "Open an issue" +msgstr "Avaa ongelma" + +msgid "Contents" +msgstr "Sisällys" + +msgid "Download notebook file" +msgstr "Lataa muistikirjatiedosto" + +msgid "Sphinx Book Theme" +msgstr "Sphinx-kirjan teema" + +msgid "Fullscreen mode" +msgstr "Koko näytön tila" + +msgid "Edit this page" +msgstr "Muokkaa tätä sivua" + +msgid "By" +msgstr "Tekijä" + +msgid "Copyright" +msgstr "Tekijänoikeus" + +msgid "Source repository" +msgstr "Lähteen arkisto" + +msgid "previous page" +msgstr "Edellinen sivu" + +msgid "next page" +msgstr "seuraava sivu" + +msgid "Toggle navigation" +msgstr "Vaihda navigointia" + +msgid "repository" +msgstr "arkisto" + +msgid "suggest edit" +msgstr "ehdottaa muokkausta" + +msgid "open issue" +msgstr "avoin ongelma" + +msgid "Launch" +msgstr "Tuoda markkinoille" + +msgid "Print to PDF" +msgstr "Tulosta PDF-tiedostoon" + +msgid "By the" +msgstr "Mukaan" + +msgid "Last updated on" +msgstr "Viimeksi päivitetty" + +msgid "Download source file" +msgstr "Lataa lähdetiedosto" + +msgid "Download this page" +msgstr "Lataa tämä sivu" diff --git a/v/pdev/_static/locales/fr/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/fr/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..f663d39f Binary files /dev/null and b/v/pdev/_static/locales/fr/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/fr/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/fr/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..88f35173 --- /dev/null +++ b/v/pdev/_static/locales/fr/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Thème par le" + +msgid "Open an issue" +msgstr "Ouvrez un problème" + +msgid "Contents" +msgstr "Contenu" + +msgid "Download notebook file" +msgstr "Télécharger le fichier notebook" + +msgid "Sphinx Book Theme" +msgstr "Thème du livre Sphinx" + +msgid "Fullscreen mode" +msgstr "Mode plein écran" + +msgid "Edit this page" +msgstr "Modifier cette page" + +msgid "By" +msgstr "Par" + +msgid "Copyright" +msgstr "droits d'auteur" + +msgid "Source repository" +msgstr "Dépôt source" + +msgid "previous page" +msgstr "page précédente" + +msgid "next page" +msgstr "page suivante" + +msgid "Toggle navigation" +msgstr "Basculer la navigation" + +msgid "repository" +msgstr "dépôt" + +msgid "suggest edit" +msgstr "suggestion de modification" + +msgid "open issue" +msgstr "signaler un problème" + +msgid "Launch" +msgstr "lancement" + +msgid "Print to PDF" +msgstr "Imprimer au format PDF" + +msgid "By the" +msgstr "Par le" + +msgid "Last updated on" +msgstr "Dernière mise à jour le" + +msgid "Download source file" +msgstr "Télécharger le fichier source" + +msgid "Download this page" +msgstr "Téléchargez cette page" diff --git a/v/pdev/_static/locales/hr/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/hr/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..eca4a1a2 Binary files /dev/null and b/v/pdev/_static/locales/hr/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/hr/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/hr/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..fb9440ac --- /dev/null +++ b/v/pdev/_static/locales/hr/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: hr\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema autora" + +msgid "Open an issue" +msgstr "Otvorite izdanje" + +msgid "Contents" +msgstr "Sadržaj" + +msgid "Download notebook file" +msgstr "Preuzmi datoteku bilježnice" + +msgid "Sphinx Book Theme" +msgstr "Tema knjige Sphinx" + +msgid "Fullscreen mode" +msgstr "Način preko cijelog zaslona" + +msgid "Edit this page" +msgstr "Uredite ovu stranicu" + +msgid "By" +msgstr "Po" + +msgid "Copyright" +msgstr "Autorska prava" + +msgid "Source repository" +msgstr "Izvorno spremište" + +msgid "previous page" +msgstr "Prethodna stranica" + +msgid "next page" +msgstr "sljedeća stranica" + +msgid "Toggle navigation" +msgstr "Uključi / isključi navigaciju" + +msgid "repository" +msgstr "spremište" + +msgid "suggest edit" +msgstr "predloži uređivanje" + +msgid "open issue" +msgstr "otvoreno izdanje" + +msgid "Launch" +msgstr "Pokrenite" + +msgid "Print to PDF" +msgstr "Ispis u PDF" + +msgid "By the" +msgstr "Od strane" + +msgid "Last updated on" +msgstr "Posljednje ažuriranje:" + +msgid "Download source file" +msgstr "Preuzmi izvornu datoteku" + +msgid "Download this page" +msgstr "Preuzmite ovu stranicu" diff --git a/v/pdev/_static/locales/id/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/id/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..d07a06a9 Binary files /dev/null and b/v/pdev/_static/locales/id/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/id/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/id/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..9ffb56f7 --- /dev/null +++ b/v/pdev/_static/locales/id/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: id\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema oleh" + +msgid "Open an issue" +msgstr "Buka masalah" + +msgid "Contents" +msgstr "Isi" + +msgid "Download notebook file" +msgstr "Unduh file notebook" + +msgid "Sphinx Book Theme" +msgstr "Tema Buku Sphinx" + +msgid "Fullscreen mode" +msgstr "Mode layar penuh" + +msgid "Edit this page" +msgstr "Edit halaman ini" + +msgid "By" +msgstr "Oleh" + +msgid "Copyright" +msgstr "hak cipta" + +msgid "Source repository" +msgstr "Repositori sumber" + +msgid "previous page" +msgstr "halaman sebelumnya" + +msgid "next page" +msgstr "halaman selanjutnya" + +msgid "Toggle navigation" +msgstr "Alihkan navigasi" + +msgid "repository" +msgstr "gudang" + +msgid "suggest edit" +msgstr "menyarankan edit" + +msgid "open issue" +msgstr "masalah terbuka" + +msgid "Launch" +msgstr "Meluncurkan" + +msgid "Print to PDF" +msgstr "Cetak ke PDF" + +msgid "By the" +msgstr "Oleh" + +msgid "Last updated on" +msgstr "Terakhir diperbarui saat" + +msgid "Download source file" +msgstr "Unduh file sumber" + +msgid "Download this page" +msgstr "Unduh halaman ini" diff --git a/v/pdev/_static/locales/it/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/it/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..53ba476e Binary files /dev/null and b/v/pdev/_static/locales/it/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/it/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/it/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..04308dd2 --- /dev/null +++ b/v/pdev/_static/locales/it/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema di" + +msgid "Open an issue" +msgstr "Apri un problema" + +msgid "Contents" +msgstr "Contenuti" + +msgid "Download notebook file" +msgstr "Scarica il file del taccuino" + +msgid "Sphinx Book Theme" +msgstr "Tema del libro della Sfinge" + +msgid "Fullscreen mode" +msgstr "Modalità schermo intero" + +msgid "Edit this page" +msgstr "Modifica questa pagina" + +msgid "By" +msgstr "Di" + +msgid "Copyright" +msgstr "Diritto d'autore" + +msgid "Source repository" +msgstr "Repository di origine" + +msgid "previous page" +msgstr "pagina precedente" + +msgid "next page" +msgstr "pagina successiva" + +msgid "Toggle navigation" +msgstr "Attiva / disattiva la navigazione" + +msgid "repository" +msgstr "repository" + +msgid "suggest edit" +msgstr "suggerisci modifica" + +msgid "open issue" +msgstr "questione aperta" + +msgid "Launch" +msgstr "Lanciare" + +msgid "Print to PDF" +msgstr "Stampa in PDF" + +msgid "By the" +msgstr "Dal" + +msgid "Last updated on" +msgstr "Ultimo aggiornamento il" + +msgid "Download source file" +msgstr "Scarica il file sorgente" + +msgid "Download this page" +msgstr "Scarica questa pagina" diff --git a/v/pdev/_static/locales/iw/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/iw/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..a45c6575 Binary files /dev/null and b/v/pdev/_static/locales/iw/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/iw/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/iw/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..4ea190d3 --- /dev/null +++ b/v/pdev/_static/locales/iw/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: iw\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "נושא מאת" + +msgid "Open an issue" +msgstr "פתח גיליון" + +msgid "Contents" +msgstr "תוכן" + +msgid "Download notebook file" +msgstr "הורד קובץ מחברת" + +msgid "Sphinx Book Theme" +msgstr "נושא ספר ספינקס" + +msgid "Fullscreen mode" +msgstr "מצב מסך מלא" + +msgid "Edit this page" +msgstr "ערוך דף זה" + +msgid "By" +msgstr "על ידי" + +msgid "Copyright" +msgstr "זכויות יוצרים" + +msgid "Source repository" +msgstr "מאגר המקורות" + +msgid "previous page" +msgstr "עמוד קודם" + +msgid "next page" +msgstr "עמוד הבא" + +msgid "Toggle navigation" +msgstr "החלף ניווט" + +msgid "repository" +msgstr "מאגר" + +msgid "suggest edit" +msgstr "מציע לערוך" + +msgid "open issue" +msgstr "בעיה פתוחה" + +msgid "Launch" +msgstr "לְהַשִׁיק" + +msgid "Print to PDF" +msgstr "הדפס לקובץ PDF" + +msgid "By the" +msgstr "דרך" + +msgid "Last updated on" +msgstr "עודכן לאחרונה ב" + +msgid "Download source file" +msgstr "הורד את קובץ המקור" + +msgid "Download this page" +msgstr "הורד דף זה" diff --git a/v/pdev/_static/locales/ja/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/ja/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..1cefd29c Binary files /dev/null and b/v/pdev/_static/locales/ja/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/ja/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/ja/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..77d5a097 --- /dev/null +++ b/v/pdev/_static/locales/ja/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ja\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "のテーマ" + +msgid "Open an issue" +msgstr "問題を報告" + +msgid "Contents" +msgstr "目次" + +msgid "Download notebook file" +msgstr "ノートブックファイルをダウンロード" + +msgid "Sphinx Book Theme" +msgstr "スフィンクスの本のテーマ" + +msgid "Fullscreen mode" +msgstr "全画面モード" + +msgid "Edit this page" +msgstr "このページを編集" + +msgid "By" +msgstr "著者" + +msgid "Copyright" +msgstr "Copyright" + +msgid "Source repository" +msgstr "ソースリポジトリ" + +msgid "previous page" +msgstr "前のページ" + +msgid "next page" +msgstr "次のページ" + +msgid "Toggle navigation" +msgstr "ナビゲーションを切り替え" + +msgid "repository" +msgstr "リポジトリ" + +msgid "suggest edit" +msgstr "編集を提案する" + +msgid "open issue" +msgstr "未解決の問題" + +msgid "Launch" +msgstr "起動" + +msgid "Print to PDF" +msgstr "PDFに印刷" + +msgid "By the" +msgstr "によって" + +msgid "Last updated on" +msgstr "最終更新日" + +msgid "Download source file" +msgstr "ソースファイルをダウンロード" + +msgid "Download this page" +msgstr "このページをダウンロード" diff --git a/v/pdev/_static/locales/ko/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/ko/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..06c7ec93 Binary files /dev/null and b/v/pdev/_static/locales/ko/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/ko/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/ko/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..6ee3d781 --- /dev/null +++ b/v/pdev/_static/locales/ko/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ko\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "테마별" + +msgid "Open an issue" +msgstr "이슈 열기" + +msgid "Contents" +msgstr "내용" + +msgid "Download notebook file" +msgstr "노트북 파일 다운로드" + +msgid "Sphinx Book Theme" +msgstr "스핑크스 도서 테마" + +msgid "Fullscreen mode" +msgstr "전체 화면으로보기" + +msgid "Edit this page" +msgstr "이 페이지 편집" + +msgid "By" +msgstr "으로" + +msgid "Copyright" +msgstr "저작권" + +msgid "Source repository" +msgstr "소스 저장소" + +msgid "previous page" +msgstr "이전 페이지" + +msgid "next page" +msgstr "다음 페이지" + +msgid "Toggle navigation" +msgstr "탐색 전환" + +msgid "repository" +msgstr "저장소" + +msgid "suggest edit" +msgstr "편집 제안" + +msgid "open issue" +msgstr "열린 문제" + +msgid "Launch" +msgstr "시작하다" + +msgid "Print to PDF" +msgstr "PDF로 인쇄" + +msgid "By the" +msgstr "에 의해" + +msgid "Last updated on" +msgstr "마지막 업데이트" + +msgid "Download source file" +msgstr "소스 파일 다운로드" + +msgid "Download this page" +msgstr "이 페이지 다운로드" diff --git a/v/pdev/_static/locales/lt/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/lt/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..4468ba04 Binary files /dev/null and b/v/pdev/_static/locales/lt/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/lt/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/lt/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..01be2679 --- /dev/null +++ b/v/pdev/_static/locales/lt/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema" + +msgid "Open an issue" +msgstr "Atidarykite problemą" + +msgid "Contents" +msgstr "Turinys" + +msgid "Download notebook file" +msgstr "Atsisiųsti nešiojamojo kompiuterio failą" + +msgid "Sphinx Book Theme" +msgstr "Sfinkso knygos tema" + +msgid "Fullscreen mode" +msgstr "Pilno ekrano režimas" + +msgid "Edit this page" +msgstr "Redaguoti šį puslapį" + +msgid "By" +msgstr "Iki" + +msgid "Copyright" +msgstr "Autorių teisės" + +msgid "Source repository" +msgstr "Šaltinio saugykla" + +msgid "previous page" +msgstr "Ankstesnis puslapis" + +msgid "next page" +msgstr "Kitas puslapis" + +msgid "Toggle navigation" +msgstr "Perjungti naršymą" + +msgid "repository" +msgstr "saugykla" + +msgid "suggest edit" +msgstr "pasiūlyti redaguoti" + +msgid "open issue" +msgstr "atviras klausimas" + +msgid "Launch" +msgstr "Paleiskite" + +msgid "Print to PDF" +msgstr "Spausdinti į PDF" + +msgid "By the" +msgstr "Prie" + +msgid "Last updated on" +msgstr "Paskutinį kartą atnaujinta" + +msgid "Download source file" +msgstr "Atsisiųsti šaltinio failą" + +msgid "Download this page" +msgstr "Atsisiųskite šį puslapį" diff --git a/v/pdev/_static/locales/lv/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/lv/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..74aa4d89 Binary files /dev/null and b/v/pdev/_static/locales/lv/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/lv/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/lv/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..993a1e41 --- /dev/null +++ b/v/pdev/_static/locales/lv/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lv\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Autora tēma" + +msgid "Open an issue" +msgstr "Atveriet problēmu" + +msgid "Contents" +msgstr "Saturs" + +msgid "Download notebook file" +msgstr "Lejupielādēt piezīmju grāmatiņu" + +msgid "Sphinx Book Theme" +msgstr "Sfinksa grāmatas tēma" + +msgid "Fullscreen mode" +msgstr "Pilnekrāna režīms" + +msgid "Edit this page" +msgstr "Rediģēt šo lapu" + +msgid "By" +msgstr "Autors" + +msgid "Copyright" +msgstr "Autortiesības" + +msgid "Source repository" +msgstr "Avota krātuve" + +msgid "previous page" +msgstr "iepriekšējā lapa" + +msgid "next page" +msgstr "nākamā lapaspuse" + +msgid "Toggle navigation" +msgstr "Pārslēgt navigāciju" + +msgid "repository" +msgstr "krātuve" + +msgid "suggest edit" +msgstr "ieteikt rediģēt" + +msgid "open issue" +msgstr "atklāts jautājums" + +msgid "Launch" +msgstr "Uzsākt" + +msgid "Print to PDF" +msgstr "Drukāt PDF formātā" + +msgid "By the" +msgstr "Ar" + +msgid "Last updated on" +msgstr "Pēdējoreiz atjaunināts" + +msgid "Download source file" +msgstr "Lejupielādēt avota failu" + +msgid "Download this page" +msgstr "Lejupielādējiet šo lapu" diff --git a/v/pdev/_static/locales/ml/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/ml/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..2736e8fc Binary files /dev/null and b/v/pdev/_static/locales/ml/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/ml/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/ml/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..81daf7c8 --- /dev/null +++ b/v/pdev/_static/locales/ml/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ml\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "പ്രമേയം" + +msgid "Open an issue" +msgstr "ഒരു പ്രശ്നം തുറക്കുക" + +msgid "Download notebook file" +msgstr "നോട്ട്ബുക്ക് ഫയൽ ഡൺലോഡ് ചെയ്യുക" + +msgid "Sphinx Book Theme" +msgstr "സ്ഫിങ്ക്സ് പുസ്തക തീം" + +msgid "Edit this page" +msgstr "ഈ പേജ് എഡിറ്റുചെയ്യുക" + +msgid "By" +msgstr "എഴുതിയത്" + +msgid "Copyright" +msgstr "പകർപ്പവകാശം" + +msgid "Source repository" +msgstr "ഉറവിട ശേഖരം" + +msgid "previous page" +msgstr "മുൻപത്തെ താൾ" + +msgid "next page" +msgstr "അടുത്ത പേജ്" + +msgid "Toggle navigation" +msgstr "നാവിഗേഷൻ ടോഗിൾ ചെയ്യുക" + +msgid "suggest edit" +msgstr "എഡിറ്റുചെയ്യാൻ നിർദ്ദേശിക്കുക" + +msgid "open issue" +msgstr "തുറന്ന പ്രശ്നം" + +msgid "Launch" +msgstr "സമാരംഭിക്കുക" + +msgid "Print to PDF" +msgstr "PDF- ലേക്ക് പ്രിന്റുചെയ്യുക" + +msgid "By the" +msgstr "എഴുതിയത്" + +msgid "Last updated on" +msgstr "അവസാനം അപ്ഡേറ്റുചെയ്തത്" + +msgid "Download source file" +msgstr "ഉറവിട ഫയൽ ഡൗൺലോഡുചെയ്യുക" + +msgid "Download this page" +msgstr "ഈ പേജ് ഡൗൺലോഡുചെയ്യുക" diff --git a/v/pdev/_static/locales/mr/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/mr/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..fe530100 Binary files /dev/null and b/v/pdev/_static/locales/mr/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/mr/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/mr/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..fd857bff --- /dev/null +++ b/v/pdev/_static/locales/mr/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: mr\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "द्वारा थीम" + +msgid "Open an issue" +msgstr "एक मुद्दा उघडा" + +msgid "Download notebook file" +msgstr "नोटबुक फाईल डाउनलोड करा" + +msgid "Sphinx Book Theme" +msgstr "स्फिंक्स बुक थीम" + +msgid "Edit this page" +msgstr "हे पृष्ठ संपादित करा" + +msgid "By" +msgstr "द्वारा" + +msgid "Copyright" +msgstr "कॉपीराइट" + +msgid "Source repository" +msgstr "स्त्रोत भांडार" + +msgid "previous page" +msgstr "मागील पान" + +msgid "next page" +msgstr "पुढील पृष्ठ" + +msgid "Toggle navigation" +msgstr "नेव्हिगेशन टॉगल करा" + +msgid "suggest edit" +msgstr "संपादन सुचवा" + +msgid "open issue" +msgstr "खुला मुद्दा" + +msgid "Launch" +msgstr "लाँच करा" + +msgid "Print to PDF" +msgstr "पीडीएफवर मुद्रित करा" + +msgid "By the" +msgstr "द्वारा" + +msgid "Last updated on" +msgstr "अखेरचे अद्यतनित" + +msgid "Download source file" +msgstr "स्त्रोत फाइल डाउनलोड करा" + +msgid "Download this page" +msgstr "हे पृष्ठ डाउनलोड करा" diff --git a/v/pdev/_static/locales/ms/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/ms/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..f02603fa Binary files /dev/null and b/v/pdev/_static/locales/ms/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/ms/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/ms/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..b616d70f --- /dev/null +++ b/v/pdev/_static/locales/ms/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ms\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema oleh" + +msgid "Open an issue" +msgstr "Buka masalah" + +msgid "Download notebook file" +msgstr "Muat turun fail buku nota" + +msgid "Sphinx Book Theme" +msgstr "Tema Buku Sphinx" + +msgid "Edit this page" +msgstr "Edit halaman ini" + +msgid "By" +msgstr "Oleh" + +msgid "Copyright" +msgstr "hak cipta" + +msgid "Source repository" +msgstr "Repositori sumber" + +msgid "previous page" +msgstr "halaman sebelumnya" + +msgid "next page" +msgstr "muka surat seterusnya" + +msgid "Toggle navigation" +msgstr "Togol navigasi" + +msgid "suggest edit" +msgstr "cadangkan edit" + +msgid "open issue" +msgstr "isu terbuka" + +msgid "Launch" +msgstr "Lancarkan" + +msgid "Print to PDF" +msgstr "Cetak ke PDF" + +msgid "By the" +msgstr "Oleh" + +msgid "Last updated on" +msgstr "Terakhir dikemas kini pada" + +msgid "Download source file" +msgstr "Muat turun fail sumber" + +msgid "Download this page" +msgstr "Muat turun halaman ini" diff --git a/v/pdev/_static/locales/nl/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/nl/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..e59e7ecb Binary files /dev/null and b/v/pdev/_static/locales/nl/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/nl/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/nl/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..f16f4bcc --- /dev/null +++ b/v/pdev/_static/locales/nl/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Thema door de" + +msgid "Open an issue" +msgstr "Open een probleem" + +msgid "Contents" +msgstr "Inhoud" + +msgid "Download notebook file" +msgstr "Download notebookbestand" + +msgid "Sphinx Book Theme" +msgstr "Sphinx-boekthema" + +msgid "Fullscreen mode" +msgstr "Volledig scherm" + +msgid "Edit this page" +msgstr "bewerk deze pagina" + +msgid "By" +msgstr "Door" + +msgid "Copyright" +msgstr "auteursrechten" + +msgid "Source repository" +msgstr "Bronopslagplaats" + +msgid "previous page" +msgstr "vorige pagina" + +msgid "next page" +msgstr "volgende bladzijde" + +msgid "Toggle navigation" +msgstr "Schakel navigatie" + +msgid "repository" +msgstr "repository" + +msgid "suggest edit" +msgstr "suggereren bewerken" + +msgid "open issue" +msgstr "open probleem" + +msgid "Launch" +msgstr "Lancering" + +msgid "Print to PDF" +msgstr "Afdrukken naar pdf" + +msgid "By the" +msgstr "Door de" + +msgid "Last updated on" +msgstr "Laatst geupdate op" + +msgid "Download source file" +msgstr "Download het bronbestand" + +msgid "Download this page" +msgstr "Download deze pagina" diff --git a/v/pdev/_static/locales/no/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/no/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..6cd15c88 Binary files /dev/null and b/v/pdev/_static/locales/no/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/no/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/no/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..b1d304ee --- /dev/null +++ b/v/pdev/_static/locales/no/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: no\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema av" + +msgid "Open an issue" +msgstr "Åpne et problem" + +msgid "Contents" +msgstr "Innhold" + +msgid "Download notebook file" +msgstr "Last ned notatbokfilen" + +msgid "Sphinx Book Theme" +msgstr "Sphinx boktema" + +msgid "Fullscreen mode" +msgstr "Fullskjerm-modus" + +msgid "Edit this page" +msgstr "Rediger denne siden" + +msgid "By" +msgstr "Av" + +msgid "Copyright" +msgstr "opphavsrett" + +msgid "Source repository" +msgstr "Kildedepot" + +msgid "previous page" +msgstr "forrige side" + +msgid "next page" +msgstr "neste side" + +msgid "Toggle navigation" +msgstr "Bytt navigasjon" + +msgid "repository" +msgstr "oppbevaringssted" + +msgid "suggest edit" +msgstr "foreslå redigering" + +msgid "open issue" +msgstr "åpent nummer" + +msgid "Launch" +msgstr "Start" + +msgid "Print to PDF" +msgstr "Skriv ut til PDF" + +msgid "By the" +msgstr "Ved" + +msgid "Last updated on" +msgstr "Sist oppdatert den" + +msgid "Download source file" +msgstr "Last ned kildefilen" + +msgid "Download this page" +msgstr "Last ned denne siden" diff --git a/v/pdev/_static/locales/pl/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/pl/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..9ebb584f Binary files /dev/null and b/v/pdev/_static/locales/pl/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/pl/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/pl/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..80d2c896 --- /dev/null +++ b/v/pdev/_static/locales/pl/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Motyw autorstwa" + +msgid "Open an issue" +msgstr "Otwórz problem" + +msgid "Contents" +msgstr "Zawartość" + +msgid "Download notebook file" +msgstr "Pobierz plik notatnika" + +msgid "Sphinx Book Theme" +msgstr "Motyw książki Sphinx" + +msgid "Fullscreen mode" +msgstr "Pełny ekran" + +msgid "Edit this page" +msgstr "Edytuj tę strone" + +msgid "By" +msgstr "Przez" + +msgid "Copyright" +msgstr "prawa autorskie" + +msgid "Source repository" +msgstr "Repozytorium źródłowe" + +msgid "previous page" +msgstr "Poprzednia strona" + +msgid "next page" +msgstr "Następna strona" + +msgid "Toggle navigation" +msgstr "Przełącz nawigację" + +msgid "repository" +msgstr "magazyn" + +msgid "suggest edit" +msgstr "zaproponuj edycję" + +msgid "open issue" +msgstr "otwarty problem" + +msgid "Launch" +msgstr "Uruchomić" + +msgid "Print to PDF" +msgstr "Drukuj do PDF" + +msgid "By the" +msgstr "Przez" + +msgid "Last updated on" +msgstr "Ostatnia aktualizacja" + +msgid "Download source file" +msgstr "Pobierz plik źródłowy" + +msgid "Download this page" +msgstr "Pobierz tę stronę" diff --git a/v/pdev/_static/locales/pt/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/pt/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..d0ddb872 Binary files /dev/null and b/v/pdev/_static/locales/pt/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/pt/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/pt/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..45ac847f --- /dev/null +++ b/v/pdev/_static/locales/pt/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pt\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema por" + +msgid "Open an issue" +msgstr "Abra um problema" + +msgid "Contents" +msgstr "Conteúdo" + +msgid "Download notebook file" +msgstr "Baixar arquivo de notebook" + +msgid "Sphinx Book Theme" +msgstr "Tema do livro Sphinx" + +msgid "Fullscreen mode" +msgstr "Modo tela cheia" + +msgid "Edit this page" +msgstr "Edite essa página" + +msgid "By" +msgstr "De" + +msgid "Copyright" +msgstr "direito autoral" + +msgid "Source repository" +msgstr "Repositório fonte" + +msgid "previous page" +msgstr "página anterior" + +msgid "next page" +msgstr "próxima página" + +msgid "Toggle navigation" +msgstr "Alternar de navegação" + +msgid "repository" +msgstr "repositório" + +msgid "suggest edit" +msgstr "sugerir edição" + +msgid "open issue" +msgstr "questão aberta" + +msgid "Launch" +msgstr "Lançamento" + +msgid "Print to PDF" +msgstr "Imprimir em PDF" + +msgid "By the" +msgstr "Pelo" + +msgid "Last updated on" +msgstr "Última atualização em" + +msgid "Download source file" +msgstr "Baixar arquivo fonte" + +msgid "Download this page" +msgstr "Baixe esta página" diff --git a/v/pdev/_static/locales/ro/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/ro/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..3c36ab1d Binary files /dev/null and b/v/pdev/_static/locales/ro/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/ro/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/ro/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..532b3b84 --- /dev/null +++ b/v/pdev/_static/locales/ro/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ro\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema de" + +msgid "Open an issue" +msgstr "Deschideți o problemă" + +msgid "Contents" +msgstr "Cuprins" + +msgid "Download notebook file" +msgstr "Descărcați fișierul notebook" + +msgid "Sphinx Book Theme" +msgstr "Tema Sphinx Book" + +msgid "Fullscreen mode" +msgstr "Modul ecran întreg" + +msgid "Edit this page" +msgstr "Editați această pagină" + +msgid "By" +msgstr "De" + +msgid "Copyright" +msgstr "Drepturi de autor" + +msgid "Source repository" +msgstr "Depozit sursă" + +msgid "previous page" +msgstr "pagina anterioară" + +msgid "next page" +msgstr "pagina următoare" + +msgid "Toggle navigation" +msgstr "Comutare navigare" + +msgid "repository" +msgstr "repertoriu" + +msgid "suggest edit" +msgstr "sugerează editare" + +msgid "open issue" +msgstr "problema deschisă" + +msgid "Launch" +msgstr "Lansa" + +msgid "Print to PDF" +msgstr "Imprimați în PDF" + +msgid "By the" +msgstr "Langa" + +msgid "Last updated on" +msgstr "Ultima actualizare la" + +msgid "Download source file" +msgstr "Descărcați fișierul sursă" + +msgid "Download this page" +msgstr "Descarcă această pagină" diff --git a/v/pdev/_static/locales/ru/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/ru/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..6b8ca41f Binary files /dev/null and b/v/pdev/_static/locales/ru/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/ru/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/ru/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..b718b482 --- /dev/null +++ b/v/pdev/_static/locales/ru/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ru\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Тема от" + +msgid "Open an issue" +msgstr "Открыть вопрос" + +msgid "Contents" +msgstr "Содержание" + +msgid "Download notebook file" +msgstr "Скачать файл записной книжки" + +msgid "Sphinx Book Theme" +msgstr "Тема книги Сфинкс" + +msgid "Fullscreen mode" +msgstr "Полноэкранный режим" + +msgid "Edit this page" +msgstr "Редактировать эту страницу" + +msgid "By" +msgstr "По" + +msgid "Copyright" +msgstr "авторское право" + +msgid "Source repository" +msgstr "Исходный репозиторий" + +msgid "previous page" +msgstr "Предыдущая страница" + +msgid "next page" +msgstr "Следующая страница" + +msgid "Toggle navigation" +msgstr "Переключить навигацию" + +msgid "repository" +msgstr "хранилище" + +msgid "suggest edit" +msgstr "предложить редактировать" + +msgid "open issue" +msgstr "открытый вопрос" + +msgid "Launch" +msgstr "Запуск" + +msgid "Print to PDF" +msgstr "Распечатать в PDF" + +msgid "By the" +msgstr "Посредством" + +msgid "Last updated on" +msgstr "Последнее обновление" + +msgid "Download source file" +msgstr "Скачать исходный файл" + +msgid "Download this page" +msgstr "Загрузите эту страницу" diff --git a/v/pdev/_static/locales/sk/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/sk/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..59bd0ddf Binary files /dev/null and b/v/pdev/_static/locales/sk/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/sk/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/sk/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..f6c423b6 --- /dev/null +++ b/v/pdev/_static/locales/sk/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: sk\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Téma od" + +msgid "Open an issue" +msgstr "Otvorte problém" + +msgid "Contents" +msgstr "Obsah" + +msgid "Download notebook file" +msgstr "Stiahnite si zošit" + +msgid "Sphinx Book Theme" +msgstr "Téma knihy Sfinga" + +msgid "Fullscreen mode" +msgstr "Režim celej obrazovky" + +msgid "Edit this page" +msgstr "Upraviť túto stránku" + +msgid "By" +msgstr "Autor:" + +msgid "Copyright" +msgstr "Autorské práva" + +msgid "Source repository" +msgstr "Zdrojové úložisko" + +msgid "previous page" +msgstr "predchádzajúca strana" + +msgid "next page" +msgstr "ďalšia strana" + +msgid "Toggle navigation" +msgstr "Prepnúť navigáciu" + +msgid "repository" +msgstr "Úložisko" + +msgid "suggest edit" +msgstr "navrhnúť úpravu" + +msgid "open issue" +msgstr "otvorené vydanie" + +msgid "Launch" +msgstr "Spustiť" + +msgid "Print to PDF" +msgstr "Tlač do PDF" + +msgid "By the" +msgstr "Podľa" + +msgid "Last updated on" +msgstr "Posledná aktualizácia dňa" + +msgid "Download source file" +msgstr "Stiahnite si zdrojový súbor" + +msgid "Download this page" +msgstr "Stiahnite si túto stránku" diff --git a/v/pdev/_static/locales/sl/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/sl/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..87bf26de Binary files /dev/null and b/v/pdev/_static/locales/sl/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/sl/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/sl/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..9822dc58 --- /dev/null +++ b/v/pdev/_static/locales/sl/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: sl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema avtorja" + +msgid "Open an issue" +msgstr "Odprite številko" + +msgid "Contents" +msgstr "Vsebina" + +msgid "Download notebook file" +msgstr "Prenesite datoteko zvezka" + +msgid "Sphinx Book Theme" +msgstr "Tema knjige Sphinx" + +msgid "Fullscreen mode" +msgstr "Celozaslonski način" + +msgid "Edit this page" +msgstr "Uredite to stran" + +msgid "By" +msgstr "Avtor" + +msgid "Copyright" +msgstr "avtorske pravice" + +msgid "Source repository" +msgstr "Izvorno skladišče" + +msgid "previous page" +msgstr "Prejšnja stran" + +msgid "next page" +msgstr "Naslednja stran" + +msgid "Toggle navigation" +msgstr "Preklopi navigacijo" + +msgid "repository" +msgstr "odlagališče" + +msgid "suggest edit" +msgstr "predlagajte urejanje" + +msgid "open issue" +msgstr "odprto vprašanje" + +msgid "Launch" +msgstr "Kosilo" + +msgid "Print to PDF" +msgstr "Natisni v PDF" + +msgid "By the" +msgstr "Avtor" + +msgid "Last updated on" +msgstr "Nazadnje posodobljeno dne" + +msgid "Download source file" +msgstr "Prenesite izvorno datoteko" + +msgid "Download this page" +msgstr "Prenesite to stran" diff --git a/v/pdev/_static/locales/sr/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/sr/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..ec740f48 Binary files /dev/null and b/v/pdev/_static/locales/sr/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/sr/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/sr/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..e809230c --- /dev/null +++ b/v/pdev/_static/locales/sr/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: sr\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Тхеме би" + +msgid "Open an issue" +msgstr "Отворите издање" + +msgid "Contents" +msgstr "Садржај" + +msgid "Download notebook file" +msgstr "Преузмите датотеку бележнице" + +msgid "Sphinx Book Theme" +msgstr "Тема књиге Спхинк" + +msgid "Fullscreen mode" +msgstr "Режим целог екрана" + +msgid "Edit this page" +msgstr "Уредите ову страницу" + +msgid "By" +msgstr "Од стране" + +msgid "Copyright" +msgstr "Ауторско право" + +msgid "Source repository" +msgstr "Изворно спремиште" + +msgid "previous page" +msgstr "Претходна страница" + +msgid "next page" +msgstr "Следећа страна" + +msgid "Toggle navigation" +msgstr "Укључи / искључи навигацију" + +msgid "repository" +msgstr "спремиште" + +msgid "suggest edit" +msgstr "предложи уређивање" + +msgid "open issue" +msgstr "отворено издање" + +msgid "Launch" +msgstr "Лансирање" + +msgid "Print to PDF" +msgstr "Испис у ПДФ" + +msgid "By the" +msgstr "Од" + +msgid "Last updated on" +msgstr "Последње ажурирање" + +msgid "Download source file" +msgstr "Преузми изворну датотеку" + +msgid "Download this page" +msgstr "Преузмите ову страницу" diff --git a/v/pdev/_static/locales/sv/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/sv/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..b07dc76f Binary files /dev/null and b/v/pdev/_static/locales/sv/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/sv/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/sv/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..2421b001 --- /dev/null +++ b/v/pdev/_static/locales/sv/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: sv\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema av" + +msgid "Open an issue" +msgstr "Öppna en problemrapport" + +msgid "Contents" +msgstr "Innehåll" + +msgid "Download notebook file" +msgstr "Ladda ner notebook-fil" + +msgid "Sphinx Book Theme" +msgstr "Sphinx Boktema" + +msgid "Fullscreen mode" +msgstr "Fullskärmsläge" + +msgid "Edit this page" +msgstr "Redigera den här sidan" + +msgid "By" +msgstr "Av" + +msgid "Copyright" +msgstr "Upphovsrätt" + +msgid "Source repository" +msgstr "Källkodsrepositorium" + +msgid "previous page" +msgstr "föregående sida" + +msgid "next page" +msgstr "nästa sida" + +msgid "Toggle navigation" +msgstr "Växla navigering" + +msgid "repository" +msgstr "repositorium" + +msgid "suggest edit" +msgstr "föreslå ändring" + +msgid "open issue" +msgstr "öppna problemrapport" + +msgid "Launch" +msgstr "Öppna" + +msgid "Print to PDF" +msgstr "Skriv ut till PDF" + +msgid "By the" +msgstr "Av den" + +msgid "Last updated on" +msgstr "Senast uppdaterad den" + +msgid "Download source file" +msgstr "Ladda ner källfil" + +msgid "Download this page" +msgstr "Ladda ner den här sidan" diff --git a/v/pdev/_static/locales/ta/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/ta/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..29f52e1f Binary files /dev/null and b/v/pdev/_static/locales/ta/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/ta/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/ta/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..500042f4 --- /dev/null +++ b/v/pdev/_static/locales/ta/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ta\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "வழங்கிய தீம்" + +msgid "Open an issue" +msgstr "சிக்கலைத் திறக்கவும்" + +msgid "Download notebook file" +msgstr "நோட்புக் கோப்பைப் பதிவிறக்கவும்" + +msgid "Sphinx Book Theme" +msgstr "ஸ்பிங்க்ஸ் புத்தக தீம்" + +msgid "Edit this page" +msgstr "இந்தப் பக்கத்தைத் திருத்தவும்" + +msgid "By" +msgstr "வழங்கியவர்" + +msgid "Copyright" +msgstr "பதிப்புரிமை" + +msgid "Source repository" +msgstr "மூல களஞ்சியம்" + +msgid "previous page" +msgstr "முந்தைய பக்கம்" + +msgid "next page" +msgstr "அடுத்த பக்கம்" + +msgid "Toggle navigation" +msgstr "வழிசெலுத்தலை நிலைமாற்று" + +msgid "suggest edit" +msgstr "திருத்த பரிந்துரைக்கவும்" + +msgid "open issue" +msgstr "திறந்த பிரச்சினை" + +msgid "Launch" +msgstr "தொடங்க" + +msgid "Print to PDF" +msgstr "PDF இல் அச்சிடுக" + +msgid "By the" +msgstr "மூலம்" + +msgid "Last updated on" +msgstr "கடைசியாக புதுப்பிக்கப்பட்டது" + +msgid "Download source file" +msgstr "மூல கோப்பைப் பதிவிறக்குக" + +msgid "Download this page" +msgstr "இந்தப் பக்கத்தைப் பதிவிறக்கவும்" diff --git a/v/pdev/_static/locales/te/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/te/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..0a5f4b46 Binary files /dev/null and b/v/pdev/_static/locales/te/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/te/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/te/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..b1afebba --- /dev/null +++ b/v/pdev/_static/locales/te/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: te\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "ద్వారా థీమ్" + +msgid "Open an issue" +msgstr "సమస్యను తెరవండి" + +msgid "Download notebook file" +msgstr "నోట్బుక్ ఫైల్ను డౌన్లోడ్ చేయండి" + +msgid "Sphinx Book Theme" +msgstr "సింహిక పుస్తక థీమ్" + +msgid "Edit this page" +msgstr "ఈ పేజీని సవరించండి" + +msgid "By" +msgstr "ద్వారా" + +msgid "Copyright" +msgstr "కాపీరైట్" + +msgid "Source repository" +msgstr "మూల రిపోజిటరీ" + +msgid "previous page" +msgstr "ముందు పేజి" + +msgid "next page" +msgstr "తరువాతి పేజీ" + +msgid "Toggle navigation" +msgstr "నావిగేషన్ను టోగుల్ చేయండి" + +msgid "suggest edit" +msgstr "సవరించమని సూచించండి" + +msgid "open issue" +msgstr "ఓపెన్ ఇష్యూ" + +msgid "Launch" +msgstr "ప్రారంభించండి" + +msgid "Print to PDF" +msgstr "PDF కి ముద్రించండి" + +msgid "By the" +msgstr "ద్వారా" + +msgid "Last updated on" +msgstr "చివరిగా నవీకరించబడింది" + +msgid "Download source file" +msgstr "మూల ఫైల్ను డౌన్లోడ్ చేయండి" + +msgid "Download this page" +msgstr "ఈ పేజీని డౌన్లోడ్ చేయండి" diff --git a/v/pdev/_static/locales/tg/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/tg/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..b21c6c63 Binary files /dev/null and b/v/pdev/_static/locales/tg/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/tg/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/tg/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..29b8237b --- /dev/null +++ b/v/pdev/_static/locales/tg/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: tg\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Мавзӯъи аз" + +msgid "Open an issue" +msgstr "Масъаларо кушоед" + +msgid "Contents" +msgstr "Мундариҷа" + +msgid "Download notebook file" +msgstr "Файли дафтарро зеркашӣ кунед" + +msgid "Sphinx Book Theme" +msgstr "Сфинкс Мавзӯи китоб" + +msgid "Fullscreen mode" +msgstr "Ҳолати экрани пурра" + +msgid "Edit this page" +msgstr "Ин саҳифаро таҳрир кунед" + +msgid "By" +msgstr "Бо" + +msgid "Copyright" +msgstr "Ҳуқуқи муаллиф" + +msgid "Source repository" +msgstr "Анбори манбаъ" + +msgid "previous page" +msgstr "саҳифаи қаблӣ" + +msgid "next page" +msgstr "саҳифаи оянда" + +msgid "Toggle navigation" +msgstr "Гузаришро иваз кунед" + +msgid "repository" +msgstr "анбор" + +msgid "suggest edit" +msgstr "пешниҳод вироиш" + +msgid "open issue" +msgstr "барориши кушод" + +msgid "Launch" +msgstr "Оғоз" + +msgid "Print to PDF" +msgstr "Чоп ба PDF" + +msgid "By the" +msgstr "Бо" + +msgid "Last updated on" +msgstr "Last навсозӣ дар" + +msgid "Download source file" +msgstr "Файли манбаъро зеркашӣ кунед" + +msgid "Download this page" +msgstr "Ин саҳифаро зеркашӣ кунед" diff --git a/v/pdev/_static/locales/th/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/th/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..abede98a Binary files /dev/null and b/v/pdev/_static/locales/th/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/th/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/th/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..ac65ee05 --- /dev/null +++ b/v/pdev/_static/locales/th/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: th\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "ธีมโดย" + +msgid "Open an issue" +msgstr "เปิดปัญหา" + +msgid "Contents" +msgstr "สารบัญ" + +msgid "Download notebook file" +msgstr "ดาวน์โหลดไฟล์สมุดบันทึก" + +msgid "Sphinx Book Theme" +msgstr "ธีมหนังสือสฟิงซ์" + +msgid "Fullscreen mode" +msgstr "โหมดเต็มหน้าจอ" + +msgid "Edit this page" +msgstr "แก้ไขหน้านี้" + +msgid "By" +msgstr "โดย" + +msgid "Copyright" +msgstr "ลิขสิทธิ์" + +msgid "Source repository" +msgstr "ที่เก็บซอร์ส" + +msgid "previous page" +msgstr "หน้าที่แล้ว" + +msgid "next page" +msgstr "หน้าต่อไป" + +msgid "Toggle navigation" +msgstr "ไม่ต้องสลับช่องทาง" + +msgid "repository" +msgstr "ที่เก็บ" + +msgid "suggest edit" +msgstr "แนะนำแก้ไข" + +msgid "open issue" +msgstr "เปิดปัญหา" + +msgid "Launch" +msgstr "เปิด" + +msgid "Print to PDF" +msgstr "พิมพ์เป็น PDF" + +msgid "By the" +msgstr "โดย" + +msgid "Last updated on" +msgstr "ปรับปรุงล่าสุดเมื่อ" + +msgid "Download source file" +msgstr "ดาวน์โหลดไฟล์ต้นฉบับ" + +msgid "Download this page" +msgstr "ดาวน์โหลดหน้านี้" diff --git a/v/pdev/_static/locales/tl/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/tl/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..8df1b733 Binary files /dev/null and b/v/pdev/_static/locales/tl/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/tl/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/tl/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..662d66ca --- /dev/null +++ b/v/pdev/_static/locales/tl/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: tl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tema ng" + +msgid "Open an issue" +msgstr "Magbukas ng isyu" + +msgid "Download notebook file" +msgstr "Mag-download ng file ng notebook" + +msgid "Sphinx Book Theme" +msgstr "Tema ng Sphinx Book" + +msgid "Edit this page" +msgstr "I-edit ang pahinang ito" + +msgid "By" +msgstr "Ni" + +msgid "Copyright" +msgstr "Copyright" + +msgid "Source repository" +msgstr "Pinagmulan ng imbakan" + +msgid "previous page" +msgstr "Nakaraang pahina" + +msgid "next page" +msgstr "Susunod na pahina" + +msgid "Toggle navigation" +msgstr "I-toggle ang pag-navigate" + +msgid "suggest edit" +msgstr "iminumungkahi i-edit" + +msgid "open issue" +msgstr "bukas na isyu" + +msgid "Launch" +msgstr "Ilunsad" + +msgid "Print to PDF" +msgstr "I-print sa PDF" + +msgid "By the" +msgstr "Sa pamamagitan ng" + +msgid "Last updated on" +msgstr "Huling na-update noong" + +msgid "Download source file" +msgstr "Mag-download ng file ng pinagmulan" + +msgid "Download this page" +msgstr "I-download ang pahinang ito" diff --git a/v/pdev/_static/locales/tr/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/tr/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..029ae18a Binary files /dev/null and b/v/pdev/_static/locales/tr/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/tr/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/tr/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..d1ae7233 --- /dev/null +++ b/v/pdev/_static/locales/tr/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: tr\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Tarafından tema" + +msgid "Open an issue" +msgstr "Bir sorunu açın" + +msgid "Contents" +msgstr "İçindekiler" + +msgid "Download notebook file" +msgstr "Defter dosyasını indirin" + +msgid "Sphinx Book Theme" +msgstr "Sfenks Kitap Teması" + +msgid "Fullscreen mode" +msgstr "Tam ekran modu" + +msgid "Edit this page" +msgstr "Bu sayfayı düzenle" + +msgid "By" +msgstr "Tarafından" + +msgid "Copyright" +msgstr "Telif hakkı" + +msgid "Source repository" +msgstr "Kaynak kod deposu" + +msgid "previous page" +msgstr "önceki sayfa" + +msgid "next page" +msgstr "sonraki Sayfa" + +msgid "Toggle navigation" +msgstr "Gezinmeyi değiştir" + +msgid "repository" +msgstr "depo" + +msgid "suggest edit" +msgstr "düzenleme öner" + +msgid "open issue" +msgstr "Açık konu" + +msgid "Launch" +msgstr "Başlatmak" + +msgid "Print to PDF" +msgstr "PDF olarak yazdır" + +msgid "By the" +msgstr "Tarafından" + +msgid "Last updated on" +msgstr "Son güncelleme tarihi" + +msgid "Download source file" +msgstr "Kaynak dosyayı indirin" + +msgid "Download this page" +msgstr "Bu sayfayı indirin" diff --git a/v/pdev/_static/locales/uk/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/uk/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..16ab7890 Binary files /dev/null and b/v/pdev/_static/locales/uk/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/uk/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/uk/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..be49ab85 --- /dev/null +++ b/v/pdev/_static/locales/uk/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: uk\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Тема від" + +msgid "Open an issue" +msgstr "Відкрийте випуск" + +msgid "Contents" +msgstr "Зміст" + +msgid "Download notebook file" +msgstr "Завантажте файл блокнота" + +msgid "Sphinx Book Theme" +msgstr "Тема книги \"Сфінкс\"" + +msgid "Fullscreen mode" +msgstr "Повноекранний режим" + +msgid "Edit this page" +msgstr "Редагувати цю сторінку" + +msgid "By" +msgstr "Автор" + +msgid "Copyright" +msgstr "Авторське право" + +msgid "Source repository" +msgstr "Джерело сховища" + +msgid "previous page" +msgstr "Попередня сторінка" + +msgid "next page" +msgstr "Наступна сторінка" + +msgid "Toggle navigation" +msgstr "Переключити навігацію" + +msgid "repository" +msgstr "сховище" + +msgid "suggest edit" +msgstr "запропонувати редагувати" + +msgid "open issue" +msgstr "відкритий випуск" + +msgid "Launch" +msgstr "Запуск" + +msgid "Print to PDF" +msgstr "Друк у форматі PDF" + +msgid "By the" +msgstr "По" + +msgid "Last updated on" +msgstr "Останнє оновлення:" + +msgid "Download source file" +msgstr "Завантажити вихідний файл" + +msgid "Download this page" +msgstr "Завантажте цю сторінку" diff --git a/v/pdev/_static/locales/ur/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/ur/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..de8c84b9 Binary files /dev/null and b/v/pdev/_static/locales/ur/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/ur/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/ur/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..94bcab33 --- /dev/null +++ b/v/pdev/_static/locales/ur/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ur\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "کے ذریعہ تھیم" + +msgid "Open an issue" +msgstr "ایک مسئلہ کھولیں" + +msgid "Download notebook file" +msgstr "نوٹ بک فائل ڈاؤن لوڈ کریں" + +msgid "Sphinx Book Theme" +msgstr "سپنکس بک تھیم" + +msgid "Edit this page" +msgstr "اس صفحے میں ترمیم کریں" + +msgid "By" +msgstr "بذریعہ" + +msgid "Copyright" +msgstr "کاپی رائٹ" + +msgid "Source repository" +msgstr "ماخذ ذخیرہ" + +msgid "previous page" +msgstr "سابقہ صفحہ" + +msgid "next page" +msgstr "اگلا صفحہ" + +msgid "Toggle navigation" +msgstr "نیویگیشن ٹوگل کریں" + +msgid "suggest edit" +msgstr "ترمیم کی تجویز کریں" + +msgid "open issue" +msgstr "کھلا مسئلہ" + +msgid "Launch" +msgstr "لانچ کریں" + +msgid "Print to PDF" +msgstr "پی ڈی ایف پرنٹ کریں" + +msgid "By the" +msgstr "کی طرف" + +msgid "Last updated on" +msgstr "آخری بار تازہ کاری ہوئی" + +msgid "Download source file" +msgstr "سورس فائل ڈاؤن لوڈ کریں" + +msgid "Download this page" +msgstr "اس صفحے کو ڈاؤن لوڈ کریں" diff --git a/v/pdev/_static/locales/vi/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/vi/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..2bb32555 Binary files /dev/null and b/v/pdev/_static/locales/vi/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/vi/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/vi/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..116236dc --- /dev/null +++ b/v/pdev/_static/locales/vi/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: vi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "Chủ đề của" + +msgid "Open an issue" +msgstr "Mở một vấn đề" + +msgid "Contents" +msgstr "Nội dung" + +msgid "Download notebook file" +msgstr "Tải xuống tệp sổ tay" + +msgid "Sphinx Book Theme" +msgstr "Chủ đề sách nhân sư" + +msgid "Fullscreen mode" +msgstr "Chế độ toàn màn hình" + +msgid "Edit this page" +msgstr "chỉnh sửa trang này" + +msgid "By" +msgstr "Bởi" + +msgid "Copyright" +msgstr "Bản quyền" + +msgid "Source repository" +msgstr "Kho nguồn" + +msgid "previous page" +msgstr "trang trước" + +msgid "next page" +msgstr "Trang tiếp theo" + +msgid "Toggle navigation" +msgstr "Chuyển đổi điều hướng thành" + +msgid "repository" +msgstr "kho" + +msgid "suggest edit" +msgstr "đề nghị chỉnh sửa" + +msgid "open issue" +msgstr "vấn đề mở" + +msgid "Launch" +msgstr "Phóng" + +msgid "Print to PDF" +msgstr "In sang PDF" + +msgid "By the" +msgstr "Bằng" + +msgid "Last updated on" +msgstr "Cập nhật lần cuối vào" + +msgid "Download source file" +msgstr "Tải xuống tệp nguồn" + +msgid "Download this page" +msgstr "Tải xuống trang này" diff --git a/v/pdev/_static/locales/zh_CN/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/zh_CN/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..0e3235d0 Binary files /dev/null and b/v/pdev/_static/locales/zh_CN/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/zh_CN/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/zh_CN/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..4f4ab579 --- /dev/null +++ b/v/pdev/_static/locales/zh_CN/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "主题作者:" + +msgid "Open an issue" +msgstr "创建议题" + +msgid "Contents" +msgstr "目录" + +msgid "Download notebook file" +msgstr "下载笔记本文件" + +msgid "Sphinx Book Theme" +msgstr "Sphinx Book 主题" + +msgid "Fullscreen mode" +msgstr "全屏模式" + +msgid "Edit this page" +msgstr "编辑此页面" + +msgid "By" +msgstr "作者:" + +msgid "Copyright" +msgstr "版权" + +msgid "Source repository" +msgstr "源码库" + +msgid "previous page" +msgstr "上一页" + +msgid "next page" +msgstr "下一页" + +msgid "Toggle navigation" +msgstr "显示或隐藏导航栏" + +msgid "repository" +msgstr "仓库" + +msgid "suggest edit" +msgstr "提出修改建议" + +msgid "open issue" +msgstr "创建议题" + +msgid "Launch" +msgstr "启动" + +msgid "Print to PDF" +msgstr "列印成 PDF" + +msgid "By the" +msgstr "作者:" + +msgid "Last updated on" +msgstr "上次更新时间:" + +msgid "Download source file" +msgstr "下载源文件" + +msgid "Download this page" +msgstr "下载此页面" diff --git a/v/pdev/_static/locales/zh_TW/LC_MESSAGES/booktheme.mo b/v/pdev/_static/locales/zh_TW/LC_MESSAGES/booktheme.mo new file mode 100644 index 00000000..9116fa95 Binary files /dev/null and b/v/pdev/_static/locales/zh_TW/LC_MESSAGES/booktheme.mo differ diff --git a/v/pdev/_static/locales/zh_TW/LC_MESSAGES/booktheme.po b/v/pdev/_static/locales/zh_TW/LC_MESSAGES/booktheme.po new file mode 100644 index 00000000..42b43b86 --- /dev/null +++ b/v/pdev/_static/locales/zh_TW/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_TW\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Theme by the" +msgstr "佈景主題作者:" + +msgid "Open an issue" +msgstr "開啟議題" + +msgid "Contents" +msgstr "目錄" + +msgid "Download notebook file" +msgstr "下載 Notebook 檔案" + +msgid "Sphinx Book Theme" +msgstr "Sphinx Book 佈景主題" + +msgid "Fullscreen mode" +msgstr "全螢幕模式" + +msgid "Edit this page" +msgstr "編輯此頁面" + +msgid "By" +msgstr "作者:" + +msgid "Copyright" +msgstr "Copyright" + +msgid "Source repository" +msgstr "來源儲存庫" + +msgid "previous page" +msgstr "上一頁" + +msgid "next page" +msgstr "下一頁" + +msgid "Toggle navigation" +msgstr "顯示或隱藏導覽列" + +msgid "repository" +msgstr "儲存庫" + +msgid "suggest edit" +msgstr "提出修改建議" + +msgid "open issue" +msgstr "公開的問題" + +msgid "Launch" +msgstr "啟動" + +msgid "Print to PDF" +msgstr "列印成 PDF" + +msgid "By the" +msgstr "作者:" + +msgid "Last updated on" +msgstr "最後更新時間:" + +msgid "Download source file" +msgstr "下載原始檔" + +msgid "Download this page" +msgstr "下載此頁面" diff --git a/v/pdev/_static/logo.png b/v/pdev/_static/logo.png new file mode 100644 index 00000000..bb2e67d0 Binary files /dev/null and b/v/pdev/_static/logo.png differ diff --git a/v/pdev/_static/minus.png b/v/pdev/_static/minus.png new file mode 100644 index 00000000..d96755fd Binary files /dev/null and b/v/pdev/_static/minus.png differ diff --git a/v/pdev/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css b/v/pdev/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css new file mode 100644 index 00000000..33566310 --- /dev/null +++ b/v/pdev/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css @@ -0,0 +1,2342 @@ +/* Variables */ +:root { + --mystnb-source-bg-color: #f7f7f7; + --mystnb-stdout-bg-color: #fcfcfc; + --mystnb-stderr-bg-color: #fdd; + --mystnb-traceback-bg-color: #fcfcfc; + --mystnb-source-border-color: #ccc; + --mystnb-source-margin-color: green; + --mystnb-stdout-border-color: #f7f7f7; + --mystnb-stderr-border-color: #f7f7f7; + --mystnb-traceback-border-color: #ffd6d6; + --mystnb-hide-prompt-opacity: 70%; + --mystnb-source-border-radius: .4em; + --mystnb-source-border-width: 1px; +} + +/* Whole cell */ +div.container.cell { + padding-left: 0; + margin-bottom: 1em; +} + +/* Removing all background formatting so we can control at the div level */ +.cell_input div.highlight, +.cell_output pre, +.cell_input pre, +.cell_output .output { + border: none; + box-shadow: none; +} + +.cell_output .output pre, +.cell_input pre { + margin: 0px; +} + +/* Input cells */ +div.cell div.cell_input, +div.cell details.above-input>summary { + padding-left: 0em; + padding-right: 0em; + border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid; + background-color: var(--mystnb-source-bg-color); + border-left-color: var(--mystnb-source-margin-color); + border-left-width: medium; + border-radius: var(--mystnb-source-border-radius); +} + +div.cell_input>div, +div.cell_output div.output>div.highlight { + margin: 0em !important; + border: none !important; +} + +/* All cell outputs */ +.cell_output { + padding-left: 1em; + padding-right: 0em; + margin-top: 1em; +} + +/* Text outputs from cells */ +.cell_output .output.text_plain, +.cell_output .output.traceback, +.cell_output .output.stream, +.cell_output .output.stderr { + margin-top: 1em; + margin-bottom: 0em; + box-shadow: none; +} + +.cell_output .output.text_plain, +.cell_output .output.stream { + background: var(--mystnb-stdout-bg-color); + border: 1px solid var(--mystnb-stdout-border-color); +} + +.cell_output .output.stderr { + background: var(--mystnb-stderr-bg-color); + border: 1px solid var(--mystnb-stderr-border-color); +} + +.cell_output .output.traceback { + background: var(--mystnb-traceback-bg-color); + border: 1px solid var(--mystnb-traceback-border-color); +} + +/* Collapsible cell content */ +div.cell details.above-input div.cell_input { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-top: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed; +} + +div.cell div.cell_input.above-output-prompt { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +div.cell details.above-input>summary { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed; + padding-left: 1em; + margin-bottom: 0; +} + +div.cell details.above-output>summary { + background-color: var(--mystnb-source-bg-color); + padding-left: 1em; + padding-right: 0em; + border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid; + border-radius: var(--mystnb-source-border-radius); + border-left-color: var(--mystnb-source-margin-color); + border-left-width: medium; +} + +div.cell details.below-input>summary { + background-color: var(--mystnb-source-bg-color); + padding-left: 1em; + padding-right: 0em; + border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid; + border-top: none; + border-bottom-left-radius: var(--mystnb-source-border-radius); + border-bottom-right-radius: var(--mystnb-source-border-radius); + border-left-color: var(--mystnb-source-margin-color); + border-left-width: medium; +} + +div.cell details.hide>summary>span { + opacity: var(--mystnb-hide-prompt-opacity); +} + +div.cell details.hide[open]>summary>span.collapsed { + display: none; +} + +div.cell details.hide:not([open])>summary>span.expanded { + display: none; +} + +@keyframes collapsed-fade-in { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} +div.cell details.hide[open]>summary~* { + -moz-animation: collapsed-fade-in 0.3s ease-in-out; + -webkit-animation: collapsed-fade-in 0.3s ease-in-out; + animation: collapsed-fade-in 0.3s ease-in-out; +} + +/* Math align to the left */ +.cell_output .MathJax_Display { + text-align: left !important; +} + +/* Pandas tables. Pulled from the Jupyter / nbsphinx CSS */ +div.cell_output table { + border: none; + border-collapse: collapse; + border-spacing: 0; + color: black; + font-size: 1em; + table-layout: fixed; +} + +div.cell_output thead { + border-bottom: 1px solid black; + vertical-align: bottom; +} + +div.cell_output tr, +div.cell_output th, +div.cell_output td { + text-align: right; + vertical-align: middle; + padding: 0.5em 0.5em; + line-height: normal; + white-space: normal; + max-width: none; + border: none; +} + +div.cell_output th { + font-weight: bold; +} + +div.cell_output tbody tr:nth-child(odd) { + background: #f5f5f5; +} + +div.cell_output tbody tr:hover { + background: rgba(66, 165, 245, 0.2); +} + +/** source code line numbers **/ +span.linenos { + opacity: 0.5; +} + +/* Inline text from `paste` operation */ + +span.pasted-text { + font-weight: bold; +} + +span.pasted-inline img { + max-height: 2em; +} + +tbody span.pasted-inline img { + max-height: none; +} + +/* Font colors for translated ANSI escape sequences +Color values are copied from Jupyter Notebook +https://github.com/jupyter/notebook/blob/52581f8eda9b319eb0390ac77fe5903c38f81e3e/notebook/static/notebook/less/ansicolors.less#L14-L21 +Background colors from +https://nbsphinx.readthedocs.io/en/latest/code-cells.html#ANSI-Colors +*/ +div.highlight .-Color-Bold { + font-weight: bold; +} + +div.highlight .-Color[class*=-Black] { + color: #3E424D +} + +div.highlight .-Color[class*=-Red] { + color: #E75C58 +} + +div.highlight .-Color[class*=-Green] { + color: #00A250 +} + +div.highlight .-Color[class*=-Yellow] { + color: #DDB62B +} + +div.highlight .-Color[class*=-Blue] { + color: #208FFB +} + +div.highlight .-Color[class*=-Magenta] { + color: #D160C4 +} + +div.highlight .-Color[class*=-Cyan] { + color: #60C6C8 +} + +div.highlight .-Color[class*=-White] { + color: #C5C1B4 +} + +div.highlight .-Color[class*=-BGBlack] { + background-color: #3E424D +} + +div.highlight .-Color[class*=-BGRed] { + background-color: #E75C58 +} + +div.highlight .-Color[class*=-BGGreen] { + background-color: #00A250 +} + +div.highlight .-Color[class*=-BGYellow] { + background-color: #DDB62B +} + +div.highlight .-Color[class*=-BGBlue] { + background-color: #208FFB +} + +div.highlight .-Color[class*=-BGMagenta] { + background-color: #D160C4 +} + +div.highlight .-Color[class*=-BGCyan] { + background-color: #60C6C8 +} + +div.highlight .-Color[class*=-BGWhite] { + background-color: #C5C1B4 +} + +/* Font colors for 8-bit ANSI */ + +div.highlight .-Color[class*=-C0] { + color: #000000 +} + +div.highlight .-Color[class*=-BGC0] { + background-color: #000000 +} + +div.highlight .-Color[class*=-C1] { + color: #800000 +} + +div.highlight .-Color[class*=-BGC1] { + background-color: #800000 +} + +div.highlight .-Color[class*=-C2] { + color: #008000 +} + +div.highlight .-Color[class*=-BGC2] { + background-color: #008000 +} + +div.highlight .-Color[class*=-C3] { + color: #808000 +} + +div.highlight .-Color[class*=-BGC3] { + background-color: #808000 +} + +div.highlight .-Color[class*=-C4] { + color: #000080 +} + +div.highlight .-Color[class*=-BGC4] { + background-color: #000080 +} + +div.highlight .-Color[class*=-C5] { + color: #800080 +} + +div.highlight .-Color[class*=-BGC5] { + background-color: #800080 +} + +div.highlight .-Color[class*=-C6] { + color: #008080 +} + +div.highlight .-Color[class*=-BGC6] { + background-color: #008080 +} + +div.highlight .-Color[class*=-C7] { + color: #C0C0C0 +} + +div.highlight .-Color[class*=-BGC7] { + background-color: #C0C0C0 +} + +div.highlight .-Color[class*=-C8] { + color: #808080 +} + +div.highlight .-Color[class*=-BGC8] { + background-color: #808080 +} + +div.highlight .-Color[class*=-C9] { + color: #FF0000 +} + +div.highlight .-Color[class*=-BGC9] { + background-color: #FF0000 +} + +div.highlight .-Color[class*=-C10] { + color: #00FF00 +} + +div.highlight .-Color[class*=-BGC10] { + background-color: #00FF00 +} + +div.highlight .-Color[class*=-C11] { + color: #FFFF00 +} + +div.highlight .-Color[class*=-BGC11] { + background-color: #FFFF00 +} + +div.highlight .-Color[class*=-C12] { + color: #0000FF +} + +div.highlight .-Color[class*=-BGC12] { + background-color: #0000FF +} + +div.highlight .-Color[class*=-C13] { + color: #FF00FF +} + +div.highlight .-Color[class*=-BGC13] { + background-color: #FF00FF +} + +div.highlight .-Color[class*=-C14] { + color: #00FFFF +} + +div.highlight .-Color[class*=-BGC14] { + background-color: #00FFFF +} + +div.highlight .-Color[class*=-C15] { + color: #FFFFFF +} + +div.highlight .-Color[class*=-BGC15] { + background-color: #FFFFFF +} + +div.highlight .-Color[class*=-C16] { + color: #000000 +} + +div.highlight .-Color[class*=-BGC16] { + background-color: #000000 +} + +div.highlight .-Color[class*=-C17] { + color: #00005F +} + +div.highlight .-Color[class*=-BGC17] { + background-color: #00005F +} + +div.highlight .-Color[class*=-C18] { + color: #000087 +} + +div.highlight .-Color[class*=-BGC18] { + background-color: #000087 +} + +div.highlight .-Color[class*=-C19] { + color: #0000AF +} + +div.highlight .-Color[class*=-BGC19] { + background-color: #0000AF +} + +div.highlight .-Color[class*=-C20] { + color: #0000D7 +} + +div.highlight .-Color[class*=-BGC20] { + background-color: #0000D7 +} + +div.highlight .-Color[class*=-C21] { + color: #0000FF +} + +div.highlight .-Color[class*=-BGC21] { + background-color: #0000FF +} + +div.highlight .-Color[class*=-C22] { + color: #005F00 +} + +div.highlight .-Color[class*=-BGC22] { + background-color: #005F00 +} + +div.highlight .-Color[class*=-C23] { + color: #005F5F +} + +div.highlight .-Color[class*=-BGC23] { + background-color: #005F5F +} + +div.highlight .-Color[class*=-C24] { + color: #005F87 +} + +div.highlight .-Color[class*=-BGC24] { + background-color: #005F87 +} + +div.highlight .-Color[class*=-C25] { + color: #005FAF +} + +div.highlight .-Color[class*=-BGC25] { + background-color: #005FAF +} + +div.highlight .-Color[class*=-C26] { + color: #005FD7 +} + +div.highlight .-Color[class*=-BGC26] { + background-color: #005FD7 +} + +div.highlight .-Color[class*=-C27] { + color: #005FFF +} + +div.highlight .-Color[class*=-BGC27] { + background-color: #005FFF +} + +div.highlight .-Color[class*=-C28] { + color: #008700 +} + +div.highlight .-Color[class*=-BGC28] { + background-color: #008700 +} + +div.highlight .-Color[class*=-C29] { + color: #00875F +} + +div.highlight .-Color[class*=-BGC29] { + background-color: #00875F +} + +div.highlight .-Color[class*=-C30] { + color: #008787 +} + +div.highlight .-Color[class*=-BGC30] { + background-color: #008787 +} + +div.highlight .-Color[class*=-C31] { + color: #0087AF +} + +div.highlight .-Color[class*=-BGC31] { + background-color: #0087AF +} + +div.highlight .-Color[class*=-C32] { + color: #0087D7 +} + +div.highlight .-Color[class*=-BGC32] { + background-color: #0087D7 +} + +div.highlight .-Color[class*=-C33] { + color: #0087FF +} + +div.highlight .-Color[class*=-BGC33] { + background-color: #0087FF +} + +div.highlight .-Color[class*=-C34] { + color: #00AF00 +} + +div.highlight .-Color[class*=-BGC34] { + background-color: #00AF00 +} + +div.highlight .-Color[class*=-C35] { + color: #00AF5F +} + +div.highlight .-Color[class*=-BGC35] { + background-color: #00AF5F +} + +div.highlight .-Color[class*=-C36] { + color: #00AF87 +} + +div.highlight .-Color[class*=-BGC36] { + background-color: #00AF87 +} + +div.highlight .-Color[class*=-C37] { + color: #00AFAF +} + +div.highlight .-Color[class*=-BGC37] { + background-color: #00AFAF +} + +div.highlight .-Color[class*=-C38] { + color: #00AFD7 +} + +div.highlight .-Color[class*=-BGC38] { + background-color: #00AFD7 +} + +div.highlight .-Color[class*=-C39] { + color: #00AFFF +} + +div.highlight .-Color[class*=-BGC39] { + background-color: #00AFFF +} + +div.highlight .-Color[class*=-C40] { + color: #00D700 +} + +div.highlight .-Color[class*=-BGC40] { + background-color: #00D700 +} + +div.highlight .-Color[class*=-C41] { + color: #00D75F +} + +div.highlight .-Color[class*=-BGC41] { + background-color: #00D75F +} + +div.highlight .-Color[class*=-C42] { + color: #00D787 +} + +div.highlight .-Color[class*=-BGC42] { + background-color: #00D787 +} + +div.highlight .-Color[class*=-C43] { + color: #00D7AF +} + +div.highlight .-Color[class*=-BGC43] { + background-color: #00D7AF +} + +div.highlight .-Color[class*=-C44] { + color: #00D7D7 +} + +div.highlight .-Color[class*=-BGC44] { + background-color: #00D7D7 +} + +div.highlight .-Color[class*=-C45] { + color: #00D7FF +} + +div.highlight .-Color[class*=-BGC45] { + background-color: #00D7FF +} + +div.highlight .-Color[class*=-C46] { + color: #00FF00 +} + +div.highlight .-Color[class*=-BGC46] { + background-color: #00FF00 +} + +div.highlight .-Color[class*=-C47] { + color: #00FF5F +} + +div.highlight .-Color[class*=-BGC47] { + background-color: #00FF5F +} + +div.highlight .-Color[class*=-C48] { + color: #00FF87 +} + +div.highlight .-Color[class*=-BGC48] { + background-color: #00FF87 +} + +div.highlight .-Color[class*=-C49] { + color: #00FFAF +} + +div.highlight .-Color[class*=-BGC49] { + background-color: #00FFAF +} + +div.highlight .-Color[class*=-C50] { + color: #00FFD7 +} + +div.highlight .-Color[class*=-BGC50] { + background-color: #00FFD7 +} + +div.highlight .-Color[class*=-C51] { + color: #00FFFF +} + +div.highlight .-Color[class*=-BGC51] { + background-color: #00FFFF +} + +div.highlight .-Color[class*=-C52] { + color: #5F0000 +} + +div.highlight .-Color[class*=-BGC52] { + background-color: #5F0000 +} + +div.highlight .-Color[class*=-C53] { + color: #5F005F +} + +div.highlight .-Color[class*=-BGC53] { + background-color: #5F005F +} + +div.highlight .-Color[class*=-C54] { + color: #5F0087 +} + +div.highlight .-Color[class*=-BGC54] { + background-color: #5F0087 +} + +div.highlight .-Color[class*=-C55] { + color: #5F00AF +} + +div.highlight .-Color[class*=-BGC55] { + background-color: #5F00AF +} + +div.highlight .-Color[class*=-C56] { + color: #5F00D7 +} + +div.highlight .-Color[class*=-BGC56] { + background-color: #5F00D7 +} + +div.highlight .-Color[class*=-C57] { + color: #5F00FF +} + +div.highlight .-Color[class*=-BGC57] { + background-color: #5F00FF +} + +div.highlight .-Color[class*=-C58] { + color: #5F5F00 +} + +div.highlight .-Color[class*=-BGC58] { + background-color: #5F5F00 +} + +div.highlight .-Color[class*=-C59] { + color: #5F5F5F +} + +div.highlight .-Color[class*=-BGC59] { + background-color: #5F5F5F +} + +div.highlight .-Color[class*=-C60] { + color: #5F5F87 +} + +div.highlight .-Color[class*=-BGC60] { + background-color: #5F5F87 +} + +div.highlight .-Color[class*=-C61] { + color: #5F5FAF +} + +div.highlight .-Color[class*=-BGC61] { + background-color: #5F5FAF +} + +div.highlight .-Color[class*=-C62] { + color: #5F5FD7 +} + +div.highlight .-Color[class*=-BGC62] { + background-color: #5F5FD7 +} + +div.highlight .-Color[class*=-C63] { + color: #5F5FFF +} + +div.highlight .-Color[class*=-BGC63] { + background-color: #5F5FFF +} + +div.highlight .-Color[class*=-C64] { + color: #5F8700 +} + +div.highlight .-Color[class*=-BGC64] { + background-color: #5F8700 +} + +div.highlight .-Color[class*=-C65] { + color: #5F875F +} + +div.highlight .-Color[class*=-BGC65] { + background-color: #5F875F +} + +div.highlight .-Color[class*=-C66] { + color: #5F8787 +} + +div.highlight .-Color[class*=-BGC66] { + background-color: #5F8787 +} + +div.highlight .-Color[class*=-C67] { + color: #5F87AF +} + +div.highlight .-Color[class*=-BGC67] { + background-color: #5F87AF +} + +div.highlight .-Color[class*=-C68] { + color: #5F87D7 +} + +div.highlight .-Color[class*=-BGC68] { + background-color: #5F87D7 +} + +div.highlight .-Color[class*=-C69] { + color: #5F87FF +} + +div.highlight .-Color[class*=-BGC69] { + background-color: #5F87FF +} + +div.highlight .-Color[class*=-C70] { + color: #5FAF00 +} + +div.highlight .-Color[class*=-BGC70] { + background-color: #5FAF00 +} + +div.highlight .-Color[class*=-C71] { + color: #5FAF5F +} + +div.highlight .-Color[class*=-BGC71] { + background-color: #5FAF5F +} + +div.highlight .-Color[class*=-C72] { + color: #5FAF87 +} + +div.highlight .-Color[class*=-BGC72] { + background-color: #5FAF87 +} + +div.highlight .-Color[class*=-C73] { + color: #5FAFAF +} + +div.highlight .-Color[class*=-BGC73] { + background-color: #5FAFAF +} + +div.highlight .-Color[class*=-C74] { + color: #5FAFD7 +} + +div.highlight .-Color[class*=-BGC74] { + background-color: #5FAFD7 +} + +div.highlight .-Color[class*=-C75] { + color: #5FAFFF +} + +div.highlight .-Color[class*=-BGC75] { + background-color: #5FAFFF +} + +div.highlight .-Color[class*=-C76] { + color: #5FD700 +} + +div.highlight .-Color[class*=-BGC76] { + background-color: #5FD700 +} + +div.highlight .-Color[class*=-C77] { + color: #5FD75F +} + +div.highlight .-Color[class*=-BGC77] { + background-color: #5FD75F +} + +div.highlight .-Color[class*=-C78] { + color: #5FD787 +} + +div.highlight .-Color[class*=-BGC78] { + background-color: #5FD787 +} + +div.highlight .-Color[class*=-C79] { + color: #5FD7AF +} + +div.highlight .-Color[class*=-BGC79] { + background-color: #5FD7AF +} + +div.highlight .-Color[class*=-C80] { + color: #5FD7D7 +} + +div.highlight .-Color[class*=-BGC80] { + background-color: #5FD7D7 +} + +div.highlight .-Color[class*=-C81] { + color: #5FD7FF +} + +div.highlight .-Color[class*=-BGC81] { + background-color: #5FD7FF +} + +div.highlight .-Color[class*=-C82] { + color: #5FFF00 +} + +div.highlight .-Color[class*=-BGC82] { + background-color: #5FFF00 +} + +div.highlight .-Color[class*=-C83] { + color: #5FFF5F +} + +div.highlight .-Color[class*=-BGC83] { + background-color: #5FFF5F +} + +div.highlight .-Color[class*=-C84] { + color: #5FFF87 +} + +div.highlight .-Color[class*=-BGC84] { + background-color: #5FFF87 +} + +div.highlight .-Color[class*=-C85] { + color: #5FFFAF +} + +div.highlight .-Color[class*=-BGC85] { + background-color: #5FFFAF +} + +div.highlight .-Color[class*=-C86] { + color: #5FFFD7 +} + +div.highlight .-Color[class*=-BGC86] { + background-color: #5FFFD7 +} + +div.highlight .-Color[class*=-C87] { + color: #5FFFFF +} + +div.highlight .-Color[class*=-BGC87] { + background-color: #5FFFFF +} + +div.highlight .-Color[class*=-C88] { + color: #870000 +} + +div.highlight .-Color[class*=-BGC88] { + background-color: #870000 +} + +div.highlight .-Color[class*=-C89] { + color: #87005F +} + +div.highlight .-Color[class*=-BGC89] { + background-color: #87005F +} + +div.highlight .-Color[class*=-C90] { + color: #870087 +} + +div.highlight .-Color[class*=-BGC90] { + background-color: #870087 +} + +div.highlight .-Color[class*=-C91] { + color: #8700AF +} + +div.highlight .-Color[class*=-BGC91] { + background-color: #8700AF +} + +div.highlight .-Color[class*=-C92] { + color: #8700D7 +} + +div.highlight .-Color[class*=-BGC92] { + background-color: #8700D7 +} + +div.highlight .-Color[class*=-C93] { + color: #8700FF +} + +div.highlight .-Color[class*=-BGC93] { + background-color: #8700FF +} + +div.highlight .-Color[class*=-C94] { + color: #875F00 +} + +div.highlight .-Color[class*=-BGC94] { + background-color: #875F00 +} + +div.highlight .-Color[class*=-C95] { + color: #875F5F +} + +div.highlight .-Color[class*=-BGC95] { + background-color: #875F5F +} + +div.highlight .-Color[class*=-C96] { + color: #875F87 +} + +div.highlight .-Color[class*=-BGC96] { + background-color: #875F87 +} + +div.highlight .-Color[class*=-C97] { + color: #875FAF +} + +div.highlight .-Color[class*=-BGC97] { + background-color: #875FAF +} + +div.highlight .-Color[class*=-C98] { + color: #875FD7 +} + +div.highlight .-Color[class*=-BGC98] { + background-color: #875FD7 +} + +div.highlight .-Color[class*=-C99] { + color: #875FFF +} + +div.highlight .-Color[class*=-BGC99] { + background-color: #875FFF +} + +div.highlight .-Color[class*=-C100] { + color: #878700 +} + +div.highlight .-Color[class*=-BGC100] { + background-color: #878700 +} + +div.highlight .-Color[class*=-C101] { + color: #87875F +} + +div.highlight .-Color[class*=-BGC101] { + background-color: #87875F +} + +div.highlight .-Color[class*=-C102] { + color: #878787 +} + +div.highlight .-Color[class*=-BGC102] { + background-color: #878787 +} + +div.highlight .-Color[class*=-C103] { + color: #8787AF +} + +div.highlight .-Color[class*=-BGC103] { + background-color: #8787AF +} + +div.highlight .-Color[class*=-C104] { + color: #8787D7 +} + +div.highlight .-Color[class*=-BGC104] { + background-color: #8787D7 +} + +div.highlight .-Color[class*=-C105] { + color: #8787FF +} + +div.highlight .-Color[class*=-BGC105] { + background-color: #8787FF +} + +div.highlight .-Color[class*=-C106] { + color: #87AF00 +} + +div.highlight .-Color[class*=-BGC106] { + background-color: #87AF00 +} + +div.highlight .-Color[class*=-C107] { + color: #87AF5F +} + +div.highlight .-Color[class*=-BGC107] { + background-color: #87AF5F +} + +div.highlight .-Color[class*=-C108] { + color: #87AF87 +} + +div.highlight .-Color[class*=-BGC108] { + background-color: #87AF87 +} + +div.highlight .-Color[class*=-C109] { + color: #87AFAF +} + +div.highlight .-Color[class*=-BGC109] { + background-color: #87AFAF +} + +div.highlight .-Color[class*=-C110] { + color: #87AFD7 +} + +div.highlight .-Color[class*=-BGC110] { + background-color: #87AFD7 +} + +div.highlight .-Color[class*=-C111] { + color: #87AFFF +} + +div.highlight .-Color[class*=-BGC111] { + background-color: #87AFFF +} + +div.highlight .-Color[class*=-C112] { + color: #87D700 +} + +div.highlight .-Color[class*=-BGC112] { + background-color: #87D700 +} + +div.highlight .-Color[class*=-C113] { + color: #87D75F +} + +div.highlight .-Color[class*=-BGC113] { + background-color: #87D75F +} + +div.highlight .-Color[class*=-C114] { + color: #87D787 +} + +div.highlight .-Color[class*=-BGC114] { + background-color: #87D787 +} + +div.highlight .-Color[class*=-C115] { + color: #87D7AF +} + +div.highlight .-Color[class*=-BGC115] { + background-color: #87D7AF +} + +div.highlight .-Color[class*=-C116] { + color: #87D7D7 +} + +div.highlight .-Color[class*=-BGC116] { + background-color: #87D7D7 +} + +div.highlight .-Color[class*=-C117] { + color: #87D7FF +} + +div.highlight .-Color[class*=-BGC117] { + background-color: #87D7FF +} + +div.highlight .-Color[class*=-C118] { + color: #87FF00 +} + +div.highlight .-Color[class*=-BGC118] { + background-color: #87FF00 +} + +div.highlight .-Color[class*=-C119] { + color: #87FF5F +} + +div.highlight .-Color[class*=-BGC119] { + background-color: #87FF5F +} + +div.highlight .-Color[class*=-C120] { + color: #87FF87 +} + +div.highlight .-Color[class*=-BGC120] { + background-color: #87FF87 +} + +div.highlight .-Color[class*=-C121] { + color: #87FFAF +} + +div.highlight .-Color[class*=-BGC121] { + background-color: #87FFAF +} + +div.highlight .-Color[class*=-C122] { + color: #87FFD7 +} + +div.highlight .-Color[class*=-BGC122] { + background-color: #87FFD7 +} + +div.highlight .-Color[class*=-C123] { + color: #87FFFF +} + +div.highlight .-Color[class*=-BGC123] { + background-color: #87FFFF +} + +div.highlight .-Color[class*=-C124] { + color: #AF0000 +} + +div.highlight .-Color[class*=-BGC124] { + background-color: #AF0000 +} + +div.highlight .-Color[class*=-C125] { + color: #AF005F +} + +div.highlight .-Color[class*=-BGC125] { + background-color: #AF005F +} + +div.highlight .-Color[class*=-C126] { + color: #AF0087 +} + +div.highlight .-Color[class*=-BGC126] { + background-color: #AF0087 +} + +div.highlight .-Color[class*=-C127] { + color: #AF00AF +} + +div.highlight .-Color[class*=-BGC127] { + background-color: #AF00AF +} + +div.highlight .-Color[class*=-C128] { + color: #AF00D7 +} + +div.highlight .-Color[class*=-BGC128] { + background-color: #AF00D7 +} + +div.highlight .-Color[class*=-C129] { + color: #AF00FF +} + +div.highlight .-Color[class*=-BGC129] { + background-color: #AF00FF +} + +div.highlight .-Color[class*=-C130] { + color: #AF5F00 +} + +div.highlight .-Color[class*=-BGC130] { + background-color: #AF5F00 +} + +div.highlight .-Color[class*=-C131] { + color: #AF5F5F +} + +div.highlight .-Color[class*=-BGC131] { + background-color: #AF5F5F +} + +div.highlight .-Color[class*=-C132] { + color: #AF5F87 +} + +div.highlight .-Color[class*=-BGC132] { + background-color: #AF5F87 +} + +div.highlight .-Color[class*=-C133] { + color: #AF5FAF +} + +div.highlight .-Color[class*=-BGC133] { + background-color: #AF5FAF +} + +div.highlight .-Color[class*=-C134] { + color: #AF5FD7 +} + +div.highlight .-Color[class*=-BGC134] { + background-color: #AF5FD7 +} + +div.highlight .-Color[class*=-C135] { + color: #AF5FFF +} + +div.highlight .-Color[class*=-BGC135] { + background-color: #AF5FFF +} + +div.highlight .-Color[class*=-C136] { + color: #AF8700 +} + +div.highlight .-Color[class*=-BGC136] { + background-color: #AF8700 +} + +div.highlight .-Color[class*=-C137] { + color: #AF875F +} + +div.highlight .-Color[class*=-BGC137] { + background-color: #AF875F +} + +div.highlight .-Color[class*=-C138] { + color: #AF8787 +} + +div.highlight .-Color[class*=-BGC138] { + background-color: #AF8787 +} + +div.highlight .-Color[class*=-C139] { + color: #AF87AF +} + +div.highlight .-Color[class*=-BGC139] { + background-color: #AF87AF +} + +div.highlight .-Color[class*=-C140] { + color: #AF87D7 +} + +div.highlight .-Color[class*=-BGC140] { + background-color: #AF87D7 +} + +div.highlight .-Color[class*=-C141] { + color: #AF87FF +} + +div.highlight .-Color[class*=-BGC141] { + background-color: #AF87FF +} + +div.highlight .-Color[class*=-C142] { + color: #AFAF00 +} + +div.highlight .-Color[class*=-BGC142] { + background-color: #AFAF00 +} + +div.highlight .-Color[class*=-C143] { + color: #AFAF5F +} + +div.highlight .-Color[class*=-BGC143] { + background-color: #AFAF5F +} + +div.highlight .-Color[class*=-C144] { + color: #AFAF87 +} + +div.highlight .-Color[class*=-BGC144] { + background-color: #AFAF87 +} + +div.highlight .-Color[class*=-C145] { + color: #AFAFAF +} + +div.highlight .-Color[class*=-BGC145] { + background-color: #AFAFAF +} + +div.highlight .-Color[class*=-C146] { + color: #AFAFD7 +} + +div.highlight .-Color[class*=-BGC146] { + background-color: #AFAFD7 +} + +div.highlight .-Color[class*=-C147] { + color: #AFAFFF +} + +div.highlight .-Color[class*=-BGC147] { + background-color: #AFAFFF +} + +div.highlight .-Color[class*=-C148] { + color: #AFD700 +} + +div.highlight .-Color[class*=-BGC148] { + background-color: #AFD700 +} + +div.highlight .-Color[class*=-C149] { + color: #AFD75F +} + +div.highlight .-Color[class*=-BGC149] { + background-color: #AFD75F +} + +div.highlight .-Color[class*=-C150] { + color: #AFD787 +} + +div.highlight .-Color[class*=-BGC150] { + background-color: #AFD787 +} + +div.highlight .-Color[class*=-C151] { + color: #AFD7AF +} + +div.highlight .-Color[class*=-BGC151] { + background-color: #AFD7AF +} + +div.highlight .-Color[class*=-C152] { + color: #AFD7D7 +} + +div.highlight .-Color[class*=-BGC152] { + background-color: #AFD7D7 +} + +div.highlight .-Color[class*=-C153] { + color: #AFD7FF +} + +div.highlight .-Color[class*=-BGC153] { + background-color: #AFD7FF +} + +div.highlight .-Color[class*=-C154] { + color: #AFFF00 +} + +div.highlight .-Color[class*=-BGC154] { + background-color: #AFFF00 +} + +div.highlight .-Color[class*=-C155] { + color: #AFFF5F +} + +div.highlight .-Color[class*=-BGC155] { + background-color: #AFFF5F +} + +div.highlight .-Color[class*=-C156] { + color: #AFFF87 +} + +div.highlight .-Color[class*=-BGC156] { + background-color: #AFFF87 +} + +div.highlight .-Color[class*=-C157] { + color: #AFFFAF +} + +div.highlight .-Color[class*=-BGC157] { + background-color: #AFFFAF +} + +div.highlight .-Color[class*=-C158] { + color: #AFFFD7 +} + +div.highlight .-Color[class*=-BGC158] { + background-color: #AFFFD7 +} + +div.highlight .-Color[class*=-C159] { + color: #AFFFFF +} + +div.highlight .-Color[class*=-BGC159] { + background-color: #AFFFFF +} + +div.highlight .-Color[class*=-C160] { + color: #D70000 +} + +div.highlight .-Color[class*=-BGC160] { + background-color: #D70000 +} + +div.highlight .-Color[class*=-C161] { + color: #D7005F +} + +div.highlight .-Color[class*=-BGC161] { + background-color: #D7005F +} + +div.highlight .-Color[class*=-C162] { + color: #D70087 +} + +div.highlight .-Color[class*=-BGC162] { + background-color: #D70087 +} + +div.highlight .-Color[class*=-C163] { + color: #D700AF +} + +div.highlight .-Color[class*=-BGC163] { + background-color: #D700AF +} + +div.highlight .-Color[class*=-C164] { + color: #D700D7 +} + +div.highlight .-Color[class*=-BGC164] { + background-color: #D700D7 +} + +div.highlight .-Color[class*=-C165] { + color: #D700FF +} + +div.highlight .-Color[class*=-BGC165] { + background-color: #D700FF +} + +div.highlight .-Color[class*=-C166] { + color: #D75F00 +} + +div.highlight .-Color[class*=-BGC166] { + background-color: #D75F00 +} + +div.highlight .-Color[class*=-C167] { + color: #D75F5F +} + +div.highlight .-Color[class*=-BGC167] { + background-color: #D75F5F +} + +div.highlight .-Color[class*=-C168] { + color: #D75F87 +} + +div.highlight .-Color[class*=-BGC168] { + background-color: #D75F87 +} + +div.highlight .-Color[class*=-C169] { + color: #D75FAF +} + +div.highlight .-Color[class*=-BGC169] { + background-color: #D75FAF +} + +div.highlight .-Color[class*=-C170] { + color: #D75FD7 +} + +div.highlight .-Color[class*=-BGC170] { + background-color: #D75FD7 +} + +div.highlight .-Color[class*=-C171] { + color: #D75FFF +} + +div.highlight .-Color[class*=-BGC171] { + background-color: #D75FFF +} + +div.highlight .-Color[class*=-C172] { + color: #D78700 +} + +div.highlight .-Color[class*=-BGC172] { + background-color: #D78700 +} + +div.highlight .-Color[class*=-C173] { + color: #D7875F +} + +div.highlight .-Color[class*=-BGC173] { + background-color: #D7875F +} + +div.highlight .-Color[class*=-C174] { + color: #D78787 +} + +div.highlight .-Color[class*=-BGC174] { + background-color: #D78787 +} + +div.highlight .-Color[class*=-C175] { + color: #D787AF +} + +div.highlight .-Color[class*=-BGC175] { + background-color: #D787AF +} + +div.highlight .-Color[class*=-C176] { + color: #D787D7 +} + +div.highlight .-Color[class*=-BGC176] { + background-color: #D787D7 +} + +div.highlight .-Color[class*=-C177] { + color: #D787FF +} + +div.highlight .-Color[class*=-BGC177] { + background-color: #D787FF +} + +div.highlight .-Color[class*=-C178] { + color: #D7AF00 +} + +div.highlight .-Color[class*=-BGC178] { + background-color: #D7AF00 +} + +div.highlight .-Color[class*=-C179] { + color: #D7AF5F +} + +div.highlight .-Color[class*=-BGC179] { + background-color: #D7AF5F +} + +div.highlight .-Color[class*=-C180] { + color: #D7AF87 +} + +div.highlight .-Color[class*=-BGC180] { + background-color: #D7AF87 +} + +div.highlight .-Color[class*=-C181] { + color: #D7AFAF +} + +div.highlight .-Color[class*=-BGC181] { + background-color: #D7AFAF +} + +div.highlight .-Color[class*=-C182] { + color: #D7AFD7 +} + +div.highlight .-Color[class*=-BGC182] { + background-color: #D7AFD7 +} + +div.highlight .-Color[class*=-C183] { + color: #D7AFFF +} + +div.highlight .-Color[class*=-BGC183] { + background-color: #D7AFFF +} + +div.highlight .-Color[class*=-C184] { + color: #D7D700 +} + +div.highlight .-Color[class*=-BGC184] { + background-color: #D7D700 +} + +div.highlight .-Color[class*=-C185] { + color: #D7D75F +} + +div.highlight .-Color[class*=-BGC185] { + background-color: #D7D75F +} + +div.highlight .-Color[class*=-C186] { + color: #D7D787 +} + +div.highlight .-Color[class*=-BGC186] { + background-color: #D7D787 +} + +div.highlight .-Color[class*=-C187] { + color: #D7D7AF +} + +div.highlight .-Color[class*=-BGC187] { + background-color: #D7D7AF +} + +div.highlight .-Color[class*=-C188] { + color: #D7D7D7 +} + +div.highlight .-Color[class*=-BGC188] { + background-color: #D7D7D7 +} + +div.highlight .-Color[class*=-C189] { + color: #D7D7FF +} + +div.highlight .-Color[class*=-BGC189] { + background-color: #D7D7FF +} + +div.highlight .-Color[class*=-C190] { + color: #D7FF00 +} + +div.highlight .-Color[class*=-BGC190] { + background-color: #D7FF00 +} + +div.highlight .-Color[class*=-C191] { + color: #D7FF5F +} + +div.highlight .-Color[class*=-BGC191] { + background-color: #D7FF5F +} + +div.highlight .-Color[class*=-C192] { + color: #D7FF87 +} + +div.highlight .-Color[class*=-BGC192] { + background-color: #D7FF87 +} + +div.highlight .-Color[class*=-C193] { + color: #D7FFAF +} + +div.highlight .-Color[class*=-BGC193] { + background-color: #D7FFAF +} + +div.highlight .-Color[class*=-C194] { + color: #D7FFD7 +} + +div.highlight .-Color[class*=-BGC194] { + background-color: #D7FFD7 +} + +div.highlight .-Color[class*=-C195] { + color: #D7FFFF +} + +div.highlight .-Color[class*=-BGC195] { + background-color: #D7FFFF +} + +div.highlight .-Color[class*=-C196] { + color: #FF0000 +} + +div.highlight .-Color[class*=-BGC196] { + background-color: #FF0000 +} + +div.highlight .-Color[class*=-C197] { + color: #FF005F +} + +div.highlight .-Color[class*=-BGC197] { + background-color: #FF005F +} + +div.highlight .-Color[class*=-C198] { + color: #FF0087 +} + +div.highlight .-Color[class*=-BGC198] { + background-color: #FF0087 +} + +div.highlight .-Color[class*=-C199] { + color: #FF00AF +} + +div.highlight .-Color[class*=-BGC199] { + background-color: #FF00AF +} + +div.highlight .-Color[class*=-C200] { + color: #FF00D7 +} + +div.highlight .-Color[class*=-BGC200] { + background-color: #FF00D7 +} + +div.highlight .-Color[class*=-C201] { + color: #FF00FF +} + +div.highlight .-Color[class*=-BGC201] { + background-color: #FF00FF +} + +div.highlight .-Color[class*=-C202] { + color: #FF5F00 +} + +div.highlight .-Color[class*=-BGC202] { + background-color: #FF5F00 +} + +div.highlight .-Color[class*=-C203] { + color: #FF5F5F +} + +div.highlight .-Color[class*=-BGC203] { + background-color: #FF5F5F +} + +div.highlight .-Color[class*=-C204] { + color: #FF5F87 +} + +div.highlight .-Color[class*=-BGC204] { + background-color: #FF5F87 +} + +div.highlight .-Color[class*=-C205] { + color: #FF5FAF +} + +div.highlight .-Color[class*=-BGC205] { + background-color: #FF5FAF +} + +div.highlight .-Color[class*=-C206] { + color: #FF5FD7 +} + +div.highlight .-Color[class*=-BGC206] { + background-color: #FF5FD7 +} + +div.highlight .-Color[class*=-C207] { + color: #FF5FFF +} + +div.highlight .-Color[class*=-BGC207] { + background-color: #FF5FFF +} + +div.highlight .-Color[class*=-C208] { + color: #FF8700 +} + +div.highlight .-Color[class*=-BGC208] { + background-color: #FF8700 +} + +div.highlight .-Color[class*=-C209] { + color: #FF875F +} + +div.highlight .-Color[class*=-BGC209] { + background-color: #FF875F +} + +div.highlight .-Color[class*=-C210] { + color: #FF8787 +} + +div.highlight .-Color[class*=-BGC210] { + background-color: #FF8787 +} + +div.highlight .-Color[class*=-C211] { + color: #FF87AF +} + +div.highlight .-Color[class*=-BGC211] { + background-color: #FF87AF +} + +div.highlight .-Color[class*=-C212] { + color: #FF87D7 +} + +div.highlight .-Color[class*=-BGC212] { + background-color: #FF87D7 +} + +div.highlight .-Color[class*=-C213] { + color: #FF87FF +} + +div.highlight .-Color[class*=-BGC213] { + background-color: #FF87FF +} + +div.highlight .-Color[class*=-C214] { + color: #FFAF00 +} + +div.highlight .-Color[class*=-BGC214] { + background-color: #FFAF00 +} + +div.highlight .-Color[class*=-C215] { + color: #FFAF5F +} + +div.highlight .-Color[class*=-BGC215] { + background-color: #FFAF5F +} + +div.highlight .-Color[class*=-C216] { + color: #FFAF87 +} + +div.highlight .-Color[class*=-BGC216] { + background-color: #FFAF87 +} + +div.highlight .-Color[class*=-C217] { + color: #FFAFAF +} + +div.highlight .-Color[class*=-BGC217] { + background-color: #FFAFAF +} + +div.highlight .-Color[class*=-C218] { + color: #FFAFD7 +} + +div.highlight .-Color[class*=-BGC218] { + background-color: #FFAFD7 +} + +div.highlight .-Color[class*=-C219] { + color: #FFAFFF +} + +div.highlight .-Color[class*=-BGC219] { + background-color: #FFAFFF +} + +div.highlight .-Color[class*=-C220] { + color: #FFD700 +} + +div.highlight .-Color[class*=-BGC220] { + background-color: #FFD700 +} + +div.highlight .-Color[class*=-C221] { + color: #FFD75F +} + +div.highlight .-Color[class*=-BGC221] { + background-color: #FFD75F +} + +div.highlight .-Color[class*=-C222] { + color: #FFD787 +} + +div.highlight .-Color[class*=-BGC222] { + background-color: #FFD787 +} + +div.highlight .-Color[class*=-C223] { + color: #FFD7AF +} + +div.highlight .-Color[class*=-BGC223] { + background-color: #FFD7AF +} + +div.highlight .-Color[class*=-C224] { + color: #FFD7D7 +} + +div.highlight .-Color[class*=-BGC224] { + background-color: #FFD7D7 +} + +div.highlight .-Color[class*=-C225] { + color: #FFD7FF +} + +div.highlight .-Color[class*=-BGC225] { + background-color: #FFD7FF +} + +div.highlight .-Color[class*=-C226] { + color: #FFFF00 +} + +div.highlight .-Color[class*=-BGC226] { + background-color: #FFFF00 +} + +div.highlight .-Color[class*=-C227] { + color: #FFFF5F +} + +div.highlight .-Color[class*=-BGC227] { + background-color: #FFFF5F +} + +div.highlight .-Color[class*=-C228] { + color: #FFFF87 +} + +div.highlight .-Color[class*=-BGC228] { + background-color: #FFFF87 +} + +div.highlight .-Color[class*=-C229] { + color: #FFFFAF +} + +div.highlight .-Color[class*=-BGC229] { + background-color: #FFFFAF +} + +div.highlight .-Color[class*=-C230] { + color: #FFFFD7 +} + +div.highlight .-Color[class*=-BGC230] { + background-color: #FFFFD7 +} + +div.highlight .-Color[class*=-C231] { + color: #FFFFFF +} + +div.highlight .-Color[class*=-BGC231] { + background-color: #FFFFFF +} + +div.highlight .-Color[class*=-C232] { + color: #080808 +} + +div.highlight .-Color[class*=-BGC232] { + background-color: #080808 +} + +div.highlight .-Color[class*=-C233] { + color: #121212 +} + +div.highlight .-Color[class*=-BGC233] { + background-color: #121212 +} + +div.highlight .-Color[class*=-C234] { + color: #1C1C1C +} + +div.highlight .-Color[class*=-BGC234] { + background-color: #1C1C1C +} + +div.highlight .-Color[class*=-C235] { + color: #262626 +} + +div.highlight .-Color[class*=-BGC235] { + background-color: #262626 +} + +div.highlight .-Color[class*=-C236] { + color: #303030 +} + +div.highlight .-Color[class*=-BGC236] { + background-color: #303030 +} + +div.highlight .-Color[class*=-C237] { + color: #3A3A3A +} + +div.highlight .-Color[class*=-BGC237] { + background-color: #3A3A3A +} + +div.highlight .-Color[class*=-C238] { + color: #444444 +} + +div.highlight .-Color[class*=-BGC238] { + background-color: #444444 +} + +div.highlight .-Color[class*=-C239] { + color: #4E4E4E +} + +div.highlight .-Color[class*=-BGC239] { + background-color: #4E4E4E +} + +div.highlight .-Color[class*=-C240] { + color: #585858 +} + +div.highlight .-Color[class*=-BGC240] { + background-color: #585858 +} + +div.highlight .-Color[class*=-C241] { + color: #626262 +} + +div.highlight .-Color[class*=-BGC241] { + background-color: #626262 +} + +div.highlight .-Color[class*=-C242] { + color: #6C6C6C +} + +div.highlight .-Color[class*=-BGC242] { + background-color: #6C6C6C +} + +div.highlight .-Color[class*=-C243] { + color: #767676 +} + +div.highlight .-Color[class*=-BGC243] { + background-color: #767676 +} + +div.highlight .-Color[class*=-C244] { + color: #808080 +} + +div.highlight .-Color[class*=-BGC244] { + background-color: #808080 +} + +div.highlight .-Color[class*=-C245] { + color: #8A8A8A +} + +div.highlight .-Color[class*=-BGC245] { + background-color: #8A8A8A +} + +div.highlight .-Color[class*=-C246] { + color: #949494 +} + +div.highlight .-Color[class*=-BGC246] { + background-color: #949494 +} + +div.highlight .-Color[class*=-C247] { + color: #9E9E9E +} + +div.highlight .-Color[class*=-BGC247] { + background-color: #9E9E9E +} + +div.highlight .-Color[class*=-C248] { + color: #A8A8A8 +} + +div.highlight .-Color[class*=-BGC248] { + background-color: #A8A8A8 +} + +div.highlight .-Color[class*=-C249] { + color: #B2B2B2 +} + +div.highlight .-Color[class*=-BGC249] { + background-color: #B2B2B2 +} + +div.highlight .-Color[class*=-C250] { + color: #BCBCBC +} + +div.highlight .-Color[class*=-BGC250] { + background-color: #BCBCBC +} + +div.highlight .-Color[class*=-C251] { + color: #C6C6C6 +} + +div.highlight .-Color[class*=-BGC251] { + background-color: #C6C6C6 +} + +div.highlight .-Color[class*=-C252] { + color: #D0D0D0 +} + +div.highlight .-Color[class*=-BGC252] { + background-color: #D0D0D0 +} + +div.highlight .-Color[class*=-C253] { + color: #DADADA +} + +div.highlight .-Color[class*=-BGC253] { + background-color: #DADADA +} + +div.highlight .-Color[class*=-C254] { + color: #E4E4E4 +} + +div.highlight .-Color[class*=-BGC254] { + background-color: #E4E4E4 +} + +div.highlight .-Color[class*=-C255] { + color: #EEEEEE +} + +div.highlight .-Color[class*=-BGC255] { + background-color: #EEEEEE +} diff --git a/v/pdev/_static/play-solid.svg b/v/pdev/_static/play-solid.svg new file mode 100644 index 00000000..bcd81f7a --- /dev/null +++ b/v/pdev/_static/play-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v/pdev/_static/plus.png b/v/pdev/_static/plus.png new file mode 100644 index 00000000..7107cec9 Binary files /dev/null and b/v/pdev/_static/plus.png differ diff --git a/v/pdev/_static/pygments.css b/v/pdev/_static/pygments.css new file mode 100644 index 00000000..997797f2 --- /dev/null +++ b/v/pdev/_static/pygments.css @@ -0,0 +1,152 @@ +html[data-theme="light"] .highlight pre { line-height: 125%; } +html[data-theme="light"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight .hll { background-color: #7971292e } +html[data-theme="light"] .highlight { background: #fefefe; color: #545454 } +html[data-theme="light"] .highlight .c { color: #797129 } /* Comment */ +html[data-theme="light"] .highlight .err { color: #d91e18 } /* Error */ +html[data-theme="light"] .highlight .k { color: #7928a1 } /* Keyword */ +html[data-theme="light"] .highlight .l { color: #797129 } /* Literal */ +html[data-theme="light"] .highlight .n { color: #545454 } /* Name */ +html[data-theme="light"] .highlight .o { color: #008000 } /* Operator */ +html[data-theme="light"] .highlight .p { color: #545454 } /* Punctuation */ +html[data-theme="light"] .highlight .ch { color: #797129 } /* Comment.Hashbang */ +html[data-theme="light"] .highlight .cm { color: #797129 } /* Comment.Multiline */ +html[data-theme="light"] .highlight .cp { color: #797129 } /* Comment.Preproc */ +html[data-theme="light"] .highlight .cpf { color: #797129 } /* Comment.PreprocFile */ +html[data-theme="light"] .highlight .c1 { color: #797129 } /* Comment.Single */ +html[data-theme="light"] .highlight .cs { color: #797129 } /* Comment.Special */ +html[data-theme="light"] .highlight .gd { color: #007faa } /* Generic.Deleted */ +html[data-theme="light"] .highlight .ge { font-style: italic } /* Generic.Emph */ +html[data-theme="light"] .highlight .gh { color: #007faa } /* Generic.Heading */ +html[data-theme="light"] .highlight .gs { font-weight: bold } /* Generic.Strong */ +html[data-theme="light"] .highlight .gu { color: #007faa } /* Generic.Subheading */ +html[data-theme="light"] .highlight .kc { color: #7928a1 } /* Keyword.Constant */ +html[data-theme="light"] .highlight .kd { color: #7928a1 } /* Keyword.Declaration */ +html[data-theme="light"] .highlight .kn { color: #7928a1 } /* Keyword.Namespace */ +html[data-theme="light"] .highlight .kp { color: #7928a1 } /* Keyword.Pseudo */ +html[data-theme="light"] .highlight .kr { color: #7928a1 } /* Keyword.Reserved */ +html[data-theme="light"] .highlight .kt { color: #797129 } /* Keyword.Type */ +html[data-theme="light"] .highlight .ld { color: #797129 } /* Literal.Date */ +html[data-theme="light"] .highlight .m { color: #797129 } /* Literal.Number */ +html[data-theme="light"] .highlight .s { color: #008000 } /* Literal.String */ +html[data-theme="light"] .highlight .na { color: #797129 } /* Name.Attribute */ +html[data-theme="light"] .highlight .nb { color: #797129 } /* Name.Builtin */ +html[data-theme="light"] .highlight .nc { color: #007faa } /* Name.Class */ +html[data-theme="light"] .highlight .no { color: #007faa } /* Name.Constant */ +html[data-theme="light"] .highlight .nd { color: #797129 } /* Name.Decorator */ +html[data-theme="light"] .highlight .ni { color: #008000 } /* Name.Entity */ +html[data-theme="light"] .highlight .ne { color: #7928a1 } /* Name.Exception */ +html[data-theme="light"] .highlight .nf { color: #007faa } /* Name.Function */ +html[data-theme="light"] .highlight .nl { color: #797129 } /* Name.Label */ +html[data-theme="light"] .highlight .nn { color: #545454 } /* Name.Namespace */ +html[data-theme="light"] .highlight .nx { color: #545454 } /* Name.Other */ +html[data-theme="light"] .highlight .py { color: #007faa } /* Name.Property */ +html[data-theme="light"] .highlight .nt { color: #007faa } /* Name.Tag */ +html[data-theme="light"] .highlight .nv { color: #d91e18 } /* Name.Variable */ +html[data-theme="light"] .highlight .ow { color: #7928a1 } /* Operator.Word */ +html[data-theme="light"] .highlight .pm { color: #545454 } /* Punctuation.Marker */ +html[data-theme="light"] .highlight .w { color: #545454 } /* Text.Whitespace */ +html[data-theme="light"] .highlight .mb { color: #797129 } /* Literal.Number.Bin */ +html[data-theme="light"] .highlight .mf { color: #797129 } /* Literal.Number.Float */ +html[data-theme="light"] .highlight .mh { color: #797129 } /* Literal.Number.Hex */ +html[data-theme="light"] .highlight .mi { color: #797129 } /* Literal.Number.Integer */ +html[data-theme="light"] .highlight .mo { color: #797129 } /* Literal.Number.Oct */ +html[data-theme="light"] .highlight .sa { color: #008000 } /* Literal.String.Affix */ +html[data-theme="light"] .highlight .sb { color: #008000 } /* Literal.String.Backtick */ +html[data-theme="light"] .highlight .sc { color: #008000 } /* Literal.String.Char */ +html[data-theme="light"] .highlight .dl { color: #008000 } /* Literal.String.Delimiter */ +html[data-theme="light"] .highlight .sd { color: #008000 } /* Literal.String.Doc */ +html[data-theme="light"] .highlight .s2 { color: #008000 } /* Literal.String.Double */ +html[data-theme="light"] .highlight .se { color: #008000 } /* Literal.String.Escape */ +html[data-theme="light"] .highlight .sh { color: #008000 } /* Literal.String.Heredoc */ +html[data-theme="light"] .highlight .si { color: #008000 } /* Literal.String.Interpol */ +html[data-theme="light"] .highlight .sx { color: #008000 } /* Literal.String.Other */ +html[data-theme="light"] .highlight .sr { color: #d91e18 } /* Literal.String.Regex */ +html[data-theme="light"] .highlight .s1 { color: #008000 } /* Literal.String.Single */ +html[data-theme="light"] .highlight .ss { color: #007faa } /* Literal.String.Symbol */ +html[data-theme="light"] .highlight .bp { color: #797129 } /* Name.Builtin.Pseudo */ +html[data-theme="light"] .highlight .fm { color: #007faa } /* Name.Function.Magic */ +html[data-theme="light"] .highlight .vc { color: #d91e18 } /* Name.Variable.Class */ +html[data-theme="light"] .highlight .vg { color: #d91e18 } /* Name.Variable.Global */ +html[data-theme="light"] .highlight .vi { color: #d91e18 } /* Name.Variable.Instance */ +html[data-theme="light"] .highlight .vm { color: #797129 } /* Name.Variable.Magic */ +html[data-theme="light"] .highlight .il { color: #797129 } /* Literal.Number.Integer.Long */ +html[data-theme="dark"] .highlight pre { line-height: 125%; } +html[data-theme="dark"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight .hll { background-color: #ffd9002e } +html[data-theme="dark"] .highlight { background: #2b2b2b; color: #f8f8f2 } +html[data-theme="dark"] .highlight .c { color: #ffd900 } /* Comment */ +html[data-theme="dark"] .highlight .err { color: #ffa07a } /* Error */ +html[data-theme="dark"] .highlight .k { color: #dcc6e0 } /* Keyword */ +html[data-theme="dark"] .highlight .l { color: #ffd900 } /* Literal */ +html[data-theme="dark"] .highlight .n { color: #f8f8f2 } /* Name */ +html[data-theme="dark"] .highlight .o { color: #abe338 } /* Operator */ +html[data-theme="dark"] .highlight .p { color: #f8f8f2 } /* Punctuation */ +html[data-theme="dark"] .highlight .ch { color: #ffd900 } /* Comment.Hashbang */ +html[data-theme="dark"] .highlight .cm { color: #ffd900 } /* Comment.Multiline */ +html[data-theme="dark"] .highlight .cp { color: #ffd900 } /* Comment.Preproc */ +html[data-theme="dark"] .highlight .cpf { color: #ffd900 } /* Comment.PreprocFile */ +html[data-theme="dark"] .highlight .c1 { color: #ffd900 } /* Comment.Single */ +html[data-theme="dark"] .highlight .cs { color: #ffd900 } /* Comment.Special */ +html[data-theme="dark"] .highlight .gd { color: #00e0e0 } /* Generic.Deleted */ +html[data-theme="dark"] .highlight .ge { font-style: italic } /* Generic.Emph */ +html[data-theme="dark"] .highlight .gh { color: #00e0e0 } /* Generic.Heading */ +html[data-theme="dark"] .highlight .gs { font-weight: bold } /* Generic.Strong */ +html[data-theme="dark"] .highlight .gu { color: #00e0e0 } /* Generic.Subheading */ +html[data-theme="dark"] .highlight .kc { color: #dcc6e0 } /* Keyword.Constant */ +html[data-theme="dark"] .highlight .kd { color: #dcc6e0 } /* Keyword.Declaration */ +html[data-theme="dark"] .highlight .kn { color: #dcc6e0 } /* Keyword.Namespace */ +html[data-theme="dark"] .highlight .kp { color: #dcc6e0 } /* Keyword.Pseudo */ +html[data-theme="dark"] .highlight .kr { color: #dcc6e0 } /* Keyword.Reserved */ +html[data-theme="dark"] .highlight .kt { color: #ffd900 } /* Keyword.Type */ +html[data-theme="dark"] .highlight .ld { color: #ffd900 } /* Literal.Date */ +html[data-theme="dark"] .highlight .m { color: #ffd900 } /* Literal.Number */ +html[data-theme="dark"] .highlight .s { color: #abe338 } /* Literal.String */ +html[data-theme="dark"] .highlight .na { color: #ffd900 } /* Name.Attribute */ +html[data-theme="dark"] .highlight .nb { color: #ffd900 } /* Name.Builtin */ +html[data-theme="dark"] .highlight .nc { color: #00e0e0 } /* Name.Class */ +html[data-theme="dark"] .highlight .no { color: #00e0e0 } /* Name.Constant */ +html[data-theme="dark"] .highlight .nd { color: #ffd900 } /* Name.Decorator */ +html[data-theme="dark"] .highlight .ni { color: #abe338 } /* Name.Entity */ +html[data-theme="dark"] .highlight .ne { color: #dcc6e0 } /* Name.Exception */ +html[data-theme="dark"] .highlight .nf { color: #00e0e0 } /* Name.Function */ +html[data-theme="dark"] .highlight .nl { color: #ffd900 } /* Name.Label */ +html[data-theme="dark"] .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ +html[data-theme="dark"] .highlight .nx { color: #f8f8f2 } /* Name.Other */ +html[data-theme="dark"] .highlight .py { color: #00e0e0 } /* Name.Property */ +html[data-theme="dark"] .highlight .nt { color: #00e0e0 } /* Name.Tag */ +html[data-theme="dark"] .highlight .nv { color: #ffa07a } /* Name.Variable */ +html[data-theme="dark"] .highlight .ow { color: #dcc6e0 } /* Operator.Word */ +html[data-theme="dark"] .highlight .pm { color: #f8f8f2 } /* Punctuation.Marker */ +html[data-theme="dark"] .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ +html[data-theme="dark"] .highlight .mb { color: #ffd900 } /* Literal.Number.Bin */ +html[data-theme="dark"] .highlight .mf { color: #ffd900 } /* Literal.Number.Float */ +html[data-theme="dark"] .highlight .mh { color: #ffd900 } /* Literal.Number.Hex */ +html[data-theme="dark"] .highlight .mi { color: #ffd900 } /* Literal.Number.Integer */ +html[data-theme="dark"] .highlight .mo { color: #ffd900 } /* Literal.Number.Oct */ +html[data-theme="dark"] .highlight .sa { color: #abe338 } /* Literal.String.Affix */ +html[data-theme="dark"] .highlight .sb { color: #abe338 } /* Literal.String.Backtick */ +html[data-theme="dark"] .highlight .sc { color: #abe338 } /* Literal.String.Char */ +html[data-theme="dark"] .highlight .dl { color: #abe338 } /* Literal.String.Delimiter */ +html[data-theme="dark"] .highlight .sd { color: #abe338 } /* Literal.String.Doc */ +html[data-theme="dark"] .highlight .s2 { color: #abe338 } /* Literal.String.Double */ +html[data-theme="dark"] .highlight .se { color: #abe338 } /* Literal.String.Escape */ +html[data-theme="dark"] .highlight .sh { color: #abe338 } /* Literal.String.Heredoc */ +html[data-theme="dark"] .highlight .si { color: #abe338 } /* Literal.String.Interpol */ +html[data-theme="dark"] .highlight .sx { color: #abe338 } /* Literal.String.Other */ +html[data-theme="dark"] .highlight .sr { color: #ffa07a } /* Literal.String.Regex */ +html[data-theme="dark"] .highlight .s1 { color: #abe338 } /* Literal.String.Single */ +html[data-theme="dark"] .highlight .ss { color: #00e0e0 } /* Literal.String.Symbol */ +html[data-theme="dark"] .highlight .bp { color: #ffd900 } /* Name.Builtin.Pseudo */ +html[data-theme="dark"] .highlight .fm { color: #00e0e0 } /* Name.Function.Magic */ +html[data-theme="dark"] .highlight .vc { color: #ffa07a } /* Name.Variable.Class */ +html[data-theme="dark"] .highlight .vg { color: #ffa07a } /* Name.Variable.Global */ +html[data-theme="dark"] .highlight .vi { color: #ffa07a } /* Name.Variable.Instance */ +html[data-theme="dark"] .highlight .vm { color: #ffd900 } /* Name.Variable.Magic */ +html[data-theme="dark"] .highlight .il { color: #ffd900 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/v/pdev/_static/sbt-webpack-macros.html b/v/pdev/_static/sbt-webpack-macros.html new file mode 100644 index 00000000..6cbf559f --- /dev/null +++ b/v/pdev/_static/sbt-webpack-macros.html @@ -0,0 +1,11 @@ + +{% macro head_pre_bootstrap() %} + +{% endmacro %} + +{% macro body_post() %} + +{% endmacro %} diff --git a/v/pdev/_static/scripts/bootstrap.js b/v/pdev/_static/scripts/bootstrap.js new file mode 100644 index 00000000..4e209b0e --- /dev/null +++ b/v/pdev/_static/scripts/bootstrap.js @@ -0,0 +1,3 @@ +/*! For license information please see bootstrap.js.LICENSE.txt */ +(()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{afterMain:()=>E,afterRead:()=>v,afterWrite:()=>C,applyStyles:()=>$,arrow:()=>J,auto:()=>a,basePlacements:()=>l,beforeMain:()=>y,beforeRead:()=>_,beforeWrite:()=>A,bottom:()=>s,clippingParents:()=>d,computeStyles:()=>it,createPopper:()=>Dt,createPopperBase:()=>St,createPopperLite:()=>$t,detectOverflow:()=>_t,end:()=>h,eventListeners:()=>st,flip:()=>bt,hide:()=>wt,left:()=>r,main:()=>w,modifierPhases:()=>O,offset:()=>Et,placements:()=>g,popper:()=>f,popperGenerator:()=>Lt,popperOffsets:()=>At,preventOverflow:()=>Tt,read:()=>b,reference:()=>p,right:()=>o,start:()=>c,top:()=>n,variationPlacements:()=>m,viewport:()=>u,write:()=>T});var i={};t.r(i),t.d(i,{Alert:()=>Oe,Button:()=>ke,Carousel:()=>ri,Collapse:()=>yi,Dropdown:()=>Vi,Modal:()=>xn,Offcanvas:()=>Vn,Popover:()=>fs,ScrollSpy:()=>Ts,Tab:()=>Ks,Toast:()=>lo,Tooltip:()=>hs});var n="top",s="bottom",o="right",r="left",a="auto",l=[n,s,o,r],c="start",h="end",d="clippingParents",u="viewport",f="popper",p="reference",m=l.reduce((function(t,e){return t.concat([e+"-"+c,e+"-"+h])}),[]),g=[].concat(l,[a]).reduce((function(t,e){return t.concat([e,e+"-"+c,e+"-"+h])}),[]),_="beforeRead",b="read",v="afterRead",y="beforeMain",w="main",E="afterMain",A="beforeWrite",T="write",C="afterWrite",O=[_,b,v,y,w,E,A,T,C];function x(t){return t?(t.nodeName||"").toLowerCase():null}function k(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function L(t){return t instanceof k(t).Element||t instanceof Element}function S(t){return t instanceof k(t).HTMLElement||t instanceof HTMLElement}function D(t){return"undefined"!=typeof ShadowRoot&&(t instanceof k(t).ShadowRoot||t instanceof ShadowRoot)}const $={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];S(s)&&x(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});S(n)&&x(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function I(t){return t.split("-")[0]}var N=Math.max,P=Math.min,M=Math.round;function j(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function F(){return!/^((?!chrome|android).)*safari/i.test(j())}function H(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&S(t)&&(s=t.offsetWidth>0&&M(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&M(n.height)/t.offsetHeight||1);var r=(L(t)?k(t):window).visualViewport,a=!F()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function B(t){var e=H(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function W(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&D(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function z(t){return k(t).getComputedStyle(t)}function R(t){return["table","td","th"].indexOf(x(t))>=0}function q(t){return((L(t)?t.ownerDocument:t.document)||window.document).documentElement}function V(t){return"html"===x(t)?t:t.assignedSlot||t.parentNode||(D(t)?t.host:null)||q(t)}function Y(t){return S(t)&&"fixed"!==z(t).position?t.offsetParent:null}function K(t){for(var e=k(t),i=Y(t);i&&R(i)&&"static"===z(i).position;)i=Y(i);return i&&("html"===x(i)||"body"===x(i)&&"static"===z(i).position)?e:i||function(t){var e=/firefox/i.test(j());if(/Trident/i.test(j())&&S(t)&&"fixed"===z(t).position)return null;var i=V(t);for(D(i)&&(i=i.host);S(i)&&["html","body"].indexOf(x(i))<0;){var n=z(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Q(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function X(t,e,i){return N(t,P(e,i))}function U(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function G(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const J={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,a=t.name,c=t.options,h=i.elements.arrow,d=i.modifiersData.popperOffsets,u=I(i.placement),f=Q(u),p=[r,o].indexOf(u)>=0?"height":"width";if(h&&d){var m=function(t,e){return U("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:G(t,l))}(c.padding,i),g=B(h),_="y"===f?n:r,b="y"===f?s:o,v=i.rects.reference[p]+i.rects.reference[f]-d[f]-i.rects.popper[p],y=d[f]-i.rects.reference[f],w=K(h),E=w?"y"===f?w.clientHeight||0:w.clientWidth||0:0,A=v/2-y/2,T=m[_],C=E-g[p]-m[b],O=E/2-g[p]/2+A,x=X(T,O,C),k=f;i.modifiersData[a]=((e={})[k]=x,e.centerOffset=x-O,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&W(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Z(t){return t.split("-")[1]}var tt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function et(t){var e,i=t.popper,a=t.popperRect,l=t.placement,c=t.variation,d=t.offsets,u=t.position,f=t.gpuAcceleration,p=t.adaptive,m=t.roundOffsets,g=t.isFixed,_=d.x,b=void 0===_?0:_,v=d.y,y=void 0===v?0:v,w="function"==typeof m?m({x:b,y}):{x:b,y};b=w.x,y=w.y;var E=d.hasOwnProperty("x"),A=d.hasOwnProperty("y"),T=r,C=n,O=window;if(p){var x=K(i),L="clientHeight",S="clientWidth";x===k(i)&&"static"!==z(x=q(i)).position&&"absolute"===u&&(L="scrollHeight",S="scrollWidth"),(l===n||(l===r||l===o)&&c===h)&&(C=s,y-=(g&&x===O&&O.visualViewport?O.visualViewport.height:x[L])-a.height,y*=f?1:-1),l!==r&&(l!==n&&l!==s||c!==h)||(T=o,b-=(g&&x===O&&O.visualViewport?O.visualViewport.width:x[S])-a.width,b*=f?1:-1)}var D,$=Object.assign({position:u},p&&tt),I=!0===m?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:M(i*s)/s||0,y:M(n*s)/s||0}}({x:b,y},k(i)):{x:b,y};return b=I.x,y=I.y,f?Object.assign({},$,((D={})[C]=A?"0":"",D[T]=E?"0":"",D.transform=(O.devicePixelRatio||1)<=1?"translate("+b+"px, "+y+"px)":"translate3d("+b+"px, "+y+"px, 0)",D)):Object.assign({},$,((e={})[C]=A?y+"px":"",e[T]=E?b+"px":"",e.transform="",e))}const it={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:I(e.placement),variation:Z(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,et(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,et(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var nt={passive:!0};const st={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=k(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,nt)})),a&&l.addEventListener("resize",i.update,nt),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,nt)})),a&&l.removeEventListener("resize",i.update,nt)}},data:{}};var ot={left:"right",right:"left",bottom:"top",top:"bottom"};function rt(t){return t.replace(/left|right|bottom|top/g,(function(t){return ot[t]}))}var at={start:"end",end:"start"};function lt(t){return t.replace(/start|end/g,(function(t){return at[t]}))}function ct(t){var e=k(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ht(t){return H(q(t)).left+ct(t).scrollLeft}function dt(t){var e=z(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function ut(t){return["html","body","#document"].indexOf(x(t))>=0?t.ownerDocument.body:S(t)&&dt(t)?t:ut(V(t))}function ft(t,e){var i;void 0===e&&(e=[]);var n=ut(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=k(n),r=s?[o].concat(o.visualViewport||[],dt(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(ft(V(r)))}function pt(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function mt(t,e,i){return e===u?pt(function(t,e){var i=k(t),n=q(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=F();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+ht(t),y:l}}(t,i)):L(e)?function(t,e){var i=H(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):pt(function(t){var e,i=q(t),n=ct(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=N(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=N(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ht(t),l=-n.scrollTop;return"rtl"===z(s||i).direction&&(a+=N(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(q(t)))}function gt(t){var e,i=t.reference,a=t.element,l=t.placement,d=l?I(l):null,u=l?Z(l):null,f=i.x+i.width/2-a.width/2,p=i.y+i.height/2-a.height/2;switch(d){case n:e={x:f,y:i.y-a.height};break;case s:e={x:f,y:i.y+i.height};break;case o:e={x:i.x+i.width,y:p};break;case r:e={x:i.x-a.width,y:p};break;default:e={x:i.x,y:i.y}}var m=d?Q(d):null;if(null!=m){var g="y"===m?"height":"width";switch(u){case c:e[m]=e[m]-(i[g]/2-a[g]/2);break;case h:e[m]=e[m]+(i[g]/2-a[g]/2)}}return e}function _t(t,e){void 0===e&&(e={});var i=e,r=i.placement,a=void 0===r?t.placement:r,c=i.strategy,h=void 0===c?t.strategy:c,m=i.boundary,g=void 0===m?d:m,_=i.rootBoundary,b=void 0===_?u:_,v=i.elementContext,y=void 0===v?f:v,w=i.altBoundary,E=void 0!==w&&w,A=i.padding,T=void 0===A?0:A,C=U("number"!=typeof T?T:G(T,l)),O=y===f?p:f,k=t.rects.popper,D=t.elements[E?O:y],$=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=ft(V(t)),i=["absolute","fixed"].indexOf(z(t).position)>=0&&S(t)?K(t):t;return L(i)?e.filter((function(t){return L(t)&&W(t,i)&&"body"!==x(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=mt(t,i,n);return e.top=N(s.top,e.top),e.right=P(s.right,e.right),e.bottom=P(s.bottom,e.bottom),e.left=N(s.left,e.left),e}),mt(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(L(D)?D:D.contextElement||q(t.elements.popper),g,b,h),I=H(t.elements.reference),M=gt({reference:I,element:k,strategy:"absolute",placement:a}),j=pt(Object.assign({},k,M)),F=y===f?j:I,B={top:$.top-F.top+C.top,bottom:F.bottom-$.bottom+C.bottom,left:$.left-F.left+C.left,right:F.right-$.right+C.right},R=t.modifiersData.offset;if(y===f&&R){var Y=R[a];Object.keys(B).forEach((function(t){var e=[o,s].indexOf(t)>=0?1:-1,i=[n,s].indexOf(t)>=0?"y":"x";B[t]+=Y[i]*e}))}return B}const bt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,h=t.name;if(!e.modifiersData[h]._skip){for(var d=i.mainAxis,u=void 0===d||d,f=i.altAxis,p=void 0===f||f,_=i.fallbackPlacements,b=i.padding,v=i.boundary,y=i.rootBoundary,w=i.altBoundary,E=i.flipVariations,A=void 0===E||E,T=i.allowedAutoPlacements,C=e.options.placement,O=I(C),x=_||(O!==C&&A?function(t){if(I(t)===a)return[];var e=rt(t);return[lt(t),e,lt(e)]}(C):[rt(C)]),k=[C].concat(x).reduce((function(t,i){return t.concat(I(i)===a?function(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,c=i.allowedAutoPlacements,h=void 0===c?g:c,d=Z(n),u=d?a?m:m.filter((function(t){return Z(t)===d})):l,f=u.filter((function(t){return h.indexOf(t)>=0}));0===f.length&&(f=u);var p=f.reduce((function(e,i){return e[i]=_t(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[I(i)],e}),{});return Object.keys(p).sort((function(t,e){return p[t]-p[e]}))}(e,{placement:i,boundary:v,rootBoundary:y,padding:b,flipVariations:A,allowedAutoPlacements:T}):i)}),[]),L=e.rects.reference,S=e.rects.popper,D=new Map,$=!0,N=k[0],P=0;P