FITS image operations using PyNOT : operate


PyNOT can perform image operations directly on multi-extension images from the command line using the pynot operate command. The operations include:


General Syntax

The basic principle of pynot operate is a sequence of the operations outlined above. Each FITS image is loaded into a variable name which is used in the sequence. For example, to subtract a constant value from an image with filename image.fits do the following:
pynot operate 'x - 10' x=image.fits
This will subtract the value 10 from each pixel in the image and save the new image to the file output.fits by default. If you want to change the output filename, you can either use the --output (or -o) option before the sequence, or use output= to set the filename.
pynot operate 'x - 10' x=image.fits output=image_sub10.fits
will create the FITS file image_sub10.fits as the result of the operation.

A few rules to follow:

  • The sequence of operations must be enclosed in quotes: pynot operate 'a + b' and not pynot operate a+b!
  • All variables in the sequence must be declared using the = without spaces around it!
  • Blank spaces in the sequence are ignored. The syntax follows normal Python syntax for operations
  • Images used in operations must have the same size!

If you are using the zsh terminal, you can add support for tab-completion of filenames after the = in the expressions by including the line: setopt magic_equal_subst in your .zshrc file.

Arithmetic operations

Addition and Subtraction

You can add or subtract numbers from an image using regular arithmetic operations + and -. Two or more images can also be added or subtracted and their error extensions (if available) will be propagated accordingly.

Example 1:

pynot operate -o diff.fits 'a - b' a=image1.fits b=image2.fits
This will subtract image2.fits from image1.fits. The resulting image diff.fits will have an error extension resulting from the square root of the individual images added in quadrature.

Example 2:
pynot operate -o mean.fits '(a + b + c)/3' a=image1.fits b=image2.fits c=image3.fits
This will calculate the mean of the three images and propagate the error extensions appropriately.

If the images have a boolean MASK extension as well, the resulting mask extension will be the union of all flagged pixels in any of the images.

Multiplication and Division

As hinted already above, you can also multiply or divide and image by numbers or other images using the arithmetic operations * and /. Their error extensions (if available) will be propagated accordingly by adding the relative errors (err/data) in quadrature.

Example 1:

pynot operate -o flux.fits 'cts / exptime' cts=image_counts.fits exptime=900
This will divide the image in units of counts by the exposure time to yield an image in units of counts per second. Note that units are not propagated yet, so this must be updated manually in the header for now.

Example 2:
pynot operate 'x1/x2' x1=image1.fits x2=image2.fits output=ratio.fits
This will divide the image1.fits image by the image2.fits image to provide an image where every pixel is the ratio of the two images.

Exponentiation

Last of the artihmetic operations is the exponentiation using the ** operator. This can take any integer or real number as exponent, but raising one image to the power of another will raise an error. Example 1:

pynot operate -o sqrt.fits '(x**2 + y**2)**0.5' x=image1.fits y=image2.fits
This operation will calculate the square root of the two images added in quadrature. The error extensions will be propagated accordingly using the approximation for 'small' errors.

Example 2:
pynot operate 'x1**x2' x1=image1.fits x2=image2.fits
This operation will result in an error: TypeError: Invalid operation with type: <class 'pynot.images.FitsImage'>

Image functions

Apply mathematical functions to an image or a set of images. The error extensions will be propagated accordingly and the mask extension will include the union of all masked pixels in all images. Currently, only the following set of functions are implemented:

mean():
Take the average of a series of images: pynot operate 'mean(x, y, z)' x=img1.fits y=img2.fits z=img3.fits
median():
Construct the median of a series of images: pynot operate 'median(x, y, z)' x=img1.fits y=img2.fits z=img3.fits
log():
Take the 10-base logarithm of an image: pynot operate 'log(img)' img=img1.fits. Note that this may raise a warning if there are zero-valued entries in the image.

Image slicing

