Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pydantic/ipyautoui aperture settings #154

Merged
merged 18 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
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
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ install_requires =
pyyaml
astrowidgets
ipyfilechooser
ipyautoui
pydantic <2
mwcraig marked this conversation as resolved.
Show resolved Hide resolved
python_requires = >=3.10
setup_requires = setuptools_scm
zip_safe = False
Expand Down
8 changes: 2 additions & 6 deletions stellarphot/gui_tools/photometry_widget_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import ipywidgets as ipw
from ipyfilechooser import FileChooser

from stellarphot.settings import ApertureSettings

__all__ = ['PhotometrySettings']

Expand Down Expand Up @@ -116,9 +117,4 @@ def _update_object_list(self, change):
self._object_name.options = []

def _update_aperture_rad(self, locator):
with open(locator.selected) as f:
stuff = f.read()

self._aperture_radius = int(stuff.split(',')[0])
self._inner_annulus = int(stuff.split(',')[1])
self._outer_annulus = int(stuff.split(',')[2])
self.aperture_settings = ApertureSettings.parse_file(locator.selected)
218 changes: 127 additions & 91 deletions stellarphot/gui_tools/seeing_profile_functions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from pathlib import Path
import warnings

import numpy as np
Expand All @@ -19,6 +20,7 @@
from stellarphot.io import TessSubmission
from stellarphot.gui_tools.fits_opener import FitsOpener
from stellarphot.plotting import seeing_plot
from stellarphot.settings import ApertureSettings, ui_generator

__all__ = ['set_keybindings', 'find_center', 'radial_profile',
'RadialProfile', 'box', 'SeeingProfileWidget']
Expand Down Expand Up @@ -160,7 +162,7 @@ def find_center(image, center_guess, cutout_size=30, max_iters=10):
return cen


# TODO: Why eactly is this separate from the class RadialProfile?
# TODO: Why exactly is this separate from the class RadialProfile?
def radial_profile(data, center, size=30, return_scaled=True):
"""
Construct a radial profile of a chunk of width ``size`` centered
Expand Down Expand Up @@ -464,12 +466,16 @@ def __init__(self, imagewidget=None, width=500):
big_box = ipw.HBox()
big_box = ipw.GridspecLayout(1, 2)
layout = ipw.Layout(width='20ch')
hb = ipw.HBox()
self.ap_t = ipw.IntText(description='Aperture radius', value=5, layout=layout, style=desc_style)
self.in_t = ipw.IntText(description='Inner annulus', value=10, layout=layout, style=desc_style)
self.out_t = ipw.IntText(description='Outer annulus', value=20, layout=layout, style=desc_style)
vb = ipw.VBox()
self.aperture_settings_file_name = ipw.Text(
description="Aperture settings file name",
style={'description_width': 'initial'},
value="aperture_settings.json"
)
self.aperture_settings = ui_generator(ApertureSettings)
self.aperture_settings.path = Path(self.aperture_settings_file_name.value)
JuanCab marked this conversation as resolved.
Show resolved Hide resolved
self.save_aps = ipw.Button(description="Save settings")
hb.children = [self.ap_t, self.save_aps] #, self.in_t, self.out_t]
vb.children = [self.aperture_settings_file_name, self.aperture_settings] #, self.save_aps] #, self.in_t, self.out_t]

lil_box = ipw.VBox()
lil_tabs = ipw.Tab()
Expand All @@ -481,7 +487,7 @@ def __init__(self, imagewidget=None, width=500):
lil_box.children = [lil_tabs, self.tess_box]

imbox = ipw.VBox()
imbox.children = [imagewidget, hb]
imbox.children = [imagewidget, vb]
big_box[0, 0] = imbox
big_box[0, 1] = lil_box
big_box.layout.width = '100%'
Expand All @@ -499,6 +505,7 @@ def __init__(self, imagewidget=None, width=500):
# Fill this in later with name of object from FITS file
self.object_name = ''
self._set_observers()
self.aperture_settings.description = ""

def load_fits(self, file):
"""
Expand Down Expand Up @@ -539,20 +546,30 @@ def _save_toggle_action(self, change):
def _save_seeing_plot(self, button):
self._seeing_plot_fig.savefig(self.seeing_file_name.value)

def _change_aperture_save_location(self, change):
new_name = change['new']
new_path = Path(new_name)
self.aperture_settings.path = new_path
self.aperture_settings.savebuttonbar.unsaved_changes = True
JuanCab marked this conversation as resolved.
Show resolved Hide resolved

def _set_observers(self):
def aperture_obs(change):
self._mse(self.iw, aperture=change['new'])
self._update_plots()
ape = ApertureSettings(**change['new'])
self.aperture_settings.description = (
f"Inner annulus: {ape.inner_annulus}, outer annulus: {ape.outer_annulus}"
)

