chandra_aca.aca_image

The aca_image module contains classes and utilities related to ACA readout images. This includes the ACAImage class for manipulating images in ACA coordinates, a first moment centroiding routine, and a library for generating synthetic ACA images that have a high-fidelity point spread function (PSF).

class chandra_aca.aca_image.ACAImage(*args, **kwargs)[source]

ACAImage is an ndarray subclass that supports functionality for the Chandra ACA. Most importantly it allows image indexing and slicing in absolute “aca” coordinates, where the image lower left coordinate is specified by object row0 and col0 attributes.

It also provides a meta dict that can be used to store additional useful information. Any keys which are all upper-case will be exposed as object attributes, e.g. img.BGDAVG <=> img.meta['BGDAVG']. The row0 attribute is a proxy for img.meta['IMGROW0'], and likewise for col0.

When initializing an ACAImage, additional *args and **kwargs are used to try initializing via np.array(*args, **kwargs). If this fails then np.zeros(*args, **kwargs) is tried. In this way one can either initialize from array data or create a new array of zeros.

Examples:

>>> import numpy as np
>>> from chandra_aca.aca_image import ACAImage
>>> dat = np.random.uniform(size=(1024, 1024))
>>> a = ACAImage(dat, row0=-512, col0=-512)
>>> a = ACAImage([[1,2], [3,4]], meta={'BGDAVG': 5.2})
>>> a = ACAImage(shape=(1024, 1024), row0=-512, col0=-512)
Parameters:
  • row0 – row coordinate of lower left image pixel (int, default=0)

  • col0 – col coordinate of lower left image pixel (int, default=0)

  • meta – dict of object attributes

  • *args – additional args passed to np.array() or np.zeros()

  • **kwargs – additional kwargs passed to np.array() or np.zeros()

property aca

Return a light copy (same data) of self but with the _aca_coords attribute switched on so that indexing is absolute.

centroid_fm(bgd=None, pix_zero_loc='center', norm_clip=None)[source]

First moment centroid of self using 6x6 mousebitten image for input 6x6 or 8x8 images.

Note that the returned norm is the sum of the background-subtracted 6x6 mousebitten image, not the entire image.

Parameters:
bgd

background to subtract, scalar or NxN ndarray (float)

pix_zero_loc

row/col coords are integral at ‘edge’ or ‘center’

norm_clipclip image norm at this min value (default is None and

implies Exception for non-positive norm)

Returns:
row, col, norm float
flicker_init(flicker_mean_time=10000, flicker_scale=1.0, seed=None)[source]

Initialize instance variables to allow for flickering pixel updates.

The flicker_scale can be interpreted as follows: if the pixel was going to flicker by a multiplicative factor of (1 + x), now make it flicker by (1 + x * flicker_scale). This applies for flickers that increase the amplitude. For flickers that make the value smaller, then it would be 1 / (1 + x) => 1 / (1 + x * flicker_scale).

The flicker_cdf file here was created using: /proj/sot/ska/www/ASPECT/ipynb/chandra_aca/flickering-pixel-model.ipynb

Examples and performance details at: /proj/sot/ska/www/ASPECT/ipynb/chandra_aca/flickering-implementation.ipynb

The model was reviewed and approved at SS&AWG on 2019-05-22.

Parameters:
flicker_mean_time

mean flickering time (sec, default=10000)

flicker_scalemultiplicative factor beyond model default for

flickering amplitude (default=1.0)

seed

random seed for reproducibility (default=None => no seed)

flicker_update(dt, use_numba=True)[source]

Propagate the image forward by dt seconds and update any pixels that have flickered during that interval.

This has the option to use one of two implementations. The default is to use the numba-based version which is about 6 times faster. The vectorized version is left in for reference.

Parameters:
dt

time (secs) to propagate image

use_numba

use the numba version of updating (default=True)

class chandra_aca.aca_image.AcaPsfLibrary(filename=None)[source]

Access the ACA PSF library, whch is a library of 8x8 images providing the integrated (pixelated) ACA PSF over a grid of subpixel locations.

Example:

>>> from chandra_aca.aca_image import AcaPsfLibrary
>>> apl = AcaPsfLibrary()  # Reads in PSF library data file
>>> img = apl.get_psf_image(row=-10.456, col=250.123, norm=100000)
>>> img
<ACAImage row0=-14 col0=247
array([[   39,    54,    56,    52,    37,    33,    30,    21],
       [   79,   144,   260,   252,   156,    86,    67,    36],
       [  162,   544,  2474,  5269,  2012,   443,   163,    57],
       [  255,  1420, 10083, 12688, 11273,  1627,   302,    78],
       [  186,  1423,  8926,  8480, 12292,  2142,   231,    64],
       [   80,   344,  1384,  6509,  4187,   665,   111,    43],
       [   40,    78,   241,   828,   616,   188,    65,    29],
       [   24,    39,    86,   157,   139,    69,    48,    32]])>
