def layer_sat_changed(self): if self.layer_stretched is not None: r = 3 * self.saturation * self.layer_stretched + self.lum r[r < 0] = 0 r[r > 1] = 1 Component.get('View').display_image( np.stack([r, self.lum, self.lum], axis=-1))
def on_save_object(self): ''' On saving we ensure that Name, OT and Con is saved as these appear in the previous object table; other props don't need to be saved as they are looked up from the DSO database on each load. ''' Component.get('Metadata').set({ 'Name': self.Name.strip(), 'OT': self.OT, 'Con': self.Con }) # prepare props in canonical format props = { 'Name': self.Name.strip(), 'OT': self.OT.strip(), 'Con': self.Con.strip(), 'RA': RA.parse(self.RA), 'Dec': Dec.parse(self.Dec), 'Diam': str_to_arcmin(self.Diam), 'Mag': str_to_float(self.Mag), 'Other': self.Other } # get catalogue to check if anything has changed and update user objects if necc. Component.get('Catalogues').check_update(props)
def snap(self, return_image=False, *args): self.return_image = return_image view = Component.get('View') if not hasattr(view, 'last_image') or (view.last_image is None): return dso = Component.get('DSO') self.save_path = '{:} {:}.{:}'.format( os.path.join(self.app.get_path('snapshots'), purify_name(dso.Name)), datetime.now().strftime('%d%b%y_%H_%M_%S'), self.save_format) if not return_image and self.save_format == 'fit': self.save_as_fits() elif self.style == 'eyepiece': im = self.eyepiece_view() elif self.style == 'landscape': im = self.landscape_view() if return_image: return im
def processing_details(self): block = [] stacker = Component.get('Stacker') d = stacker.describe() if d is None: return [] if 'total_exposure' in d: block += [s_to_minsec(d['total_exposure'])] if d['multiple_exposures']: block += ['varied exposures'] else: block += [ '{:} x {:.0f}s'.format(d['nsubs'], d['sub_exposure']) ] if not d['filters'].endswith('L'): # more interesting than just LUM block += [d['filters']] if d['nsubs'] > 1: block += ['{:} stack'.format(stacker.combine)] block += ['{:} stretch'.format(Component.get('Monochrome').stretch)] calib = ','.join([c for c in ['dark', 'flat', 'bias'] if d[c]]) calib = calib if calib else 'no darks/flats' block += [calib] return block # block[::-1] # reverse!
def on_capturing(self, *args): # user (pressing camera button) or system changes capture state logger.debug('capturing state changed') if not self.capturing: self.stop_capture() return if not Component.get('Camera').connected(): self.stop_capture(message='cannot connect to camera') return if not Component.get('FilterWheel').connected(): self.stop_capture(message='cannot connect to filterwheel') return self.gui.disable(capture_controls) self.gui.disable({'load_previous', 'new_DSO'}) self.gui.enable({'capturing'}) logger.debug('camera connected, starting capture') try: self.capture() except Exception as e: self.stop_capture(message=e)
def create_RGB(self): ''' Combine L, A and B into RGB image. Called here when previous colour process has been applied (hue_changed), or when luminosity component has changed (below) ''' if self.avail(['lum', 'A_hue']): self.RGB = lab2rgb( np.stack([100 * self.lum, self.A_hue, self.B_hue], axis=-1)) Component.get('View').display_image(self.RGB)
def filter_selected(self, filt, but): # set exposure on GUI and on scripts panel if Component.get('CaptureScript').current_script == 'seq': logger.debug('toggled filter {:}'.format(filt)) else: logger.debug('selected filter {:}'.format(filt)) Component.get('CaptureScript').filter_changed([filt]) self.hide()
def load_previous(self, path): # previous will have been saved by this point self.existing_object = True gui = self.app.gui gui.enable() gui.set('show_reticle', False, update_property=True) self.current_object_dir = path Component.initialise_previous_object()
def new_object(self, *args): if self.closing: return self.current_object_dir = None self.existing_object = False self.sub_type = None Component.initialise_new_object() self.app.gui.set('frame_script', True, update_property=True)
def connection_changed(self, device, connected): if device in self.connect_dots: self.connect_dots[device].text = jicon( 'dot', color=('g' if connected else 'r')) Component.get(device).info('not connected') if device in self.connect_buttons: self.connect_buttons[ device].text = 'disconnect...' if connected else 'connect...' Component.get(device).info('connected')
def send_to_display(self, *args): # send short subs directly to display self.fps = 1 / (time.time() - self.start_capture_time) try: Component.get('Monochrome').display_sub( Component.get('Camera').get_image()) self.capture() except Exception as e: logger.exception('problem send to display {:}'.format(e))
def update_state(self): s = 'L-R, ' if self.flip_LR else '' s += 'U-D' if self.flip_UD else '' if s.endswith(', '): s = s[:-2] flipstr = ' | flip {:}'.format(s) if s else '' invstr = ' | inv' if self.invert else '' self.info('rot {:.0f}\u00b0 | {:4.1f}x{:}{:}'.format( self.orientation, self.lever_to_zoom(self.zoom), flipstr, invstr)) Component.get('Annotator').update()
def reset(self): # Called when we have a new object and when user clears stack self.stack_cache = {} self.orig_rejects = set({}) # new self.subs.clear() self.selected_sub = -1 self.update_stack_scroller() self.set_to_subs() Component.get( 'View').reset() # might not be needed as View also does a reset! self.info('')
def new_observation(self): OT = Component.get('Metadata').get('OT', '') Name = Component.get('Metadata').get('Name', None) logger.info('{:}/{:}'.format(Name, OT)) # update observed count if DSO is known if Name is not None: name = '{:}/{:}'.format(Name, OT) if name in self.objects: self.objects[name]['Obs'] += 1 if hasattr(self, 'table'): self.table.update()
def on_save_object(self): logger.debug('saving properties to metadata') # save properties for p in self.props: Component.get('Metadata').set(p, getattr(self, p)) # only update last session if live rather than previous if self.is_new_object: self.save_session()
def on_stop(self, exception=None): if exception is None: logger.info('normal close down') else: logger.exception('root exception: {:}'.format(exception)) # save width and height Config.set('graphics', 'width', str(int(Window.width/ dp(1)))) Config.set('graphics', 'height', str(int(Window.height/dp(1)))) Config.write() Component.close() self.gui.on_close() logger.info('finished close down')
def recompute(self, widgy=None, realign=False): # initial load, recompute or realign Component.get('Aligner').reset() self.stack_cache = {} # shuffle-based realign if realign: np.random.shuffle(self.subs) # if we have a B or Ha, keep shuffling until we start with that if len([s for s in self.subs if s.filter in {'B', 'Ha'}]) > 0: while self.subs[0].filter not in {'B', 'Ha'}: np.random.shuffle(self.subs) self.selected_sub = -1 Clock.schedule_once(self._reprocess_sub, 0)
def adjust_lum(self, *args): ''' User has changed control position so generate luminance and either display it (if sub or in mono mode) or advise multispectral of the update ''' lum = self.luminance() if self.stacker.sub_or_stack == "sub": self.view.display_image(lum) else: if self.stacker.spectral_mode == "mono": self.view.display_image(lum) else: Component.get("MultiSpectral").luminance_updated(lum)
def update_panel(self): filters = Component.get('CaptureScript').get_filters() for f, but in self.buts.items(): but.state = 'down' if f in filters else 'normal' nsubs = Component.get('CaptureScript').get_nsubs() if nsubs > 1: self.nsubs_slider.value = nsubs self.nsubs_label.text = '{:} subs/filter'.format(nsubs) self.nsubs = nsubs self.title.text = 'Select filters' self.nsubs_box.disabled = False else: self.title.text = 'Select a filter' self.nsubs_box.disabled = True
def on_current_script(self, *args): ''' carry out any special actions when certain scripts are selected ''' logger.debug('Changed script to {:}'.format(self.current_script)) self.app.gui.set('script_button', self.current_script) self.update() if self.current_script == 'align': Component.get('View').fit_to_window(zero_orientation=False) self.app.gui.set('show_reticle', self.current_script == 'align', update_property=True) self.app.gui.set('80' if self.current_script == 'flat' else 'mean', True, update_property=True)
def stack_changed(self): ''' Called whenever we might need to update the stack e.g. sub added/selected deselected/change in stack view/change in mode ''' if self.is_empty(): return if self.sub_or_stack == 'sub': return # see if we can satisfy user's non-mono preferences and if not, drop thru to mono # filters = self.get_filters() if self.spectral_mode == 'LRGB': Component.get('MultiSpectral').LRGB_changed(L=self.get_stack('L'), R=self.get_stack('R'), G=self.get_stack('G'), B=self.get_stack('B')) elif self.spectral_mode == 'L+': Component.get('MultiSpectral').L_plus_changed( L=self.get_stack('L'), layer=self.get_stack(self.L_plus_other)) else: Component.get('Monochrome').L_changed(self.get_stack(filt='all')) Component.get('MultiSpectral').reset() self.update_status()
def stop_capture(self, message=None): # stops capture normally or abnormally logger.debug('stopping capture') Component.get('Camera').stop_capture() self.gui.set('capturing', False, update_property=True) self.gui.enable(capture_controls) self.gui.enable({'new_DSO'}) if Component.get('Stacker').is_empty(): self.gui.enable({'load_previous'}) if message is not None: Component.get('CaptureScript').reset_generator() logger.error('problem capturing ({:})'.format(message)) toast('Capture problem: {:}'.format(message)) self.info('')
def luminance_updated(self, lum): # called from monochrome if user updates luminance control e.g. white self.lum = lum # we are in LRGB mode if self.LAB is not None: self.create_RGB() # we are in L+ mode elif self.layer is not None: self.layer_sat_changed() # display as mono else: Component.get('View').display_image(lum)
def new_sub(self, data=None, name=None, exposure=None, filt=None, temperature=None, sub_type=None): ''' Called by Capture or WatchedCamera ''' if data is None or name is None: return logger.debug( 'New sub | type {:} name {:} expo {:} filt {:} temp {:}'.format( sub_type, name, exposure, filt, temperature)) stacker = Component.get('Stacker') if self.current_object_dir is None: # new object, so check if calibration or light if sub_type in {'dark', 'flat', 'bias'}: self.current_object_dir = os.path.join( self.session_dir, generate_observation_name(self.session_dir, prefix=sub_type)) stacker.sub_type = sub_type # is this needed any more? Component.get('DSO').Name = sub_type else: self.current_object_dir = os.path.join( self.session_dir, generate_observation_name( self.session_dir, prefix=Component.get('DSO').Name)) self.app.gui.disable(['load_previous']) add_if_not_exists(self.current_object_dir) path = os.path.join(self.current_object_dir, name) try: save_image(data=data, path=path, exposure=exposure, filt=filt, temperature=temperature, sub_type=sub_type) stacker.add_sub(Image(path)) except Exception as e: logger.exception('cannot add sub to stack ({:})'.format(e))
def update_stack_scroller(self, *args): # sub_labels is array representing the stack # these are sub positions on the screen self.update_status() gui = self.app.gui.gui labs = [ gui[n]['widget'] for n in ['sub_m2', 'sub_m1', 'sub_0', 'sub_p1', 'sub_p2'] ] # set background to transparent and text to blank to start with for l in labs: l.text = '' l.background_color[-1] = 0 # we have some subs fw = Component.get('FilterChooser') for i, l in enumerate(labs, start=self.selected_sub - 2): # if position is occupied by a sub if (i >= 0) and (i < len(self.subs)): l.text = str(i + 1) l.color = self.sub_colors[self.subs[i].status] l.background_color = fw.filter_properties[ self.subs[i].filter]['bg_color']
def on_selected_sub(self, *args): # This is the key method that dictates whether the display has changed # on new sub, change of sub selection etc and hence stack updates s = self.selected_sub if (s < 0) or (s >= len(self.subs)): return if self.sub_or_stack == 'sub': ss = self.subs[s] Component.get('Monochrome').display_sub( ss.get_image(), do_gradient=ss.sub_type == 'light') else: self.stack_changed() self.update_stack_scroller()
def get_dark(self, sub): # Find suitable dark for this sub given its parameters if sub.exposure is None: return None # choose darks that are the right shape with exposure within tolerance darks = { k: v for k, v in self.masters.items() if v.shape == sub.shape and v.sub_type == 'dark' and v.exposure is not None and abs(v.exposure - sub.exposure) < self.exposure_tol } temperature = Component.get('Session').temperature if temperature is not None: # we know temperature, select those with temperatures and within tolerance darks = [ k for k, v in darks.items() if v.temperature is not None and abs(v.temperature - temperature) < self.temperature_tol ] else: # find those within date tolerance (set to 1 to get darks in current session) darks = [k for k, v in darks.items() if v.age < self.dark_days_tol] # if we have darks, return name of first one return darks[0] if len(darks) > 0 else None
def on_action(self, name, *args): ''' Intercept any GUI event in order to load component, before passing action on to component via a property change ''' spec = self.gui[name] comp = spec['component'] c = Component.get(comp) # loads if needed if name in self.disabled_controls: return # if action is specified, simply call that method on component if 'action' in spec: getattr(c, spec['action'])() return # otherwise it involves a value change control = spec['control_type'] # handle button groups if control == 'JToggleButton': if 'group' in spec: setattr(c, spec['group'], spec['widget'].text) return if hasattr(c, name): if control == 'JToggleButton': setattr(c, name, spec['widget'].state == 'down') elif control == 'JLever': setattr(c, name, spec['widget'].value) elif control == 'JMulti': setattr(c, name, spec['widget'].text)
def compute_ADU(self, expo): ''' Estimate ADU in central part of image given exposure ''' im = Component.get('Camera').capture_sub(exposure=expo, return_image=True, on_failure=self.stop_capture) return image_stats(im)['central 75%']
def __init__(self, **kwargs): super().__init__(**kwargs) self.app = App.get_running_app() otypes = Component.get('Catalogues').get_object_types() self.otypes = {k: v['name'] for k, v in otypes.items()} self.dso_panel = DSO_panel(self) self.app.gui.add_widget(self.dso_panel) self.initial_values = {}