Photometry

The following examples are adapted from Photometry.jl to show the same examples combined with AstroImages.jl. To learn how to measure background levels, perform aperture photometry, etc see the Photometry.jl documentation.

Background Estimation

From Photometry.jl:

Estimating backgrounds is an important step in performing photometry. Ideally, we could perfectly describe the background with a scalar value or with some distribution. Unfortunately, it's impossible for us to precisely separate the background and foreground signals. Here, we use mixture of robust statistical estimators and meshing to let us get the spatially varying background from an astronomical photo.

Let's show an example [...] Now let's try and estimate the background using estimate_background. First, we'll si gma-clip to try and remove the signals from the stars. Then, the background is broken down into boxes, in this case of size (50, 50). Within each box, the given statistical estimators get the background value and RMS. By default, we use SourceExtractorBackground and StdRMS. This creates a low-resolution image, which we then need to resize. We can accomplish this using an interpolator, by default a cubic-spline interpolator via ZoomInterpolator. The end result is a smooth estimate of the spatially varying background and background RMS.

using Photometry
using AstroImages
using Plots # optional, for implot functionality

# Download our image, courtesy of astropy
url = "https://rawcdn.githack.com/astropy/photutils-datasets/8c97b4fa3a6c9e6ea072faeed2d49a20585658ba/data/M6707HH.fits"
image = AstroImage(download(url))

# sigma-clip
clipped = sigma_clip(image, 1, fill=NaN)

# get background and background rms with box-size (50, 50)
bkg, bkg_rms = estimate_background(clipped, 50)

imview(image)
imview(clipped)
imview(bkg)
imview(bkg_rms)
Example block output

Or, if you have Plots loaded:

using Plots

AstroImages.set_clims!(Percent(99.5))
AstroImages.set_cmap!(:magma)
AstroImages.set_stretch!(identity)

plot(
    implot(image, title="Original"),
    implot(clipped, title="Sigma-Clipped"),
    implot(bkg, title="Background"),
    implot(bkg_rms, title="Background RMS"),
    layout=(2, 2),
    ticks=false,
)
Example block output

We could apply a median filter, too, by specifying filter_size

# get background and background rms with box-size (50, 50) and filter_size (5, 5)
bkg_f, bkg_rms_f = estimate_background(clipped, 50, filter_size=5)

# plot
plot(
    implot(bkg, title="Unfiltered", ylabel="Background"),
    implot(bkg_f, title="Filtered"),
    implot(bkg_rms, ylabel="RMS"),
    implot(bkg_rms_f);
    layout=(2, 2),
    ticks=false,
)
Example block output

Now we can see our image after subtracting the filtered background and ready for Aperture Photometry!

subt = image .- bkg_f[axes(image)...]
clims = extrema(vcat(vec(image), vec(subt)))
plot(
    implot(image; title="Original", clims),
    implot(subt; title="Subtracted", clims),
    size=(1600,1000)
)
Example block output

Source Extraction

From the background-subtracted image, we can detect all sources in the image:

# We specify the uncertainty in the pixel data. We'll set it equal to zero.
errs = zeros(axes(subt))
sources = extract_sources(PeakMesh(), subt, errs, true) # sort from brightest to darkest
Table with 3 columns and 60924 rows:
      x     y     value
    ┌────────────────────
 1  │ 255   226   9762.42
 2  │ 940   681   9661.5
 3  │ 219   924   9653.78
 4  │ 38    678   9647.93
 5  │ 245   85    9637.55
 6  │ 44    1001  9633.13
 7  │ 503   904   9627.34
 8  │ 819   775   9625.92
 9  │ 610   62    9617.79
 10 │ 133   110   9616.88
 11 │ 592   123   9614.86
 12 │ 1055  248   9612.46
 13 │ 424   86    9611.86
 14 │ 558   125   9585.93
 15 │ 125   1021  9582.11
 16 │ 112   637   9579.71
 17 │ 196   2     9579.49
 ⋮  │  ⋮     ⋮       ⋮

There's over 60,000 sources!

We'll define a circular apperture for each source:

aps = CircularAperture.(sources.x, sources.y, 6)[1:1000] # just brightest thousand point sources
1000-element Vector{CircularAperture{Int64}}:
 CircularAperture(255, 226, r=6)
 CircularAperture(940, 681, r=6)
 CircularAperture(219, 924, r=6)
 CircularAperture(38, 678, r=6)
 CircularAperture(245, 85, r=6)
 CircularAperture(44, 1001, r=6)
 CircularAperture(503, 904, r=6)
 CircularAperture(819, 775, r=6)
 CircularAperture(610, 62, r=6)
 CircularAperture(133, 110, r=6)
 ⋮
 CircularAperture(697, 975, r=6)
 CircularAperture(703, 599, r=6)
 CircularAperture(893, 878, r=6)
 CircularAperture(291, 457, r=6)
 CircularAperture(660, 855, r=6)
 CircularAperture(711, 920, r=6)
 CircularAperture(708, 271, r=6)
 CircularAperture(701, 273, r=6)
 CircularAperture(710, 273, r=6)

We can overplot them on our original image. The coordinate sytem used by the Photometry.jl plot recipes (but not the actual return values) doesn't match AstroImages, so we must transpose our image:

implot(subt', colorbar=false)
plot!(aps)
Example block output

Measuring Photometry

Finally we can extract the source photometry

table = photometry(aps, subt)
Table with 3 columns and 1000 rows:
      xcenter  ycenter  aperture_sum
    ┌───────────────────────────────
 1  │ 255      226      20542.4
 2  │ 940      681      7125.3
 3  │ 219      924      14741.0
 4  │ 38       678      31056.4
 5  │ 245      85       -910.308
 6  │ 44       1001     2792.62
 7  │ 503      904      6954.07
 8  │ 819      775      -3136.61
 9  │ 610      62       4338.13
 10 │ 133      110      22219.0
 11 │ 592      123      18562.2
 12 │ 1055     248      3277.33
 13 │ 424      86       255299.0
 14 │ 558      125      6659.03
 15 │ 125      1021     11524.1
 16 │ 112      637      1349.78
 17 │ 196      2        -797.491
 ⋮  │    ⋮        ⋮          ⋮

And plot them:

scatter(
    table.xcenter,
    table.ycenter,
    aspectratio=1,
    marker_z=table.aperture_sum,
    markerstrokewidth=0,
    label="",
    framestyle=:box,
    background_inside=:black,
    color=:white
)
Example block output