Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
05f4e26
:sparkles: Developing PerturbationMatrix class
christinahedges Mar 30, 2022
172fbda
:white_check_mark: fixing flake8
christinahedges Mar 30, 2022
fb31d8b
:sparkles: added a 3D matrix
christinahedges Mar 31, 2022
d37a540
:art: fairly good sketch
christinahedges Apr 2, 2022
884f24d
Update src/psfmachine/perturbation.py
christinahedges Apr 5, 2022
18cca59
Update src/psfmachine/perturbation.py
christinahedges Apr 5, 2022
3df9bea
fixing identation and _clean_vectors when segments=True
jorgemarpa Apr 5, 2022
b64b9cc
:zap: faster!
christinahedges Apr 5, 2022
2d55aa1
:art: adding pixel masking
christinahedges Apr 5, 2022
7310ed3
:memo: added docstrings
christinahedges Apr 5, 2022
34d943c
smooth vector fx
jorgemarpa Apr 6, 2022
7648325
improved plots and docstrings
jorgemarpa Apr 6, 2022
b330ee6
machine uses PerturbationMatrix3D
jorgemarpa Apr 6, 2022
cc99962
demoving _time_bin method
jorgemarpa Apr 6, 2022
5e2de68
normalize poscorr/centr and fixing pytest to work with new perturbati…
jorgemarpa Apr 6, 2022
cceeb2b
smooth_vector API improved
jorgemarpa Apr 11, 2022
c3cdfb1
gaussian smooth -> spline smooth
jorgemarpa Apr 11, 2022
e5f3f8e
matching arguments between `perturbation` and `machine`
jorgemarpa Apr 11, 2022
84a064c
:sparkles: Added PCA method
christinahedges Apr 11, 2022
3da1557
:art: added fbpca explicitly as dependency
christinahedges Apr 11, 2022
1520001
:memo: docs
christinahedges Apr 11, 2022
612d3fd
merging from refactor-time
jorgemarpa Apr 12, 2022
78633dc
:bug: fixing pca
christinahedges Apr 12, 2022
14babe3
:bug: fix @jorgemarpa bug
christinahedges Apr 12, 2022
9d4a80f
:memo: update docstrings
christinahedges Apr 12, 2022
88c7493
merging from refactor-time
jorgemarpa Apr 12, 2022
556317d
Update src/psfmachine/perturbation.py
christinahedges Apr 12, 2022
648211e
adding perturbation-pca into machine
jorgemarpa Apr 12, 2022
e97d3f5
:sparkles: now pca has smoothed components
christinahedges Apr 12, 2022
ca3645d
improved spline and gaussian smooth
jorgemarpa Apr 12, 2022
8b4da3d
P3 -> P
jorgemarpa Apr 12, 2022
94c3aaf
hotfix
jorgemarpa Apr 12, 2022
68861a6
flake8
jorgemarpa Apr 12, 2022
0cbde46
:bug: update pca
christinahedges Apr 12, 2022
a00d69c
:art: merge
christinahedges Apr 12, 2022
18fdc55
:art: flake8
christinahedges Apr 12, 2022
de15bfc
perturbed model
jorgemarpa Apr 12, 2022
eb9ce3c
:art: no comment block
christinahedges Apr 12, 2022
2efb963
:memo: docstrings
christinahedges Apr 12, 2022
f7650e5
Merge branch 'refactor-time' of https://github.com/SSDataLab/psfmachi…
jorgemarpa Apr 12, 2022
70af6ac
time_resolution attr
jorgemarpa Apr 13, 2022
514f72b
matrix transpose hotfix
jorgemarpa Apr 13, 2022
c6ebac8
agressive bkg pixel mask
jorgemarpa Apr 13, 2022
eaa807f
:art: updating sane defaults, adding smooth PCA components
christinahedges Apr 15, 2022
e1bc28e
:art: sane defaults
christinahedges Apr 15, 2022
65ef6db
Merge branch 'refactor-time' of https://github.com/SSDataLab/psfmachi…
jorgemarpa Apr 16, 2022
9d336fb
pca smooth time scale arg visible
jorgemarpa Apr 18, 2022
ea876cc
time_resolution kwarg
jorgemarpa Apr 19, 2022
12cd2a2
using time_knots kwarg
jorgemarpa Apr 20, 2022
dc1a8e6
new time model diag plot and components API
jorgemarpa Apr 20, 2022
5aff7e9
Merge branch 'master' of https://github.com/jorgemarpa/psfmachine int…
jorgemarpa Apr 28, 2022
7ac7b71
updated lock
jorgemarpa Apr 28, 2022
1e6b8a4
pytest fixed
jorgemarpa Apr 28, 2022
90385d9
better pixel mask in build_time_model
jorgemarpa Apr 29, 2022
5b18a7e
Update pyproject.toml
jorgemarpa Apr 29, 2022
88ca62c
bump up mac version to 1.1.2, missing docstr and removed dup fx in utils
jorgemarpa Jun 3, 2022
5c4d877
add cbv
jorgemarpa Jun 7, 2022
19db062
better priors for perturb model
jorgemarpa Jun 22, 2022
0eba4d6
accepting other_vectors in time_model to use CBVs
jorgemarpa Jun 22, 2022
54ea59f
plotting psf-nova
jorgemarpa Jun 22, 2022
eb840a4
typo
jorgemarpa Jun 22, 2022
2e7eb70
perturb matrix 3d testing fixed with new synthetic data
jorgemarpa Jun 22, 2022
da580fa
flake8
jorgemarpa Jun 22, 2022
e63012d
mean model renormalization
jorgemarpa Jun 23, 2022
6894d93
docstrings
jorgemarpa Jun 23, 2022
742a1c2
psf metrics and renorm
jorgemarpa Jun 28, 2022
0abbc01
Merge branch 'master' of https://github.com/jorgemarpa/psfmachine int…
jorgemarpa Jun 28, 2022
063e7fb
Merge branch 'master' of https://github.com/jorgemarpa/psfmachine int…
jorgemarpa Jun 28, 2022
991629c
docstrings
jorgemarpa Jun 28, 2022
ea6c68d
Merge branch 'perturbation-cbv' of https://github.com/jorgemarpa/psfm…
jorgemarpa Jun 29, 2022
edca192
rename
jorgemarpa Jun 30, 2022
5d92487
improved HD mask and exception for psf metrics
jorgemarpa Jun 30, 2022
1240cda
norm factor
jorgemarpa Jun 30, 2022
61ad1a7
shape model normalization flag
jorgemarpa Jun 30, 2022
8f53887
Merge branch 'master' of https://github.com/jorgemarpa/psfmachine int…
jorgemarpa Jul 1, 2022
152bc30
Merge branch 'master' of https://github.com/jorgemarpa/psfmachine int…
jorgemarpa Jul 5, 2022
9958253
review 1
jorgemarpa Jul 5, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 183 additions & 2 deletions src/psfmachine/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import numpy as np
import pandas as pd
from scipy import sparse
from scipy import stats
import astropy.units as u
from tqdm import tqdm
import matplotlib.pyplot as plt
Expand Down Expand Up @@ -1047,8 +1048,11 @@ def build_shape_model(

self.psf_w = psf_w
self.psf_w_err = psf_w_err
self.normalized_shape_model = False

# We then build the same design matrix for all pixels with flux
# this non-normalized mean model is temporary and used to re-create a better
# `source_mask`
self._get_mean_model()
# remove background pixels and recreate mean model
self._update_source_mask_remove_bkg_pixels(
Expand Down Expand Up @@ -1127,8 +1131,8 @@ def _update_source_mask_remove_bkg_pixels(self, flux_cut_off=1, frame_index="mea
# (self.mean_model.max(axis=1) < 1)
# )

# Recreate mean model!
self._get_mean_model()
# create the final normalized mean model!
self._get_normalized_mean_model()

def _get_mean_model(self):
"""Convenience function to make the scene model"""
Expand All @@ -1150,6 +1154,183 @@ def _get_mean_model(self):
mean_model.eliminate_zeros()
self.mean_model = mean_model

def _get_normalized_mean_model(self, npoints=300, plot=False):
"""Renomarlize shape model to sum 1"""

# create a high resolution polar grid
r = self.source_mask.multiply(self.r).data
phi_hd = np.linspace(-np.pi, np.pi, npoints)
r_hd = np.linspace(0, r.max(), npoints)
phi_hd, r_hd = np.meshgrid(phi_hd, r_hd)

# high res DM
Ap = _make_A_polar(
phi_hd.ravel(),
r_hd.ravel(),
rmin=self.rmin,
rmax=self.rmax,
cut_r=self.cut_r,
n_r_knots=self.n_r_knots,
n_phi_knots=self.n_phi_knots,
)
# evaluate the high res model
mean_model_hd = Ap.dot(self.psf_w)
mean_model_hd[~np.isfinite(mean_model_hd)] = np.nan
mean_model_hd = mean_model_hd.reshape(phi_hd.shape)

# mask out datapoint that don't contribuite to the psf
mean_model_hd_ma = mean_model_hd.copy()
mask = mean_model_hd > -3
mean_model_hd_ma[~mask] = -np.inf
mask &= ~((r_hd > 14) & (np.gradient(mean_model_hd_ma, axis=0) > 0))
mean_model_hd_ma[~mask] = -np.inf

# double integral using trapezoidal rule
integral = np.trapz(
np.trapz(10 ** mean_model_hd_ma, r_hd[:, 0], axis=0),
phi_hd[0, :],
axis=0,
)
# renormalize weights and build new shape model
if not self.normalized_shape_model:
self.psf_w *= np.log10(integral)
self.normalized_shape_model = True
self._get_mean_model()

if plot:
fig, ax = plt.subplots(1, 2, figsize=(9, 5))
im = ax[0].scatter(
phi_hd.ravel(),
r_hd.ravel(),
c=mean_model_hd_ma.ravel(),
vmin=-3,
vmax=-1,
s=1,
label=r"$\int = $" + f"{integral:.4f}",
)
im = ax[1].scatter(
r_hd.ravel() * np.cos(phi_hd.ravel()),
r_hd.ravel() * np.sin(phi_hd.ravel()),
c=mean_model_hd_ma.ravel(),
vmin=-3,
vmax=-1,
s=1,
)
ax[0].legend()
fig.colorbar(im, ax=ax, location="bottom")
plt.show()

def get_psf_metrics(self, npoints_per_pixel=10):
"""
Computes three metrics for the PSF model:
source_psf_fraction: the amount of PSF in the data. Tells how much of a
sources is used to estimate the PSF, values are in between [0, 1].
perturbed_ratio_mean: the ratio between the mean model and perturbed model
for each source. Usefull to find when the time model affects the
mean value of the light curve.
perturbed_std: the standard deviation of the perturbed model for each
source. USeful to find when the time model introduces variability in the
light curve.

If npoints_per_pixel > 0, it creates high npoints_per_pixel shape models for each source by
dividing each pixels into a grid of [npoints_per_pixel x npoints_per_pixel]. This provides
a better estimate of `source_psf_fraction`.

Parameters
----------
npoints_per_pixel : int
Value in which each pixel axis is split to increase npoints_per_pixel. Default is
0 for no subpixel npoints_per_pixel.

"""
if npoints_per_pixel > 0:
# find from which observation (TPF) a sources comes
obs_per_pixel = self.source_mask.multiply(self.pix2obs).tocsr()
tpf_idx = []
for k in range(self.source_mask.shape[0]):
pix = obs_per_pixel[k].data
mode = stats.mode(pix)[0]
if len(mode) > 0:
tpf_idx.append(mode[0])
else:
tpf_idx.append(
[x for x, ss in enumerate(self.tpf_meta["sources"]) if k in ss][
0
]
)
tpf_idx = np.array(tpf_idx)

# get the pix coord for each source, we know how to increase resolution in
# the pixel space but not in WCS
row = self.source_mask.multiply(self.row).tocsr()
col = self.source_mask.multiply(self.column).tocsr()
mean_model_hd_sum = []
# iterating per sources avoids creating a new super large `source_mask`
# with high resolution, which a priori is hard
for k in range(self.nsources):
# find row, col combo for each source
row_ = row[k].data
col_ = col[k].data
colhd, rowhd = [], []
# pixels are divided into `resolution` - 1 subpixels
for c, r in zip(col_, row_):
x = np.linspace(c - 0.5, c + 0.5, npoints_per_pixel + 1)
y = np.linspace(r - 0.5, r + 0.5, npoints_per_pixel + 1)
x, y = np.meshgrid(x, y)
colhd.extend(x[:, :-1].ravel())
rowhd.extend(y[:-1].ravel())
colhd = np.array(colhd)
rowhd = np.array(rowhd)
# convert to ra, dec beacuse machine shape model works in sky coord
rahd, dechd = self.tpfs[tpf_idx[k]].wcs.wcs_pix2world(
colhd - self.tpfs[tpf_idx[k]].column,
rowhd - self.tpfs[tpf_idx[k]].row,
0,
)
drahd = rahd - self.sources["ra"][k]
ddechd = dechd - self.sources["dec"][k]
drahd = drahd * (u.deg)
ddechd = ddechd * (u.deg)
rhd = np.hypot(drahd, ddechd).to("arcsec").value
phihd = np.arctan2(ddechd, drahd).value
# create a high resolution DM
Ap = _make_A_polar(
phihd.ravel(),
rhd.ravel(),
rmin=self.rmin,
rmax=self.rmax,
cut_r=self.cut_r,
n_r_knots=self.n_r_knots,
n_phi_knots=self.n_phi_knots,
)
# evaluate the HD model
modelhd = 10 ** Ap.dot(self.psf_w)
# compute the model sum for source, how much of the source is in data
mean_model_hd_sum.append(
np.trapz(modelhd, dx=1 / npoints_per_pixel ** 2)
)

# get normalized psf fraction metric
self.source_psf_fraction = np.array(
mean_model_hd_sum
) # / np.nanmax(mean_model_hd_sum)
else:
self.source_psf_fraction = np.array(self.mean_model.sum(axis=1)).ravel()

# time model metrics
if hasattr(self, "P"):
perturbed_lcs = np.vstack(
[
np.array(self.perturbed_model(time_index=k).sum(axis=1)).ravel()
for k in range(self.time.shape[0])
]
)
self.perturbed_ratio_mean = (
np.nanmean(perturbed_lcs, axis=0)
/ np.array(self.mean_model.sum(axis=1)).ravel()
)
self.perturbed_std = np.nanstd(perturbed_lcs, axis=0)

def plot_shape_model(self, radius=20, frame_index="mean", bin_data=False):
"""
Diagnostic plot of shape model.
Expand Down
6 changes: 6 additions & 0 deletions src/psfmachine/tpf.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,8 @@ def load_shape_model(self, input=None, plot=False):
self.rmax = hdu[1].header["rmax"]
self.cut_r = hdu[1].header["cut_r"]
self.psf_w = hdu[1].data["psf_w"]
# read from header if weights come from a normalized model.
self.normalized_shape_model = bool(hdu[1].header.get("normalized"))
del hdu

# create mean model, but PRF shapes from FFI are in pixels! and TPFMachine
Expand Down Expand Up @@ -732,6 +734,10 @@ def save_shape_model(self, output=None):
)
# spline degree is hardcoded in `_make_A_polar` implementation.
table.header["spln_deg"] = (3, "Degree of the spline basis")
table.header["normalized"] = (
int(self.normalized_shape_model),
"Normalized weights",
)

table.writeto(output, checksum=True, overwrite=True)

Expand Down