Parameters:

filename – file name of ACA PSF library (default=built-in file)

Returns:

AcaPsfLibrary object

get_psf_image(row, col, norm=1.0, pix_zero_loc='center', interpolation='bilinear', aca_image=True)[source]

Get interpolated ACA PSF image that corresponds to pixel location row, col.

Parameters:
row

(float) row value of PSF centroid

col

(float) col value of PSF centroid

norm

(float) summed intensity of PSF image

pix_zero_loc

row/col coords are integral at ‘edge’ or ‘center’

interpolation

‘nearest’ | ‘bilinear’ (default)

aca_image

return ACAImage if True, else return ndarray

Returns:
ACAImage if (aca_image is True) else (ndarray image, row0, col0)
chandra_aca.aca_image.EIGHT_LABELS = array([['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1'],        ['I1', 'J1', 'K1', 'L1', 'M1', 'N1', 'O1', 'P1'],        ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2'],        ['I2', 'J2', 'K2', 'L2', 'M2', 'N2', 'O2', 'P2'],        ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3'],        ['I3', 'J3', 'K3', 'L3', 'M3', 'N3', 'O3', 'P3'],        ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4'],        ['I4', 'J4', 'K4', 'L4', 'M4', 'N4', 'O4', 'P4']], dtype='<U2')

Constant for labeling ACA image pixels using the EQ-278 spec format. Pixel A1 has the lowest values of row and column; pixel H1 has the lowest row and highest col; pixel I4 has the highest row and lowest column.

chandra_aca.aca_image.centroid_fm(img, bgd=None, pix_zero_loc='center', norm_clip=None)[source]

First moment centroid of img.

Return FM centroid in coords where lower left pixel of image has value (0.0, 0.0) at the center (for pix_zero_loc=’center’) or the lower-left edge (for pix_zero_loc=’edge’).

Parameters:
img

NxN ndarray

bgd

background to subtract, float of NXN ndarray

pix_zero_loc

row/col coords are integral at ‘edge’ or ‘center’

norm_clipclip image norm at this min value (default is None and

implies Exception for non-positive norm)

Returns:
row, col, norm float

ACAImage class

ACAImage is an ndarray subclass that supports functionality for the Chandra ACA. Most importantly it allows image indexing and slicing in absolute “aca” coordinates, where the image lower left coordinate is specified by object row0 and col0 attributes.

It also provides a meta dict that can be used to store additional useful information. Any keys which are all upper-case will be exposed as object attributes, e.g. img.BGDAVG <=> img.meta['BGDAVG']. The row0 attribute is a proxy for img.meta['IMGROW0'], and likewise for col0.

Creation

When initializing an ACAImage, additional *args and **kwargs are used to try initializing via np.array(*args, **kwargs). If this fails then np.zeros(*args, **kwargs) is tried. In this way one can either initialize from array data or create a new array of zeros.

One can easily create a new ACAImage as shown below. Note that it must always be 2-dimensional. The initial row0 and col0 values default to zero.

>>> from chandra_aca.aca_image import ACAImage
>>> im4 = np.arange(16).reshape(4, 4)
>>> a = ACAImage(im4, row0=10, col0=20)
>>> a
<ACAImage row0=10 col0=20
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])>

One could also initialize by providing a meta dict:

>>> a = ACAImage(im4, meta={'IMGROW0': 10, 'IMGCOL0': 20, 'BGDAVG': 5.2})
>>> a
<ACAImage row0=10 col0=20
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])>

Note

The printed representation of an ACAImage is always shown as the rounded integer version of the values, but the full float value is stored internally. If you print() the image then the floating point values are shown.

Image access and setting

You can access array elements as usual, and in fact do any normal numpy array operations:

>>> a[3, 3]
15

The special nature of ACAImage comes by doing array access via the aca attribute. In this case all index values are in absolute coordinates, which might be negative. In this case we can access the pixel at ACA coordinates row=13, col=23, which is equal to a[3, 3] for the given row0 and col0 offset:

>>> a.aca[13, 23]
15

Creating a new array by slicing adjusts the row0 and col0 values like you would expect:

>>> a2 = a.aca[12:, 22:]
>>> a2
<ACAImage row0=12 col0=22
array([[500,  11],
       [ 14,  15]])>

You can set values in absolute coordinates:

>>> a.aca[11:13, 21:23] = 500
>>> a
<ACAImage row0=10 col0=20
array([[  0,   1,   2,   3],
       [  4, 500, 500,   7],
       [  8, 500, 500,  11],
       [ 12,  13,  14,  15]])>

