コード例 #1
0
ファイル: watchedcamera.py プロジェクト: MartinCooke/jocular
    def watch(self, dt):
        ''' Monitor watched directory.
		'''

        for path in self.get_possible_fits():
            if self.capturing:

                try:
                    s = Image(path, check_image_data=True)
                except ImageNotReadyException as e:
                    # give it another chance on next event cycle
                    logger.debug('image not ready {:} ({:})'.format(path, e))
                except Exception as e:
                    logger.exception('other issue {:} ({:})'.format(path, e))
                    toast('Invalid fits file')
                    move_to_dir(path, 'invalid')

                try:
                    self.process_sub(s, path)
                except Exception as e:
                    logger.exception('error processing sub ({:})'.format(e))
                    toast('error processing sub')
                    move_to_dir(path, 'invalid')
            else:
                move_to_dir(path, 'unused')
コード例 #2
0
ファイル: telescope.py プロジェクト: MartinCooke/jocular
	def slew(self, RA=None, Dec=None):
		if self.connected():
			# get RA/Dec from DSO
			RA, Dec = Component.get('DSO').current_object_coordinates()
			if RA is not None:
				self.device.slew(RA=RA, Dec=Dec)
			else:
				toast('Cannot slew without coordinates')
コード例 #3
0
 def change_filter(self, name, *args):
     try:
         self.device.select_position(position=self.filtermap()[name],
                                     name=name,
                                     success_action=partial(
                                         self.fw_changed, name),
                                     failure_action=self.fw_not_changed)
     except Exception as e:
         toast('problem moving EFW ({:})'.format(e))
         if self.not_changed_action is not None:
             self.not_changed_action()
         return
コード例 #4
0
 def save_notes(self, *args):
     try:
         ol = {}
         for v in self.objects.values():
             if v.get('Notes', '').strip():
                 ol[v['Name']] = v['Notes']
         #ol = {v['Name']: v['Notes'] for v in self.objects.values() if v['Notes'].strip() != ''}
         with open(self.app.get_path('observing_notes.json'), 'w') as f:
             json.dump(ol, f, indent=1)
     except Exception as e:
         logger.exception('problem saving ({:})'.format(e))
         toast('problem saving observing notes')
コード例 #5
0
ファイル: snapshotter.py プロジェクト: MartinCooke/jocular
 def save(self, im):
     # save, handling any format conversions
     try:
         if self.save_format == 'png':
             im.save(self.save_path)
         elif self.save_format == 'jpg':
             bg = Image.new('RGB', im.size, (255, 255, 255))
             bg.paste(im, im)
             bg.save(self.save_path)
         toast('saved to {:}'.format(self.save_path), duration=2)
     except Exception as e:
         logger.exception('problem with _save ({:})'.format(e))
         toast('error saving ({:})'.format(e), duration=2)
コード例 #6
0
ファイル: capture.py プロジェクト: MartinCooke/jocular
 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('')
コード例 #7
0
    def select_filter(self,
                      name='L',
                      changed_action=None,
                      not_changed_action=None):

        logger.debug('trying to change to filter {:}'.format(name))
        self.changed_action = changed_action
        self.not_changed_action = not_changed_action

        # no change of filter
        if self.current_filter == name:
            logger.debug('no need to change filter {:}'.format(name))
            changed_action()
            return

        # filter not in wheel
        if name not in self.filtermap():
            # if we want dark or we currently have dark, special case
            if name == 'dark' or self.current_filter == 'dark':
                if name == 'dark':
                    title = 'Is scope capped for darks?'
                else:
                    title = 'Is scope uncapped for lights?'
                self.dialog = MDDialog(
                    auto_dismiss=False,
                    text=title,
                    buttons=[
                        MDFlatButton(
                            text="DONE",
                            text_color=self.app.theme_cls.primary_color,
                            on_press=partial(self.confirmed_fw_changed, name)),
                        MDFlatButton(
                            text="CANCEL",
                            text_color=self.app.theme_cls.primary_color,
                            on_press=self.confirmed_fw_not_changed)
                    ],
                )
                self.dialog.open()

            else:
                toast('Cannot find filter {:}'.format(name))
                logger.warning(
                    'Cannot find filter {:} in current filterwheel'.format(
                        name))
                if not_changed_action is not None:
                    not_changed_action()
        else:
            # if we get here, we can go ahead
            self.change_filter(name)