self.ap_t.observe(aperture_obs, names='value')
self.aperture_settings.observe(aperture_obs, names='_value')
self.save_aps.on_click(self._save_ap_settings)
self.aperture_settings_file_name.observe(self._change_aperture_save_location, names='value')
self.fits_file.register_callback(self._update_file)
self.save_toggle.observe(self._save_toggle_action, names='value')
self.save_seeing.on_click(self._save_seeing_plot)
self.setting_box.planet_num.observe(self._set_seeing_profile_name)
self.setting_box.telescope_code.observe(self._set_seeing_profile_name)

def _save_ap_settings(self, button):
ap_rad = self.ap_t.value
with open('aperture_settings.txt', 'w') as f:
f.write(f'{ap_rad},{ap_rad + 10},{ap_rad + 15}')

Expand All @@ -578,14 +595,18 @@ def _make_tess_box(self):
self.setting_box = setting_box
return box

def _update_ap_settings(self, value):
self.aperture_settings.value = value

def _make_show_event(self):

def show_event(viewer, event=None, datax=None, datay=None, aperture=None):
profile_size = 60
fig_size = (10, 5)

default_gap = 5 # pixels
default_annulus_width = 15 # pixels
self.save_toggle.disabled = False

update_aperture_settings = False
if event is not None:
# User clicked on a star, so generate profile
i = self.iw._viewer.get_image()
Expand Down Expand Up @@ -621,88 +642,103 @@ def show_event(viewer, event=None, datax=None, datay=None, aperture=None):
aperture_radius = np.round(1.5 * 2 * rad_prof.HWHM, 0)
self.rad_prof = rad_prof

# Set this AFTER the radial profile has been created to avoid an attribute
# error.
self.ap_t.value = aperture_radius
# Make an aperture settings object, but don't update it's widget yet.
ap_settings = ApertureSettings(radius=aperture_radius,
gap=default_gap,
annulus_width=default_annulus_width)
update_aperture_settings = True
else:
# User changed aperture
aperture_radius = aperture
aperture_radius = aperture['radius']
ap_settings = ApertureSettings(**aperture) # Make an ApertureSettings object

rad_prof = self.rad_prof

# DISPLAY THE SCALED PROFILE
self.out.clear_output(wait=True)
with self.out:
# sub_med += med
self._seeing_plot_fig = seeing_plot(rad_prof.r_exact, rad_prof.scaled_exact_counts,
rad_prof.ravg,
rad_prof.scaled_profile, rad_prof.HWHM,
self.object_name, gap=10, annulus_width=15,
radius = aperture_radius,
figsize=fig_size)
plt.show()

# CALCULATE AND DISPLAY NET COUNTS INSIDE RADIUS
self.out2.clear_output(wait=True)
with self.out2:
sub_blot = rad_prof.sub_data.copy().astype('float32')
min_idx = profile_size // 2 - 2 * rad_prof.FWHM
max_idx = profile_size // 2 + 2 * rad_prof.FWHM
sub_blot[min_idx:max_idx, min_idx:max_idx] = np.nan
sub_std = np.nanstd(sub_blot)
new_sub_med = np.nanmedian(sub_blot)
r_exact, ravg, tbin2 = radial_profile(rad_prof.data - new_sub_med, rad_prof.cen,
size=profile_size,
return_scaled=False)
r_exact_s, ravg_s, tbin2_s = radial_profile(rad_prof.data - new_sub_med, rad_prof.cen,
size=profile_size,
return_scaled=True)
#tbin2 = np.bincount(r.ravel(), (sub_data - sub_med).ravel())
counts = np.cumsum(tbin2)
plt.figure(figsize=fig_size)
plt.plot(rad_prof.radius_values, counts)
plt.xlim(0, 40)
ylim = plt.ylim()
plt.vlines(aperture_radius, *plt.ylim(), colors=['red'])
plt.ylim(*ylim)
plt.grid()

plt.title('Net counts in aperture')
e_sky = np.nanmax([np.sqrt(new_sub_med), sub_std])

plt.xlabel('Aperture radius (pixels)')
plt.ylabel('Net counts')
plt.show()

# CALCULATE And DISPLAY SNR AS A FUNCTION OF RADIUS
self.out3.clear_output(wait=True)
with self.out3:
read_noise = 10 # electrons
gain = 1.5 # electrons/count
# Poisson error is square root of the net number of counts enclosed
poisson = np.sqrt(np.cumsum(tbin2))

# This is obscure, but correctly calculated the number of pixels at
# each radius, since the smoothed is tbin2 divided by the number of
# pixels.
nr = tbin2 / tbin2_s

# This ignores dark current
error = np.sqrt(poisson ** 2 + np.cumsum(nr)
* (e_sky ** 2 + (read_noise / gain)** 2))

snr = np.cumsum(tbin2) / error
plt.figure(figsize=fig_size)
plt.plot(rad_prof.radius_values + 1, snr)