Now let’s make an image that represents the full ACA CCD and set a sub-image from our 4x4 image a. This uses the absolute location of a to define a slice into b:

>>> b = ACAImage(shape=(1024,1024), row0=-512, col0=-512)
>>> b[a] = a
>>> b.aca[8:16, 18:26]
<ACAImage row0=8 col0=18
array([[  0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   1,   2,   3,   0,   0],
       [  0,   0,   4, 500, 500,   7,   0,   0],
       [  0,   0,   8, 500, 500,  11,   0,   0],
       [  0,   0,  12,  13,  14,  15,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0]])>

You can also do things like adding 100 to every pixel in b within the area of a:

>>> b[a] += 100
>>> b.aca[8:16, 18:26]
<ACAImage row0=8 col0=18
array([[  0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0, 100, 101, 102, 103,   0,   0],
       [  0,   0, 104, 600, 600, 107,   0,   0],
       [  0,   0, 108, 600, 600, 111,   0,   0],
       [  0,   0, 112, 113, 114, 115,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0]])>

Image arithmetic operations

In addition to doing image arithmetic operations using explicit slices as shown previously, one can also use normal arithmetic operators like + (for addition) or += for in-place addition.

When the right-side operand is included via its ``.aca`` attribute, then the operation is done in ACA coordinates.

This means that the operation is only done on overlapping pixels. This is shown in the examples below. The supported operations for this are:

  • Addition (+ and +=)

  • Subtraction (- and -=)

  • Multiplication (* and *=)

  • Division (/ and /=)

  • True division (/ and /= in Py3+ and with __future__ division)

  • Floor division (// and //=)

  • Modulus (% and %=)

  • Power (** and **=)

Initialize images (different shape and offset)

>>> a = ACAImage(shape=(6, 6), row0=10, col0=20) + 1
>>> a
<ACAImage row0=10 col0=20
array([[1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1]])>
>>> b = ACAImage(np.arange(1, 17).reshape(4, 4), row0=8, col0=18) * 10
>>> b
<ACAImage row0=8 col0=18
array([[ 10,  20,  30,  40],
       [ 50,  60,  70,  80],
       [ 90, 100, 110, 120],
       [130, 140, 150, 160]])>

Add images (output has shape and row0/col0 of left side input)

>>> a + b.aca
<ACAImage row0=10 col0=20
array([[111, 121,   1,   1,   1,   1],
       [151, 161,   1,   1,   1,   1],
       [  1,   1,   1,   1,   1,   1],
       [  1,   1,   1,   1,   1,   1],
       [  1,   1,   1,   1,   1,   1],
       [  1,   1,   1,   1,   1,   1]])>
>>> b + a.aca
<ACAImage row0=8 col0=18
array([[ 10,  20,  30,  40],
       [ 50,  60,  70,  80],
       [ 90, 100, 111, 121],
       [130, 140, 151, 161]])>
>>> b += a.aca
>>> b
<ACAImage row0=8 col0=18
array([[ 10,  20,  30,  40],
       [ 50,  60,  70,  80],
       [ 90, 100, 111, 121],
       [130, 140, 151, 161]])>

Make ``b`` image be fully contained in ``a``

>>> b.row0 = 11
>>> b.col0 = 21
>>> a += b.aca
>>> a
<ACAImage row0=10 col0=20
array([[  1,   1,   1,   1,   1,   1],
       [  1,  11,  21,  31,  41,   1],
       [  1,  51,  61,  71,  81,   1],
       [  1,  91, 101, 112, 122,   1],
       [  1, 131, 141, 152, 162,   1],
       [  1,   1,   1,   1,   1,   1]])>

Normal image addition fails if shape is mismatched

>>> a + b
Traceback (most recent call last):
  File "<ipython-input-19-f96fb8f649b6>", line 1, in <module>
    a + b
  File "chandra_aca/aca_image.py", line 68, in _operator
    out = op(self, other)  # returns self for inplace ops
ValueError: operands could not be broadcast together with shapes (6,6) (4,4)

Meta-data

Finally, the ACAImage object can store arbitrary metadata in the meta dict attribute. However, in order to make this convenient and distinct from native numpy attributes, the meta attributes should have UPPER CASE names. In this case they can be directly accessed as object attributes instead of going through the meta dict:

>>> a.IMGROW0
10
>>> a.meta
{'IMGCOL0': 20, 'IMGROW0': 10}
>>> a.NEWATTR = 'hello'
>>> a.meta
{'IMGCOL0': 20, 'NEWATTR': 'hello', 'IMGROW0': 10}
>>> a.NEWATTR
'hello'
>>> a.meta['fail'] = 1
>>> a.fail
Traceback (most recent call last):
AttributeError: 'ACAImage' object has no attribute 'fail'