コード例 #8
0
ファイル: snapshotter.py プロジェクト: MartinCooke/jocular
 def save_as_fits(self):
     stacker = Component.get('Stacker')
     im = stacker.get_stack()
     if im is not None:
         im = im * (2**16 - 1)
         logger.debug('min {:} max {:}'.format(np.min(im), np.max(im)))
         save_image(data=im.astype(np.uint16),
                    path=self.save_path,
                    exposure=stacker.describe().get('total_exposure', None),
                    sub_type=unique_member(
                        [s.sub_type for s in stacker.subs]),
                    filt=unique_member([s.filter for s in stacker.subs]))
         toast('Saved fits to {:}'.format(os.path.basename(self.save_path)),
               duration=2.5)
     else:
         toast('no image to save')
コード例 #9
0
 def save_observing_list(self, *args):
     try:
         ''' bug here: if self.objects is changed (with new objects) then
             it won't have Added field. Temporary fix but needs migrationt
             to Catalogues
         '''
         ol = {}
         for v in self.objects.values():
             if v.get('Added', ''):
                 ol[v['Name']] = v['Added']
         # ol = {v['Name']: v['Added'] for v in self.objects.values() if v['Added'] != ''}
         with open(self.app.get_path('observing_list.json'), 'w') as f:
             json.dump(ol, f, indent=1)
     except Exception as e:
         logger.exception('problem saving ({:})'.format(e))
         toast('problem saving observing list')
コード例 #10
0
ファイル: capture.py プロジェクト: MartinCooke/jocular
    def save_capture(self, *args):
        ''' Called via camera when image is ready.
        '''

        self.fps = 1 / (time.time() - self.start_capture_time)

        # temporarily added these 2 lines while sorting out camera stop issue
        # but think this thru as it might mean last thing doesn't get saved
        #if not self.capturing:
        #    return

        im = Component.get('Camera').get_image()

        if im is None:
            toast('No image to save')
            return

        # we'll save as 16-bit int FITs
        im *= (2**16 - 1)

        # details = self.get_capture_details()

        if not hasattr(self, 'series_number') or self.series_number is None:
            self.series_number = 1
        else:
            self.series_number += 1

        sub_type = Component.get('CaptureScript').get_sub_type()
        filt = Component.get('FilterWheel').current_filter
        pref = sub_type if sub_type in {'flat', 'dark'} else filt
        name = '{:}_{:d}.fit'.format(pref, self.series_number)

        Component.get('ObjectIO').new_sub(
            data=im.astype(np.uint16),
            name=name,
            exposure=self.exposure,
            filt=filt,
            temperature=Component.get('Session').temperature,
            sub_type=sub_type)

        # ask for next capture immediately
        self.capture()
