Skip to content

Commit

Permalink
Merge pull request #154 from mwcraig/add-aperture-settings
Browse files Browse the repository at this point in the history
  • Loading branch information
JuanCab authored Aug 18, 2023
2 parents 80a79a0 + 56ece82 commit 4407138
Show file tree
Hide file tree
Showing 14 changed files with 326 additions and 167 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Other Changes and Additions
^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ Major reorganizaiton of code including moving functions to new modules. [#130, #133]
+ Now requires python 3.10 or later. [#147]
+ Use pydantic for aperture settings. [#154]

Bug Fixes
^^^^^^^^^
Expand Down
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
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)
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

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

0 comments on commit 4407138

Please sign in to comment.