If you want to cut only a part of the image, you can use image slicing following that for numpy arrays in Python, i.e., the first index slices along the y-axis and the second index slices along the x-axis. For example: img[ystart:yend, xstart:xend]
will return the image part from the ystartth pixel until the yendth pixel in the y-axis and similarly for the x-axis. You do not have to provide all the numbers in order to slice an image though. So to trim off the first 50 rows of the image in the y-axis you can provide only the ystart index: img[50:], here yend is assumed to be the end of the array axis. Similarly, if you want all the first 1000 rows along the y-axis, you do: img[:1000], where ystart is assumed to be 0, when not given.
If you only want to slice along the x-axis, you still have to give an empty slice along the y-axis (by giving a :). So if you want to trim off the first and last 50 columns in the x-direction, you would do: img[:, 50:-50]. Note here that a negative index counts backwards from the end of the array.

Technically, you can also provide a third number which is the step size for your slices. This is by default 1 when not given, i.e., all pixels from xstart to xend are kept. If for some reason you would only want every 2nd pixel you could do: img[50:-50:2, 50:-50:2] to trim off the first and last 50 pixels in both x- and y-direction and take only every second pixel. Note however, that skipping pixels like this does not conserve the flux! A proper resampling should be done instead using the resample operation.

Note: the slicing is applied to all extensions of the given image file.

Example: We try to subtract two images which do not have exactly the same image dimensions:

pynot operate 'a - b' a=image1.fits b=image2.fits
This results in the following error: ValueError: Cannot operate on images of different sizes!. PyNOT tells us the image dimensions of each image that is loaded to a variable, so the command above will load the two images and provide the following log information in the terminal:

+ Assigned variable: a = <FitsImage: (200, 6144)>
                                + Assigned variable: b = <FitsImage: (199, 6144)>
                                

From this, we can see that image2.fits, loaded to the variable b has one row of pixels less than image1.fits. If we are sure that the images are otherwise aligned, we can perform the image subtraction simply by removing the last row of image1.fits:

pynot operate 'a[:-1] - b' a=image1.fits b=image2.fits
The result will be an image of dimensions (199, 6144).

Image shifting

FITS images can also be shifted along either x- or y-axes by a given number of pixels. If the shifts are whole numbers of pixels the image will simply be shifted. The image size is maintained, and regions where the image has been shifted out of its former range are filled by NaN values. If the shifts are fractional pixel values, the image will be interpolated in order to apply the sub-pixel shifts. The shift is applied to all extensions of the FITS image file.
The syntax for the shift operation is:

pynot operate 'shift(img, dx, dy)' img=image.fits
where dx and dy are the shifts in units of pixels. These values can either be defined as numbers in the expression or as variables after the expression:
pynot operate 'shift(img, 2, 2)' img=image.fits
or
pynot operate 'shift(img, dx, dy)' img=image.fits dx=2 dy=2
both shift the image by 2 pixels to the right along the x-axis and 2 pixels up along the y-axis.

Negative shifts will move the image towards the left along the x-axis and down along the y-axis.

Example 1:
To shift an image by 10 rows along the y-axis, do the following:

pynot operate 'shift(img, dy=10)' img=image.fits
Example 2:
To shift an image by 10.5 rows back along the x-axis, do the following:
pynot operate 'shift(img, dx=10.5)' img=image.fits

You do not have to specify both shifts if you use the dx= or dy= syntax, however, if you just provide the numerical values you must give both, otherwise a single value is assumed to be a shift along the x-axis. That is, to shift an image by 100 pixels along the x-axis, you can do:

pynot operate 'shift(img, 100)' img=image.fits
but to shift the image by 100 pixels along the y-axis you must do:
pynot operate 'shift(img, 0, 100)' img=image.fits

Image resampling

You can resample a FITS image into a new image size by specifying the new number of pixels along the x-axis and y-axis. The new dimensions must be larger than at least 2 pixels! The syntax is straight-forward:

pynot operate 'resample(x, 100, 100)' x=image.fits output=image_small.fits
This operation will take the original image image.fits and resample it onto a new grid of 100 by 100 pixels using linear interpolation on a regularly spaced grid. The number of new pixels along the axes must be given as whole integer numbers. If not, the values will be truncated to the nearest whole integer value, e.g., 100.9 will be truncated to 100 pixels.
All extensions of the FITS image file will be resampled onto the new image dimensions.