コード例 #11
0
ファイル: stacker.py プロジェクト: MartinCooke/jocular
    def add_sub(self, sub):
        ''' called by ObjectIO.new_sub
        '''
        ''' check if sub is the same dims as existing stack, otherwise don't add; note
            dims are in reverse order to those of the numpy array
        '''
        if not self.is_empty():
            width, height = self.subs[0].image.shape
            if sub.shape[1] != width or sub.shape[0] != height:
                msg = 'sub dimensions {:} x {:} incompatible with those of current stack {:}'.format(
                    width, height, self.subs[0].get_image().shape)
                toast(msg)
                logger.error(msg)
                return

        self.process(sub)
        self.subs.append(sub)
        self.sub_added()

        self.app.gui.has_changed('Stacker', not self.is_empty())
        ''' I don't think we need to set the details here because it is done
コード例 #12
0
ファイル: capture.py プロジェクト: MartinCooke/jocular
    def get_flat_exposure(self):
        # make a series of test exposures to get ADU just over half-way (0.5)

        logger.info('doing autoflat')

        min_exposure, max_exposure = 1, 2.5
        min_ADU, max_ADU = .3, .8

        expos = np.linspace(min_exposure, max_exposure, 5)
        adus = np.ones(len(expos))  # normalised ADUs actually

        # do shortest first in case no point
        adus[0] = self.compute_ADU(expos[0])
        if adus[0] > max_ADU:
            toast('Too early to collect flats')
            return None

        # do longest in case too late
        adus[-1] = self.compute_ADU(expos[-1])
        if adus[-1] < min_ADU:
            toast('Too late to collect flats')
            return None

        # do remaining ADUs
        adus[1:-1] = [self.compute_ADU(e) for e in expos[1:-1]]

        # interpolate to get things purrrrfect
        f = interp1d(expos, adus)
        adu_target = .7
        xvals = np.linspace(min_exposure, max_exposure, 500)
        best = np.argmin(np.abs(adu_target - f(xvals)))
        best_exposure = xvals[best]
        mesg = 'best exposure for autoflat {:} with ADU {:}'.format(
            best_exposure, f(best_exposure))
        toast(mesg)
        logger.info(mesg)
        return float(best_exposure)
コード例 #13
0
    def load(self, dt):

        # load DSOs
        try:
            ''' self.objects is not a copy, so when user objects are added
                in Catalogues.update_user_catalogue, they are also added
                to self.objects. This causes issues because new objects
                dont have all the augmented properties like Added let alone
                transit information. 

                Should migrate some of the functionality
                of ObservingList and Catalogues to make it easier
                to maintain consistency. In fact, all the lookups should
                be done via Catalogues and not via ObservingList
            '''

            self.objects = Component.get('Catalogues').get_basic_dsos()
        except Exception as e:
            logger.exception('problem loading DSOs ({:})'.format(e))
            toast('problem loading DSOs')
            self.objects = {}

        # load observing list (maps names to date added)
        try:
            with open(self.app.get_path('observing_list.json'), 'r') as f:
                observing_list = json.load(f)
        except:
            observing_list = {}
        logger.info('{:} on observing list'.format(len(observing_list)))

        # load observing notes
        try:
            with open(self.app.get_path('observing_notes.json'), 'r') as f:
                observing_notes = json.load(f)
        except:
            observing_notes = {}
        logger.info('{:} observing notes'.format(len(observing_notes)))

        # load previous observations if necc and count previous
        try:
            obs = Component.get('Observations').get_observations()
            previous = dict(
                Counter(
                    [v['Name'].lower() for v in obs.values() if 'Name' in v]))
        except Exception as e:
            logger.warning('problem loading previous DSOs ({:})'.format(e))
            previous = {}
        logger.info('{:} unique previous observations found'.format(
            len(previous)))

        # augment/combine DSO info
        for v in self.objects.values():
            name = v['Name']
            v['Obs'] = previous.get(name.lower(), 0)
            v['Added'] = observing_list.get(name, '')
            v['List'] = 'Y' if name in observing_list else 'N'
            v['Notes'] = observing_notes.get(name, '')
            v['Other'] = v.get('Other', '')

        try:
            self.compute_transits()
        except Exception as e:
            logger.exception('problem computing transits {:}'.format(e))
コード例 #14
0
ファイル: snapshotter.py プロジェクト: MartinCooke/jocular
    def eyepiece_view(self):

        ox, oy = Metrics.get('origin')
        radius = Metrics.get('radius')

        # thanks to callump for the use of scrn_density
        scrn_density = KivyMetrics.density
        cx = int(Window.left + ox / scrn_density)
        cy = int(Window.top + Window.height / scrn_density - oy / scrn_density)
        r = int(radius / scrn_density - 40)
        w = 2 * r

        try:
            im = self.mss_grabber(bbox=(cx - r, cy - r, cx + r, cy + r))
        except Exception as e:
            logger.exception('problem with screen grab ({:})'.format(e))
            toast('screen grab error ({:})'.format(e))
            return

        blur = 17  # radius of blurred edge in pixels

        # new from callump
        w = im.width
        r = w / 2

        # construct alpha channel to composite eyepiece circle
        # quite likely some of these steps are redundant
        alpha = Image.new('L', (w, w), color=0)
        _alpha = np.ones((w, w))
        grid_x, grid_y = np.meshgrid(np.arange(w), np.arange(w))
        d = pow((grid_x - r)**2 + (grid_y - r)**2, .5) - (r - 30)
        db = d <= blur
        _alpha[db] = d[db] / blur
        alpha.putdata(_alpha.ravel(), scale=255)
        if self.dark_surround:
            grad_im = Image.new('RGBA', (w, w), color=(0, 0, 0))
        else:
            grad_im = Image.new('RGBA', (w, w), color=(255, 255, 255))
        grad_im.putalpha(alpha)

        # composite with scope image, converting if needed (to test)
        try:
            if im.mode != 'RGBA':
                im = im.convert('RGBA')
            im = Image.alpha_composite(im, grad_im)
        except Exception as e:
            logger.exception('problem compositing ({:})'.format(e))
            toast('compositing error ({:})'.format(e))
            return

        # annotate Draw object with required level of detail
        sf, sf_size, hh = self.small_font(w)
        lf, lf_size, hh = self.large_font(w)
        rowsep = sf_size + 6
        if self.annotation != 'plain':
            draw = ImageDraw.Draw(im)
            w, h = im.size
            row = self.draw_aligned(draw,
                                    0,
                                    0,
                                    w,
                                    Component.get('DSO').Name,
                                    lf,
                                    align='left',
                                    rowsep=lf_size + 30)

        if self.annotation == 'full':
            self.draw_block(self.DSO_details(),
                            draw,
                            row,
                            0,
                            w,
                            sf,
                            align='left',
                            rowsep=rowsep)
            self.draw_block(self.processing_details()[::-1],
                            draw,
                            h - 1.5 * rowsep,
                            0,
                            w,
                            sf,
                            align='right',
                            rowsep=-rowsep)
            self.draw_block(self.kit_details(),
                            draw,
                            h - 1.5 * rowsep,
                            0,
                            w,
                            sf,
                            align='left',
                            rowsep=-rowsep)  # neg builds from base
            self.draw_block(self.session_details(),
                            draw,
                            5,
                            0,
                            w,
                            sf,
                            align='right',
                            rowsep=rowsep)

        # for high density screens, reduce size a little
        im = im.resize((int(im.width / self.imreduction),
                        int(im.height / self.imreduction)))

        if self.return_image:
            return im

        self.save(im)
コード例 #15
0
 def rebuild_table(self, dt=None):
     self.observations_table.data = self.get_observations()         
     self.observations_table.update()
     toast('rebuilt!')
コード例 #16
0
ファイル: snapshotter.py プロジェクト: MartinCooke/jocular
    def landscape_view(self):

        # to do: better scaling of fonts based on image size

        # v0.5: ensure tmp is in snapshots dir now that user can start from anywhere
        nm = os.path.join(self.app.get_path('snapshots'), '_tmp.png')
        im = Component.get('View').last_image.copy()
        try:
            imsave(nm, im[::-1])
            orig_im = Image.open(nm)
        except Exception as e:
            logger.exception('load/save problem ({:})'.format(e))
            toast('load/save error ({:})'.format(e))
            return

        w, h = orig_im.size

        # NB this is indep of screen size, unlike eyepiece view
        sf, sf_size, small_text_height = self.small_font(w)
        lf, lf_size, large_text_height = self.large_font(w)

        rowgap = small_text_height / 2
        obj_details = self.DSO_details()
        proc_details = self.processing_details()
        sesh_details = self.session_details() + self.kit_details()
        max_rows = max(
            [len(obj_details),
             len(proc_details),
             len(sesh_details)])
        total_height = h
        if self.annotation != 'plain':
            total_height = total_height + 2 * rowgap + large_text_height
        if self.annotation == 'full':
            total_height = int(total_height + max_rows * small_text_height +
                               (max_rows - 1) * rowgap)

        im = Image.new(
            'RGBA',
            (w + 2 * self.framewidth, total_height + 2 * self.framewidth),
            color=(0, 0, 0) if self.dark_surround else (255, 255, 255))

        im.paste(orig_im, (self.framewidth, self.framewidth))
        draw = ImageDraw.Draw(im)

        w += 2 * self.framewidth
        h += 2 * self.framewidth

        # move row base to base of text
        w += large_text_height

        if self.annotation != 'plain':
            row = self.draw_aligned(draw,
                                    h,
                                    0,
                                    w,
                                    Component.get('DSO').Name,
                                    lf,
                                    align='center',
                                    rowsep=large_text_height + 2 * rowgap)

        if self.annotation == 'full':
            row += rowgap
            draw.line((5, row, w - 5, row), fill=self.grey(), width=1)
            row += (rowgap + small_text_height)
            cols = [int(x) for x in orig_im.size[0] * np.linspace(0, 1, 4)]
            self.draw_block(sesh_details,
                            draw,
                            row,
                            cols[0],
                            cols[1],
                            sf,
                            align='left',
                            rowsep=small_text_height + rowgap)
            self.draw_block(obj_details,
                            draw,
                            row,
                            cols[1],
                            cols[2],
                            sf,
                            align='center',
                            rowsep=small_text_height + rowgap)
            self.draw_block(proc_details,
                            draw,
                            row,
                            cols[2],
                            cols[3],
                            sf,
                            align='right',
                            rowsep=small_text_height + rowgap)

        # delete temporary file and save
        os.remove(nm)

        if self.return_image:
            return im

        self.save(im)
コード例 #17
0
    def do_save(self, content, *args):

        self.dialog.dismiss()

        metadata = Component.get('Metadata')
        subs = Component.get('Stacker').subs

        # save master if requested
        if content.save_master:
            Component.get('Calibrator').create_master(
                exposure=content.exposure,
                temperature=content.temperature,
                filt=unique_member([s.filter for s in subs]),
                sub_type=content.sub_type)

        # save info.json, handle rejects, and update observations table

        metadata.set({
            'exposure': content.exposure,
            'sub_type': content.sub_type,
            'temperature': content.temperature
        })

        oldpath = self.current_object_dir
        Component.save_object()

        # check if name has been changed
        name = metadata.get('Name', default='')
        if name:
            session_dir, object_dir = os.path.split(self.current_object_dir)
            if name != object_dir:
                # user has changed name, so generate a (unique) new folder name
                new_name = generate_observation_name(session_dir, prefix=name)
                # change directory name; any problem, don't bother
                try:
                    os.rename(self.current_object_dir,
                              os.path.join(session_dir, new_name))
                    self.current_object_dir = os.path.join(
                        session_dir, new_name)
                except Exception as e:
                    logger.exception('cannot change name ({:})'.format(e))

        # save metadata, and if successful update observations
        newpath = self.current_object_dir
        try:
            ''' kludge for now: temperature at least may have been changed by session 
                during save_object so ensure metadata is reset with these new values
            '''
            metadata.set({
                'exposure': content.exposure,
                'sub_type': content.sub_type,
                'temperature': content.temperature
            })
            metadata.save(newpath,
                          change_fits_headers=content.change_fits_headers)
            Component.get('Observations').update(oldpath, newpath)
            Component.get('ObservingList').new_observation()
            toast('Saved to {:}'.format(newpath))
        except Exception as e:
            logger.exception('OSError saving info3.json to {:} ({:})'.format(
                newpath, e))

        self.new_object()
コード例 #18
0
ファイル: telescope.py プロジェクト: MartinCooke/jocular
	def slew(self, RA=None, Dec=None):
		toast('Slewing not possible for manual scope')