plt.title(f'Signal to noise ratio max {snr.max():.1f} '
f'at radius {snr.argmax() + 1}')
plt.xlim(0, 40)
ylim = plt.ylim()
plt.vlines(aperture_radius, *plt.ylim(), colors=['red'])
plt.ylim(*ylim)
plt.xlabel('Aperture radius (pixels)')
plt.ylabel('SNR')
plt.grid()
plt.show()
if update_aperture_settings:
self._update_ap_settings(ap_settings.dict())

self._update_plots()

return show_event

def _update_plots(self):
# DISPLAY THE SCALED PROFILE
fig_size = (10, 5)
profile_size = 60

rad_prof = self.rad_prof
self.out.clear_output(wait=True)
ap_settings = ApertureSettings(**self.aperture_settings.value)
with self.out:
# sub_med += med
self._seeing_plot_fig = seeing_plot(rad_prof.r_exact, rad_prof.scaled_exact_counts,
rad_prof.ravg,
rad_prof.scaled_profile, rad_prof.HWHM,
self.object_name,
aperture_settings=ap_settings,
figsize=fig_size)
plt.show()

# CALCULATE AND DISPLAY NET COUNTS INSIDE RADIUS
self.out2.clear_output(wait=True)
with self.out2:
sub_blot = rad_prof.sub_data.copy().astype('float32')
min_idx = profile_size // 2 - 2 * rad_prof.FWHM
max_idx = profile_size // 2 + 2 * rad_prof.FWHM
sub_blot[min_idx:max_idx, min_idx:max_idx] = np.nan
sub_std = np.nanstd(sub_blot)
new_sub_med = np.nanmedian(sub_blot)
r_exact, ravg, tbin2 = radial_profile(rad_prof.data - new_sub_med, rad_prof.cen,
size=profile_size,
return_scaled=False)
r_exact_s, ravg_s, tbin2_s = radial_profile(rad_prof.data - new_sub_med, rad_prof.cen,
size=profile_size,
return_scaled=True)
#tbin2 = np.bincount(r.ravel(), (sub_data - sub_med).ravel())
counts = np.cumsum(tbin2)
plt.figure(figsize=fig_size)
plt.plot(rad_prof.radius_values, counts)
plt.xlim(0, 40)
ylim = plt.ylim()
plt.vlines(ap_settings.radius, *plt.ylim(), colors=['red'])
plt.ylim(*ylim)
plt.grid()

plt.title('Net counts in aperture')
e_sky = np.nanmax([np.sqrt(new_sub_med), sub_std])

plt.xlabel('Aperture radius (pixels)')
plt.ylabel('Net counts')
plt.show()

# CALCULATE And DISPLAY SNR AS A FUNCTION OF RADIUS
self.out3.clear_output(wait=True)
with self.out3:
read_noise = 10 # electrons
gain = 1.5 # electrons/count
# Poisson error is square root of the net number of counts enclosed
poisson = np.sqrt(np.cumsum(tbin2))

# This is obscure, but correctly calculated the number of pixels at
# each radius, since the smoothed is tbin2 divided by the number of
# pixels.
nr = tbin2 / tbin2_s

# This ignores dark current
error = np.sqrt(poisson ** 2 + np.cumsum(nr)
* (e_sky ** 2 + (read_noise / gain)** 2))

snr = np.cumsum(tbin2) / error
plt.figure(figsize=fig_size)
plt.plot(rad_prof.radius_values + 1, snr)

plt.title(f'Signal to noise ratio max {snr.max():.1f} '
f'at radius {snr.argmax() + 1}')
plt.xlim(0, 40)
ylim = plt.ylim()
plt.vlines(ap_settings.radius, *plt.ylim(), colors=['red'])
plt.ylim(*ylim)
plt.xlabel('Aperture radius (pixels)')
plt.ylabel('SNR')
plt.grid()
plt.show()
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,6 @@
"source": [
"seeing_profile.box"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
Expand Down
18 changes: 12 additions & 6 deletions stellarphot/notebooks/photometry/03-photometry-template.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,21 @@
"sources = SourceListData.read(source_file_name)\n",
"\n",
"# Retrieve the aperture and annulus settings\n",
"aperture_radius = ps.aperture_radius\n",
"inner_annulus = ps.inner_annulus\n",
"outer_annulus = ps.outer_annulus\n",
"aperture_settings = ps.aperture_settings\n",
"\n",
"# Retrieve the object name here\n",
"object_name = ps.object_name"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"aperture_settings.inner_annulus"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -149,8 +156,7 @@
" object_name,\n",
" sources, feder_cg_16m,\n",
" feder_obs,\n",
" aperture_radius,\n",
" inner_annulus, outer_annulus,\n",
" aperture_settings,\n",
" shift_tolerance, max_adu, fwhm_estimate,\n",
" include_dig_noise=True,\n",
" reject_too_close=True,\n",
Expand Down Expand Up @@ -201,7 +207,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
"version": "3.10.6"
}
},
"nbformat": 4,
Expand Down
Loading