Skip to content

Commit

Permalink
Revise and use ApertureSettings and...
Browse files Browse the repository at this point in the history
...move the plot update to a separate method
  • Loading branch information
mwcraig committed Aug 14, 2023
1 parent 510ee17 commit 63de141
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 95 deletions.
194 changes: 109 additions & 85 deletions stellarphot/gui_tools/seeing_profile_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,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.value = dict(**self.aperture_settings.value)

def load_fits(self, file):
"""
Expand Down Expand Up @@ -543,9 +544,13 @@ def _save_seeing_plot(self, button):

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.fits_file.register_callback(self._update_file)
self.save_toggle.observe(self._save_toggle_action, names='value')
Expand Down Expand Up @@ -580,14 +585,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 @@ -626,89 +635,104 @@ def show_event(viewer, event=None, datax=None, datay=None, aperture=None):
# Set this AFTER the radial profile has been created to avoid an attribute
# error.
self.ap_t.value = aperture_radius
self.aperture_settings.value = dict(
radius=aperture_radius,
inner_annulus=aperture_radius + 5,
outer_annulus=aperture_radius + 15
)

# 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,
aperture_settings=ApertureSettings(**self.aperture_settings.value),
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()
20 changes: 10 additions & 10 deletions stellarphot/settings/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@ class ApertureSettings(BaseModel):
outer_annulus : int
Outer radius of the annulus in pixels.
"""
radius : conint(ge=1) = Field(autoui=CustomBoundedIntTex)
inner_annulus : conint(ge=1) = Field(autoui=CustomBoundedIntTex)
outer_annulus : conint(ge=1) = Field(autoui=CustomBoundedIntTex)
radius : conint(ge=1) = Field(autoui=CustomBoundedIntTex, default=1)
gap : conint(ge=1) = Field(autoui=CustomBoundedIntTex, default=1)
annulus_width : conint(ge=1) = Field(autoui=CustomBoundedIntTex, default=1)

class Config:
validate_assignment = True
validate_all = True

@root_validator(skip_on_failure=True)
def check_annuli(cls, values):
if values['inner_annulus'] >= values['outer_annulus']:
raise ValueError('inner_annulus must be smaller than outer_annulus')
if values['radius'] >= values['inner_annulus']:
raise ValueError('radius must be smaller than inner_annulus')
return values
@property
def inner_annulus(self):
return self.radius + self.gap

@property
def outer_annulus(self):
return self.inner_annulus + self.annulus_width

0 comments on commit 63de141

Please sign in to comment.