Пример #1
0
    def set_focus_area(self):
        """
        The user may select a place on the moon where lighting conditions are optimal for setting
        the focus. This method stores the current location, so the telescope can be moved to it
        later to update the focus setting.

        The user may opt to focus on a star rather than on a moon feature (by setting the
        "focus on star" configuration parameter). In this case the focus position does not move
        with the moon among the stars. Therefore, rather than computing the center offset in this
        case the RA,DE coordinates are used directly.

        :return: -
        """

        if not self.is_aligned:
            if self.configuration.protocol_level > 0:
                Miscellaneous.protocol(
                    "Internal error: Attempt to set focus area without alignment."
                )
            raise RuntimeError("Cannot set focus area without alignment.")
        # Look up the current position of the telescope mount.
        (ra_focus, de_focus) = self.tel.lookup_tel_position()
        # Translate telescope position into true (RA,DE) coordinates
        (self.true_ra_focus,
         self.true_de_focus) = (self.telescope_to_ephemeris_coordinates(
             ra_focus, de_focus))
        if not self.configuration.conf.getboolean("Workflow", "focus on star"):
            # Compute current moon position and displacement of telescope position relative to moon
            # center.
            self.me.update(datetime.now())
            self.ra_offset_focus_area = self.true_ra_focus - self.me.ra
            self.de_offset_focus_area = self.true_de_focus - self.me.de
Пример #2
0
    def set_focus_area(self):
        """
        The user may select a place on the moon where lighting conditions are optimal for setting
        the focus. This method stores the current location, so the telescope can be moved to it
        later to update the focus setting.

        The user may opt to focus on a star rather than on a moon feature (by setting the
        "focus on star" configuration parameter). In this case the focus position does not move
        with the moon among the stars. Therefore, rather than computing the center offset in this
        case the RA,DE coordinates are used directly.

        :return: -
        """

        if not self.is_aligned:
            if self.configuration.protocol_level > 0:
                Miscellaneous.protocol(
                    "Internal error: Attempt to set focus area without alignment.")
            raise RuntimeError("Cannot set focus area without alignment.")
        # Look up the current position of the telescope mount.
        (ra_focus, de_focus) = self.tel.lookup_tel_position()
        # Translate telescope position into true (RA,DE) coordinates
        (self.true_ra_focus, self.true_de_focus) = (
            self.telescope_to_ephemeris_coordinates(ra_focus, de_focus))
        if not self.configuration.conf.getboolean("Workflow", "focus on star"):
            # Compute current moon position and displacement of telescope position relative to moon
            # center.
            self.me.update(datetime.now())
            self.ra_offset_focus_area = self.true_ra_focus - self.me.ra
            self.de_offset_focus_area = self.true_de_focus - self.me.de
Пример #3
0
    def execute_rank_frames(self):

        self.set_status_bar_processing_phase("ranking frames")
        # Rank the frames by their overall local contrast.
        if self.configuration.global_parameters_protocol_level > 0:
            Miscellaneous.protocol("+++ Start ranking images +++",
                                   self.attached_log_file)
        self.my_timer.create_no_check('Ranking images')

        try:
            self.rank_frames = RankFrames(self.frames, self.configuration,
                                          self.work_current_progress_signal)
            self.rank_frames.frame_score()
            self.my_timer.stop('Ranking images')
        except Error as e:
            self.abort_job_signal.emit("Error: " + e.message +
                                       ", continuing with next job")
            self.my_timer.stop('Ranking images')
            return
        except Exception as e:
            self.abort_job_signal.emit("Error: " + str(e) +
                                       ", continuing with next job")
            self.my_timer.stop('Ranking images')
            return

        if self.configuration.global_parameters_protocol_level > 1:
            Miscellaneous.protocol("           Index of best frame: " +
                                   str(self.rank_frames.frame_ranks_max_index),
                                   self.attached_log_file,
                                   precede_with_timestamp=False)

        self.work_next_task_signal.emit("Align frames")
    def compute_landmark_offsets(self, me, landmark):
        """
        Compute offsets in (RA, DE) relative to the moon center for the landmark feature. Take into
        account topocentric parallax and libration.

        :param me: object with positions of the sun and moon, including libration info
        :param landmark: name of the landmark (String)
        :return: offset (radians) in (RA, DE) of landmark relative to moon center
        """

        try:
            # Get selenographic longitude and latitude of landmark.
            longitude = radians(self.landmarks[landmark][0])
            latitude = radians(self.landmarks[landmark][1])
            if self.configuration.protocol_level > 0:
                print("")
                Miscellaneous.protocol("New Landmark selected: " + landmark +
                                       ", selenographic longitude: " +
                                       str(round(degrees(longitude), 3)) +
                                       ", latitude: " +
                                       str(round(degrees(latitude), 3)) + ".")
            # Perform the coordinate transformation and return the offsets (in radians)
            return self.coord_translation(me, longitude, latitude)
        except:
            # This is an internal error and should not occur.
            print("Error in landmark_selection: unknown landmark",
                  file=sys.stderr)
            return 0., 0.
    def compute_landmark_offsets(self, me, landmark):
        """
        Compute offsets in (RA, DE) relative to the moon center for the landmark feature. Take into
        account topocentric parallax and libration.

        :param me: object with positions of the sun and moon, including libration info
        :param landmark: name of the landmark (String)
        :return: offset (radians) in (RA, DE) of landmark relative to moon center
        """

        try:
            # Get selenographic longitude and latitude of landmark.
            longitude = radians(self.landmarks[landmark][0])
            latitude = radians(self.landmarks[landmark][1])
            if self.configuration.protocol_level > 0:
                print("")
                Miscellaneous.protocol(
                    "New Landmark selected: " + landmark + ", selenographic longitude: " + str(
                        round(degrees(longitude), 3)) + ", latitude: " + str(
                        round(degrees(latitude), 3)) + ".")
            # Perform the coordinate transformation and return the offsets (in radians)
            return self.coord_translation(me, longitude, latitude)
        except:
            # This is an internal error and should not occur.
            print("Error in landmark_selection: unknown landmark", file=sys.stderr)
            return 0., 0.
Пример #6
0
    def compute_coordinate_correction(self):
        """
        Compute the current difference between telescope and celestial coordinates, including
        alignment and (if available) drift rate.

        :return: telescope - celestial coordinates: (Offset in RA, Offset in DE)
        """

        # If an alignment has been performed, compute offsets in RA and DE.
        if self.is_aligned:
            # Set correction to static value from last alignment
            ra_offset = self.ra_correction
            de_offset = self.de_correction
            # In case drift has been determined, add time-dependent correction
            # term
            if self.is_drift_set:
                now = datetime.now()
                try:
                    fract = float(str(now)[19:24])
                except:
                    fract = 0.
                # Compute time in seconds since last alignment.
                time_diff = (time.mktime(now.timetuple()) + fract -
                             self.alignment_time)
                ra_offset += time_diff * self.drift_ra
                de_offset += time_diff * self.drift_de
        # Before the first alignment, set offsets to zero and print a warning to stdout.
        else:
            if self.configuration.protocol_level > 2:
                Miscellaneous.protocol(
                    "Info: I will apply zero coordinate correction before "
                    "alignment.")
            ra_offset = 0.
            de_offset = 0.
        return ra_offset, de_offset
    def done(self):
        """
        On exit from the frame viewer, update the stack frame size and send a completion signal.

        :return: -
        """

        # Check if a new stack size was selected.
        if (self.configuration.alignment_points_frame_number !=
                self.spinBox_number_frames.value() and
                self.configuration.alignment_points_frame_number is not None) or \
                self.configuration.alignment_points_frame_percent != \
                self.spinBox_percentage_frames.value():
            # Save the (potentially changed) stack size.
            self.configuration.alignment_points_frame_percent = \
                self.spinBox_percentage_frames.value()
            self.configuration.alignment_points_frame_number = self.spinBox_number_frames.value()

            # Write the stack size change into the protocol.
            if self.configuration.global_parameters_protocol_level > 1:
                Miscellaneous.protocol("           The user has selected a new stack size: " +
                    str(self.configuration.alignment_points_frame_number) + " frames (" +
                    str(self.configuration.alignment_points_frame_percent) + "% of all frames).",
                    self.stacked_image_log_file, precede_with_timestamp=False)

        # Send a completion message.
        if self.parent_gui is not None:
            self.signal_finished.emit(self.signal_payload)

        # Close the Window.
        plt.close()
        self.close()
Пример #8
0
    def normalize_and_analyze_image(self, image_array, filename_appendix):
        """
        For an image array (as produced by the camera), optimize brightness and contrast. Store the
        image in the reference image directory. Then use ORB for keypoint detection and descriptor
        computation.

        :param image_array: Numpy array with image as produced by the camera object.
        :param filename_appendix: String to be appended to filename. The filename begins with
        the current time (hours, minutes, seconds) for later reference.
        :return: tuple with four objects: the normalized image array, the image object, the
        keypoints, and the keypoint descriptors.
        """

        # height, width = image_array.shape[:2]

        # Optimize the contrast in the image.
        normalized_image_array = self.clahe.apply(image_array)

        # Version for tests: use image (already normalized) stored at last session.
        # normalized_image_array = image_array

        # Build the normalized image from the luminance channel.
        normalized_image = fromarray(normalized_image_array, 'L')
        # Write the normalized image to disk.
        normalized_image_filename = self.build_filename() + filename_appendix
        normalized_image.save(normalized_image_filename)
        if self.configuration.protocol_level > 2:
            Miscellaneous.protocol("Still image '" + filename_appendix +
                                   " captured for auto-alignment.")
        # Use the ORB for keypoint detection
        normalized_image_kp = self.orb.detect(normalized_image_array, None)
        # Compute the descriptors with ORB
        normalized_image_kp, normalized_image_des = self.orb.compute(normalized_image_array,
                                                                     normalized_image_kp)
        return normalized_image_array, normalized_image, normalized_image_kp, normalized_image_des
Пример #9
0
    def center_offset_to_telescope_coordinates(self, delta_ra, delta_de):
        """
        Translate offset angles relative to moon center into equatorial coordinates (RA, DE) in
        the coordinate system of the telescope mount.

        :param delta_ra: Center offset angle in ra
        :param delta_de: Center offset angle in de
        :return: Equatorial telescope mount coordinates (RA, DE)
        """

        # Compute current position of the moon.
        self.me.update(datetime.now())
        if self.configuration.protocol_level > 2:
            Miscellaneous.protocol(
                "Translating center offset to equatorial coordinates, "
                "center offsets: RA: " + str(round(degrees(delta_ra), 5)) +
                ", DE: " + str(round(degrees(delta_de), 5)) +
                ", moon position (center): RA: " +
                str(round(degrees(self.me.ra), 5)) + ", DE: " +
                str(round(degrees(self.me.de), 5)) + " (all in degrees).")
        # Add the offset to moon center coordinates.
        ra = self.me.ra + delta_ra
        de = self.me.de + delta_de
        # Translate coordinates into telescope system
        return self.ephemeris_to_telescope_coordinates(ra, de)
Пример #10
0
    def compute_coordinate_correction(self):
        """
        Compute the current difference between telescope and celestial coordinates, including
        alignment and (if available) drift rate.

        :return: telescope - celestial coordinates: (Offset in RA, Offset in DE)
        """

        # If an alignment has been performed, compute offsets in RA and DE.
        if self.is_aligned:
            # Set correction to static value from last alignment
            ra_offset = self.ra_correction
            de_offset = self.de_correction
            # In case drift has been determined, add time-dependent correction
            # term
            if self.is_drift_set:
                now = datetime.now()
                try:
                    fract = float(str(now)[19:24])
                except:
                    fract = 0.
                # Compute time in seconds since last alignment.
                time_diff = (time.mktime(now.timetuple()) + fract - self.alignment_time)
                ra_offset += time_diff * self.drift_ra
                de_offset += time_diff * self.drift_de
        # Before the first alignment, set offsets to zero and print a warning to stdout.
        else:
            if self.configuration.protocol_level > 2:
                Miscellaneous.protocol("Info: I will apply zero coordinate correction before "
                                       "alignment.")
            ra_offset = 0.
            de_offset = 0.
        return ra_offset, de_offset
Пример #11
0
    def recompute_selected_version(input_image, layers):
        """
        Do the actual computation. Starting from the original image, for each sharpening layer
        apply a Gaussian filter using the layer's parameters. Store the result in the central
        data object for the selected version.

        :return: -
        """

        # Initialize the new image with the original image.
        new_image = input_image

        # Apply all sharpening layers. If the amount is positive, sharpen the image. A negative
        # amount between -1 and 0 means that the image is to be softened with a Gaussian kernel.
        # In this case If the sign of the amount is reversed and taken as the weight with which the
        # softened image is mixed with the original one.
        for layer in layers:
            if layer.amount > 0.:
                new_image = Miscellaneous.gaussian_sharpen(
                    new_image,
                    layer.amount,
                    layer.radius,
                    luminance_only=layer.luminance_only)
            elif -1. <= layer.amount < 0.:
                new_image = Miscellaneous.gaussian_blur(
                    new_image,
                    -layer.amount,
                    layer.radius,
                    luminance_only=layer.luminance_only)

        # Store the result in the central data object.
        return new_image
Пример #12
0
    def execute_compute_frame_qualities(self):

        if self.configuration.global_parameters_protocol_level > 1:
            Miscellaneous.protocol(
                "           Number of alignment points selected: " +
                str(len(self.alignment_points.alignment_points)) +
                ", aps dropped because too dim: " +
                str(self.alignment_points.alignment_points_dropped_dim) +
                ", aps dropped because too little structure: " +
                str(self.alignment_points.alignment_points_dropped_structure),
                self.attached_log_file,
                precede_with_timestamp=False)

        self.set_status_bar_processing_phase(
            "ranking all frames at all alignment points")
        # For each alignment point rank frames by their quality.
        self.my_timer.create_no_check('Rank frames at alignment points')
        if self.configuration.global_parameters_protocol_level > 0:
            Miscellaneous.protocol(
                "+++ Start ranking all frames at all alignment points +++",
                self.attached_log_file)
        self.alignment_points.compute_frame_qualities()
        self.my_timer.stop('Rank frames at alignment points')

        self.work_next_task_signal.emit("Stack frames")
Пример #13
0
    def execute_save_stacked_image(self):

        self.set_status_bar_processing_phase("saving result")
        # Save the image as 16bit int (color or mono).
        if self.configuration.global_parameters_protocol_level > 0:
            Miscellaneous.protocol("+++ Start saving the stacked image +++",
                                   self.attached_log_file)
        self.my_timer.create_no_check('Saving the stacked image')
        self.frames.save_image(self.stacked_image_name,
                               self.stack_frames.stacked_image,
                               color=self.frames.color,
                               avoid_overwriting=False)
        self.my_timer.stop('Saving the stacked image')
        if self.configuration.global_parameters_protocol_level > 1:
            Miscellaneous.protocol(
                "           The stacked image was written to: " +
                self.stacked_image_name,
                self.attached_log_file,
                precede_with_timestamp=False)

        # If postprocessing is included after stacking, set the stacked image as input.
        if self.configuration.global_parameters_include_postprocessing:
            self.postproc_input_image = self.stack_frames.stacked_image
            self.postproc_input_name = self.stacked_image_name
            self.postprocessed_image_name = PostprocDataObject.set_file_name_processed(
                self.stacked_image_name, self.configuration.postproc_suffix,
                self.configuration.global_parameters_image_format)
            self.work_next_task_signal.emit("Postprocessing")
        else:
            self.work_next_task_signal.emit("Next job")

            # Print timing info for this job.
            self.my_timer.stop('Execution over all')
            if self.configuration.global_parameters_protocol_level > 0:
                self.my_timer.protocol(self.attached_log_file)
Пример #14
0
    def execute_set_alignment_points(self):

        # If not executing in "automatic" mode, the APs are created on the main_gui thread.
        if self.main_gui.automatic:
            self.set_status_bar_processing_phase("creating alignment points")
            # Initialize the AlignmentPoints object.
            self.my_timer.create_no_check('Initialize alignment point object')
            self.alignment_points = AlignmentPoints(
                self.configuration,
                self.frames,
                self.rank_frames,
                self.align_frames,
                progress_signal=self.work_current_progress_signal)
            self.my_timer.stop('Initialize alignment point object')

            # Create alignment points, and create an image with wll alignment point boxes and patches.
            if self.configuration.global_parameters_protocol_level > 0:
                Miscellaneous.protocol(
                    "+++ Start creating alignment points +++",
                    self.attached_log_file)
            self.my_timer.create_no_check('Create alignment points')

            # If a ROI is selected, alignment points are created in the ROI window only.
            self.alignment_points.create_ap_grid()

            self.my_timer.stop('Create alignment points')

        self.work_next_task_signal.emit("Compute frame qualities")
Пример #15
0
    def Execute2D(self, w):
        ##
        ## Input files /Output folder
        ##
        self.filestack = self.ObtainTarget()
        params = self.ObtainParamsBottomTable(self.obj_args, self.args)
        output_path = params['Output Folder']
        if len(output_path) == 0:
            print('Output folder unspecified.')
            return False
        # Unlock Folder
        m.UnlockFolder(self.parent.u_info, output_path)

        for filename in self.filestack:
            print(filename)
            output_name = os.path.basename(filename)
            # input_image = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)

            input_image = m.imread(filename, flags=cv2.IMREAD_GRAYSCALE)
            output_image = self.FilterApplication2D(w, input_image)
            savename = os.path.join(output_path, output_name)
            flag = m.SaveImage(output_image, savename)

        print('2D filters were applied!')
        # Change folder type
        self.parent.parent.ExecuteCloseFileFolder(output_path)
        self.parent.parent.OpenFolder(output_path)
Пример #16
0
    def compute_drift_rate(self):
        """
        Compute the drift rate of the telescope mount, based on two alignment points. By default,
        the first and last alignment point are used. Other indices may have been selected by the
        user.

        :return: -
        """

        self.is_drift_set = False
        if self.drift_disabled:
            return
        time_diff = (self.alignment_points[self.last_index]['time_seconds'] -
                     self.alignment_points[self.first_index]['time_seconds'])
        # Drift is only computed if the time difference of the alignment points is large enough.
        if time_diff < self.configuration.minimum_drift_seconds:
            return
        self.drift_ra = ((self.alignment_points[self.last_index]['ra_correction'] -
                          self.alignment_points[self.first_index]['ra_correction']) / time_diff)
        self.drift_de = ((self.alignment_points[self.last_index]['de_correction'] -
                          self.alignment_points[self.first_index]['de_correction']) / time_diff)
        if self.configuration.protocol_level > 1:
            Miscellaneous.protocol(
                "Drift rate based on alignment points " + str(self.first_index + 1) + " and " + str(
                    self.last_index + 1) + ": Drift in Ra = " + str(
                    round(degrees(self.drift_ra) * 216000., 3)) + ", drift in De = " + str(round(
                    degrees(self.drift_de) * 216000., 3)) + " (in arc min/hour).")
        # Set flag to true to indicate that a valid drift rate has been determined.
        self.is_drift_set = True
Пример #17
0
    def execute_reset_masters(self):

        # De-activate master frames.
        if self.configuration.global_parameters_protocol_level > 0:
            Miscellaneous.protocol("+++ De-activating master frames +++",
                                   self.attached_log_file,
                                   precede_with_timestamp=True)
        self.calibration.reset_masters()
Пример #18
0
    def run(self, u_info):

        ## Load DB
        db = DB(u_info)
        self.u_info = u_info
        ##
        ## Update split and adjust
        ##

        for iz in range(db.num_tiles_z):

            print('Saving: ', iz, '/', db.num_tiles_z)
            # Check teemporary data
            data_path = u_info.tmp_tile_ids_path + u_info.tile_path_wz.format(0, iz)
            if not os.path.isdir(data_path):
                continue

            # print('Copy from ', data_path)
            for iw in range(db.num_tiles_w):
                source_dir = u_info.tmp_tile_ids_path \
                                 + u_info.tile_path_wz.format(iw, iz)
                destination_dir = u_info.tile_ids_path \
                                      + u_info.tile_path_wz.format(iw, iz)
                shutil.rmtree(destination_dir)
                shutil.move(source_dir, destination_dir)

                ## Remove temp file
                # shutil.rmtree(source_dir)

        ##
        ## Update merges
        ##

        # print(u_info.merge_table)

        for iw in range(db.num_tiles_w):
            for iz, iy, ix in itertools.product(range(db.num_tiles_z), range(db.num_tiles_y_at_w[iw]), range(db.num_tiles_x_at_w[iw])):

                ### Load tile file
                tile_ids_filename = u_info.tile_ids_path + u_info.tile_ids_filename_wzyx.format( iw, iz, iy, ix )
                tile_ids = m.load_hdf5( tile_ids_filename, u_info.tile_var_name )

                ## Color exchange [for merge? check __merge_table.keys() ]
                for mm in u_info.merge_table.keys():
                    mm_id = self.lookup_label(mm, u_info.merge_table)
                    tile_ids[ tile_ids == int(mm) ] = mm_id

                ### Save tile file
                    m.save_hdf5(tile_ids_filename, u_info.tile_var_name, tile_ids)

        u_info.merge_table = {}
        u_info.flag_undo = 0
        u_info.flag_redo = 0

        ## Update
        print('Updating database.')
        db.Update()
        print('Successfully saved.')
Пример #19
0
    def Execute3D(self, w):
        ##
        ## Load image
        ##
        filestack = self.ObtainTarget()
        params = self.ObtainParamsBottomTable(self.obj_args, self.args)
        output_path = params['Output Folder']
        if len(output_path) == 0:
            print('Output folder unspecified.')
            return False
        #
        numz = len(filestack)
        # size = cv2.imread(filestack[0], cv2.IMREAD_GRAYSCALE).shape
        check_attribute = m.imread(filestack[0], flags=cv2.IMREAD_GRAYSCALE)
        tsize = check_attribute.shape
        tdtype = check_attribute.dtype
        input_volume = np.zeros([tsize[0], tsize[1], numz], tdtype)

        print('Loading images ...')
        for zi, filename in enumerate(filestack):
            # input_volume[:, :, zi] = cv2.imread(filename, cv2.IMREAD_GRAYSCALE).astype(tdtype)
            input_volume[:, :, zi] = m.imread(filename,
                                              flags=cv2.IMREAD_GRAYSCALE)
        ##
        ## 2D/3D filter application
        ##
        for i in range(w.count()):
            item = w.item(i)
            text = item.text()
            instance = item.data(Qt.UserRole)
            params = self.ObtainParamsFilter(instance.args)
            type = self.fi.get_type(text)
            cls = self.fi.get_class(text)

            if type == '2d':
                for zi in range(numz):
                    input_image = input_volume[:, :, zi]
                    output_image = cls.Filter(self, input_image, params)
                    input_volume[:, :, zi] = output_image
            elif type == '3d':
                tmp = cls.Filter(self, input_volume, params)
                input_volume = tmp.astype(np.uint16)

        # Unlock Folder
        m.UnlockFolder(self.parent.u_info, output_path)
        # Save segmentation
        print('Saving images ...')
        for zi, filename in enumerate(filestack):
            output_name = os.path.basename(filename)
            savename = os.path.join(output_path, output_name)
            print("Save: ", savename)
            flag = m.SaveImage(input_volume[:, :, zi], savename)

        print('2D/3D filters were applied!')
        # Change folder type
        self.parent.parent.ExecuteCloseFileFolder(output_path)
        self.parent.parent.OpenFolder(output_path)
Пример #20
0
    def _Run(self, parent, params, comm_title):

        datadir = parent.u_info.data_path

        input_files = glob.glob(os.path.join(params['Image Folder'], "*.jpg"))
        input_png = glob.glob(os.path.join(params['Image Folder'], "*.png"))
        input_tif = glob.glob(os.path.join(params['Image Folder'], "*.tif"))
        input_files.extend(input_png)
        input_files.extend(input_tif)
        if len(input_files) == 0:
            print('No images in the Image Folder.')
            return

        im = cv2.imread(input_files[0], cv2.IMREAD_UNCHANGED)
        print('Target file to check color type : ', input_files[0])
        print('Image dimensions                : ', im.shape)
        print('Image filetype                  : ', im.dtype)
        image_width  = im.shape[1]
        image_height = im.shape[0]

        if not (im.dtype == "uint8" and len(im.shape) == 3 and  input_tif == []) :
            tmpdir = os.path.join(datadir, "tmp", "DNN_test_images")
            if os.path.exists(tmpdir) :
                shutil.rmtree(tmpdir)
            os.mkdir(tmpdir)
            for input_file in input_files:
                im_col = cv2.imread(input_file)
                filename = os.path.basename(input_file)
                filename = filename.replace('.tif', '.png')
                converted_input_file = os.path.join( tmpdir, filename )
                cv2.imwrite(converted_input_file, im_col)
            params['Image Folder'] = tmpdir
            print('Filetype of images was changed to RGB 8bit, and stored in ', tmpdir)


        comm = parent.u_info.exec_translate +' ' \
                + ' --mode predict ' \
                + ' --save_freq 0 ' \
                + ' --input_dir ' + params['Image Folder'] + ' ' \
                + ' --output_dir ' + params['Output Segmentation Folder'] + ' ' \
                + ' --checkpoint ' + params['Model Folder'] + ' ' \
                + ' --image_height ' + str(image_height) + ' ' \
                + ' --image_width ' + str(image_width)

        try:
            print(comm)
            print('Start inference.')
            m.UnlockFolder(parent.u_info, params['Output Segmentation Folder'])  # Only for shared folder/file
            s.call(comm.split())
            m.LockFolder(parent.u_info, params['Output Segmentation Folder'])
            return
        except s.CalledProcessError as e:
            print("Inference was not executed.")
            m.LockFolder(parent.u_info, params['Output Segmentation Folder'])
            return
    def show_alignment_points(self, image):
        """
        Create an RGB version of a monochrome image and insert red crosses at all alignment
        point locations. Draw green alignment point boxes and white alignment point patches.

        :return: 8-bit RGB image with annotations.
        """

        if len(image.shape) == 3:
            color_image = image.astype(uint8)
        else:
            # Expand the monochrome reference frame to RGB
            color_image = stack((image.astype(uint8), ) * 3, -1)

        # For all alignment boxes insert a color-coded cross.
        cross_half_len = 5

        for alignment_point in (self.alignment_points):
            y_center = alignment_point['y']
            x_center = alignment_point['x']
            Miscellaneous.insert_cross(color_image, y_center, x_center,
                                       cross_half_len, 'red')
            box_y_low = max(alignment_point['box_y_low'], 0)
            box_y_high = min(alignment_point['box_y_high'], image.shape[0]) - 1
            box_x_low = max(alignment_point['box_x_low'], 0)
            box_x_high = min(alignment_point['box_x_high'], image.shape[1]) - 1
            for y in arange(box_y_low, box_y_high):
                color_image[y, box_x_low] = [255, 255, 255]
                color_image[y, box_x_high] = [255, 255, 255]
            for x in arange(box_x_low, box_x_high):
                color_image[box_y_low, x] = [255, 255, 255]
                color_image[box_y_high, x] = [255, 255, 255]

            patch_y_low = max(alignment_point['patch_y_low'], 0)
            patch_y_high = min(alignment_point['patch_y_high'],
                               image.shape[0]) - 1
            patch_x_low = max(alignment_point['patch_x_low'], 0)
            patch_x_high = min(alignment_point['patch_x_high'],
                               image.shape[1]) - 1
            for y in arange(patch_y_low, patch_y_high):
                color_image[y, patch_x_low] = [
                    0, int((255 + color_image[y, patch_x_low][1]) / 2.), 0
                ]
                color_image[y, patch_x_high] = [
                    0, int((255 + color_image[y, patch_x_high][1]) / 2.), 0
                ]
            for x in arange(patch_x_low, patch_x_high):
                color_image[patch_y_low, x] = [
                    0, int((255 + color_image[patch_y_low, x][1]) / 2.), 0
                ]
                color_image[patch_y_high, x] = [
                    0, int((255 + color_image[patch_y_high, x][1]) / 2.), 0
                ]

        return color_image
Пример #22
0
    def __init__(self, main_gui):
        super(Workflow, self).__init__()
        self.main_gui = main_gui
        self.configuration = main_gui.configuration

        self.my_timer = None

        self.frames = None
        self.rank_frames = None
        self.align_frames = None
        self.alignment_points = None
        self.stack_frames = None
        self.stacked_image_name = None
        self.postprocessed_image_name = None
        self.postprocessed_image = None
        self.postproc_input_image = None
        self.postproc_input_name = None
        self.activity = None
        self.attached_log_name = None
        self.attached_log_name_new = None
        self.attached_log_file = None
        self.stdout_saved = None
        self.output_redirected = False
        self.protocol_file = None

        # Switch alignment point debugging on / off.
        self.debug_AP = False

        # The following code works on Windows and Linux systems only. It is not necessary, though.
        try:
            if platform.system() == 'Windows':
                mkl_rt = CDLL('mkl_rt.dll')
            else:
                mkl_rt = CDLL('libmkl_rt.so')

            mkl_get_max_threads = mkl_rt.mkl_get_max_threads

            def mkl_set_num_threads(cores):
                mkl_rt.mkl_set_num_threads(byref(c_int(cores)))

            mkl_set_num_threads(mkl_get_max_threads())
            if self.configuration.global_parameters_protocol_level > 1:
                Miscellaneous.protocol("Number of threads used by mkl: " +
                                       str(mkl_get_max_threads()),
                                       self.attached_log_file,
                                       precede_with_timestamp=True)
        except Exception as e:
            Miscellaneous.protocol(
                "Warning: mkl_rt.dll / libmkl_rt.so does not work (not a Windows or Linux system, "
                "or Intel Math Kernel Library not installed?). " + str(e),
                self.attached_log_file,
                precede_with_timestamp=True)

        # Create the calibration object, used for potential flat / dark corrections.
        self.calibration = Calibration(self.configuration)
Пример #23
0
 def _UpdateFileSystem(self, dir_dojo):
     # Release
     m.UnlockFolder(self.u_info, dir_dojo)
     # Lock again
     m.LockFolder(self.u_info, dir_dojo)
     # Filetype
     self.u_info.open_files_type[dir_dojo] = 'Dojo'
     # Dropdown menu update
     self.parent.UpdateOpenFileMenu()
     # Combo box update
     SyncListQComboBoxExcludeDojoMtifManager.get().removeModel(dir_dojo)
     SyncListQComboBoxOnlyDojoManager.get().addModel(dir_dojo)
    def report_error(self, message):
        """
        This method is triggered by the workflow thread via a signal when an error is to be
        reported. Depending on the protocol level, the error message is written to the
        protocol (file).

        :param message: Error message to be displayed
        :return: -
        """

        if self.configuration.global_parameters_protocol_level > 0:
            Miscellaneous.protocol(message + "\n", self.workflow.attached_log_file)
Пример #25
0
    def _Run(self, parent, params, comm_title):
        #
        print('')
        training_image_file = os.path.join(params['FFNs Folder'],
                                           "grayscale_maps.h5")
        ground_truth_file = os.path.join(params['FFNs Folder'],
                                         "groundtruth.h5")
        record_file_path = os.path.join(params['FFNs Folder'],
                                        "tf_record_file")

        with h5py.File(training_image_file, 'r') as f:
            image = f['raw'][()]
            image_mean = np.mean(image).astype(np.int16)
            image_std = np.std(image).astype(np.int16)
        print('Training image mean: ', image_mean)
        print('Training image std : ', image_std)
        #
        #except:
        #    print("Error: Training Image h5 was not loaded.")
        #    return False
        #
        if params['Sparse Z'] != Qt.Unchecked:
            arg = '{"depth":9,"fov_size":[33,33,17],"deltas":[8,8,4]}'
        else:
            arg = '{"depth":12,"fov_size":[33,33,33],"deltas":[8,8,8]}'

        ##
        tmp = [ \
   '--train_coords' , record_file_path         , \
   '--data_volumes' , 'validation1@' + training_image_file + '@raw'  , \
   '--label_volumes' , 'validation1@' + ground_truth_file  + '@stack' , \
   '--model_name'  , 'convstack_3d.ConvStack3DFFNModel'    , \
   '--model_args'  , arg            , \
   '--image_mean'  , np.str( image_mean )        , \
   '--image_stddev' , np.str( image_std )        , \
   '--train_dir'  , params['Model Folder (Empty/Model)']     , \
   '--max_steps'  , np.str(np.int(params['Max Training Steps'])) ]

        comm_train = parent.u_info.exec_train[:]
        comm_train.extend(tmp)

        #
        print(comm_title)
        print('')
        print('  '.join(comm_train))
        print('')
        m.UnlockFolder(parent.u_info, params['Model Folder (Empty/Model)'])
        s.run(comm_train)
        m.LockFolder(parent.u_info, params['Model Folder (Empty/Model)'])
        print(comm_title, ' was finished.')
        #
        return True
    def start_indi_dialog(self):
        """
        The "configure" button has been clicked for the INDI telescope interface:
        Open the INDI dialog.

        :return: -
        """

        try:
            # PyIndi is only available on Linux and MacOS. On Windows systems, do nothing.
            import PyIndi
        except ImportError:
            Miscellaneous.show_detailed_error_message("The INDI interface does not seem to work.",
                                                      "Most likely PyIndi is not installed on "
                                                      "this computer.\n\nIf this is a Windows "
                                                      "system, "
                                                      "there might be an ASCOM client available. "
                                                      "In this case, try to use 'ASCOM' instead "
                                                      "of 'INDI'.")
            return

        from indi_configuration_editor import IndiConfigurationEditor

        self.indieditor = IndiConfigurationEditor(self.c, self.new_web_browser_path,
                                                  self.new_indi_server_url,
                                                  self.new_indi_pulse_guide_speed_index,
                                                  self.new_indi_guiding_interval,
                                                  self.new_indi_wait_interval,
                                                  self.new_indi_telescope_lookup_precision)
        # Start the GUI.
        self.indieditor.exec_()

        # Remember that the INDIConfigurationEditor was invoked.
        self.indieditor_called = True
        # Check if the configuration has changed.
        if self.indieditor.configuration_changed:
            # Mark the configuration object as changed.
            self.configuration_changed = True
            # Copy back the current gui values.
            self.new_web_browser_path = str(self.indieditor.input_web_browser_path.text())
            self.new_indi_server_url = str(self.indieditor.input_indi_server_url.text())
            self.new_indi_pulse_guide_speed_index = str(
                self.indieditor.pulse_guide_speed_chooser.currentIndex())
            self.new_indi_guiding_interval = str(self.indieditor.input_guiding_interval.text())
            self.new_indi_wait_interval = str(self.indieditor.input_wait_interval.text())
            self.new_indi_telescope_lookup_precision = str(
                self.indieditor.input_telescope_lookup_precision.text())
        if self.indieditor.telescope_changed:
            # Mark the telescope driver as changed.
            self.telescope_changed = True
Пример #27
0
    def __init__(self, configuration, mark_processed, debug=False):
        """
        Initialize the camera object.

        :param configuration: object containing parameters set by the user
        :param mark_processed: a method in moon_panorama_maker which marks tiles as processed
        :param debug: if True, the socket_client (FireCapture connection) is replaced with a
        mockup object with the same interface. It does not capture videos, but returns the
        acknowledgement as the real object does.

        """

        QtCore.QThread.__init__(self)

        self.configuration = configuration

        # Register method in StartQT5 (module moon_panorama_maker) for marking tile as processed.
        self.mark_processed = mark_processed

        # The "triggered" flag is set to True in "workflow" to start an exposure.
        self.triggered = False
        # The "active" flag is looked up in "workflow" to find out if a video is being acquired.
        self.active = False
        self.terminate = False
        self.active_tile_number = -1

        # Set the parameters for the socket connection to FireCapture. FireCapture might run on a
        # different computer.
        self.host = self.configuration.conf.get("Camera", "ip address")
        self.port = self.configuration.fire_capture_port_number

        # For debugging purposes, the connection to FireCapture can be replaced with a mockup class
        # which reads still images from files. These can be used to test the autoaligh mechanism.
        if debug:
            self.mysocket = SocketClientDebug(
                self.host, self.port, self.configuration.camera_debug_delay)
            if self.configuration.protocol_level > 0:
                Miscellaneous.protocol(
                    "Camera in debug mode, still camera emulated.")
        else:
            try:
                self.mysocket = SocketClient(self.host, self.port)
            except:
                raise CameraException(
                    "Unable to establish socket connection to FireCapture, host: "
                    + self.host + ", port: " + str(self.port) + ".")
            if self.configuration.protocol_level > 0:
                Miscellaneous.protocol(
                    "Camera: Connection to FireCapture program established.")
    def start_ascom_dialog(self):
        """
        The "configure" button has been clicked for the ASCOM telescope interface:
        Open the ASCOM dialog.

        :return: -
        """

        try:
            # ASCON is only available on Windows. On Linux systems, do nothing.
            from ascom_configuration_editor import AscomConfigurationEditor
        except ImportError:
            Miscellaneous.show_detailed_error_message("The ASCOM interface does not seem to work.",
                                                      "Most likely the ASCOM platform is not "
                                                      "installed on this computer.\n\nIf this is "
                                                      "a Linux system, "
                                                      "there might be an INDI client available. "
                                                      "In this case, try to use 'INDI' instead of "
                                                      "'ASCOM'.")
            return

        self.ascomeditor = AscomConfigurationEditor(self.c, self.new_ascom_driver_name,
                                                    self.new_ascom_guiding_interval,
                                                    self.new_ascom_wait_interval,
                                                    self.new_ascom_pulse_guide_speed_ra,
                                                    self.new_ascom_pulse_guide_speed_de,
                                                    self.new_ascom_telescope_lookup_precision)
        # Start the GUI.
        self.ascomeditor.exec_()

        # Remember that the AscomConfigurationEditor was invoked.
        self.ascomeditor_called = True
        # Check if the configuration has changed.
        if self.ascomeditor.configuration_changed:
            # Mark the configuration object as changed.
            self.configuration_changed = True
            # Copy back the current gui values.
            self.new_ascom_driver_name = self.ascomeditor.new_driver_name
            self.new_ascom_guiding_interval = str(self.ascomeditor.input_guiding_interval.text())
            self.new_ascom_wait_interval = str(self.ascomeditor.input_wait_interval.text())
            self.new_ascom_pulse_guide_speed_ra = str(
                self.ascomeditor.input_pulse_guide_speed_ra.text())
            self.new_ascom_pulse_guide_speed_de = str(
                self.ascomeditor.input_pulse_guide_speed_de.text())
            self.new_ascom_telescope_lookup_precision = str(
                self.ascomeditor.input_telescope_lookup_precision.text())
        if self.ascomeditor.telescope_changed:
            # Mark the telescope driver as changed.
            self.telescope_changed = True
Пример #29
0
    def _Run(self, parent, params, comm_title):
        ##
        comm_compute_partition = parent.u_info.exec_compute_partition +' ' \
                + ' --input_volume '  + os.path.join(params['FFN File Folder'], "groundtruth.h5@stack")  + ' ' \
                + ' --output_volume ' + os.path.join(params['FFN File Folder'], "af.h5@af") + ' ' \
                + ' --thresholds 0.025,0.05,0.075,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9 ' \
                + ' --lom_radius 24,24,24 ' \
                + ' --min_size 10000 '

        comm_build_coordinates = parent.u_info.exec_build_coordinates +' ' \
                + ' --partition_volumes validation1@'  +  os.path.join(params['FFN File Folder'], "af.h5@af")  + ' ' \
                + ' --coordinate_output ' + os.path.join(params['FFN File Folder'], "tf_record_file") + ' ' \
                + ' --margin 24,24,24 '
        ##
        # try:
        ##
        training_image_files = m.ObtainImageFiles(params['Training Image Folder'])
        images = [cv2.imread(i, cv2.IMREAD_GRAYSCALE) for i in training_image_files]
        images = np.array(images)
        with h5py.File(os.path.join(params['FFN File Folder'], "grayscale_maps.h5"), 'w') as f:
            f.create_dataset('raw', data=images, compression='gzip')
        print('"grayscale_maps.h5" file (training image) was generated.')

        ground_truth_files = m.ObtainImageFiles(params['Ground Truth Folder'])
        images = [cv2.imread(i, -1) for i in ground_truth_files]
        images = np.array(images).astype(np.int32)
        with h5py.File(os.path.join(params['FFN File Folder'], "groundtruth.h5"), 'w') as f:
            f.create_dataset('stack', data=images, compression='gzip')
        print('"groundtruth.h5" file (ground truth) was generated.')
        ##
        #except:
        #    print("Error: h5 files (ground truth) were not generated.")
        #    return False
        ##
        try:
            print(comm_title)
            print('Start compute_partitions.')
            print(comm_compute_partition)
            s.run(comm_compute_partition.split())
            print('Start build_coordinates.')
            print(comm_build_coordinates)
            s.run(comm_build_coordinates.split())
            print(comm_title, 'was finished.')
        ##
        except :
            print("Error: ", comm_title, " was not executed.")
            return False
        ##
        return True
Пример #30
0
 def save_color_data(self, u_info, dir):
     colordata = m.load_hdf5(u_info.color_map_file, u_info.hdf_color_name)
     filename = dir + os.sep + u_info.export_col_name + '.csv'
     print(filename)
     with open(filename, 'w') as f:
         writer = csv.writer(f, lineterminator='\n')
         writer.writerows(colordata)
Пример #31
0
 def _Run(self, parent, params, comm_title):
     ##
     comm_run = self.u_info.exec_template + ' ' \
                 + ' --test_image_folder '   + params['Test image folder'] + ' ' \
                 + ' --inferred_segmentation_folder '     + params['Inferred segmentation folder'] + ' ' \
                 + ' --tensorflow_model_file ' + params['Tensorflow model file']  + ' '
     print(comm_run)
     print('')
     ##
     m.UnlockFolder(self.u_info, params['Inferred segmentation folder']
                    )  # Only for shared folder/file
     s.run(comm_run.split())
     m.LockFolder(self.u_info, params['Inferred segmentation folder'])
     print(comm_title, 'was finished.\n')
     ##
     return True
Пример #32
0
    def __init__(self, u_info):
        ## User info
        self.u_info = u_info
        ## Load DB
        db = DB(self.u_info)

        ## Create 3D geometry

        scale_factor_xy = 2

        xmax = db.canvas_size_y / (2**scale_factor_xy)
        ymax = db.canvas_size_x / (2**scale_factor_xy)
        zmax = db.num_tiles_z
        cube_size = max([xmax, ymax, zmax])
        cube_size = np.ceil(cube_size)
        cube_size = cube_size.astype(np.int32)
        self.small_ids = np.zeros([cube_size, cube_size, cube_size],
                                  dtype=self.u_info.ids_dtype)

        for iz in range(db.num_tiles_z):
            full_map = m.ObtainFullSizeIdsPanel(self.u_info, db, iz)
            small_map = full_map[::(2**scale_factor_xy), ::(
                2**scale_factor_xy)]
            self.small_ids[0:small_map.shape[0], 0:small_map.shape[1],
                           iz] = small_map

        boundingbox_dict = {'x': xmax, 'y': ymax, 'z': zmax}
        with open(
                os.path.join(self.u_info.data_annotator_path,
                             'Boundingbox.json'), 'w') as f:
            json.dump(boundingbox_dict, f, indent=2, ensure_ascii=False)

        return None
Пример #33
0
 def open_ascom_chooser(self):
     try:
         x = win32com.client.Dispatch("ASCOM.Utilities.Chooser")
         x.DeviceType = 'Telescope'
         driver_name = x.Choose(self.new_driver_name)
         if driver_name != "":
             self.new_driver_name = driver_name
     except:
         if self.c.protocol_level > 0:
             Miscellaneous.protocol("Unable to access the ASCOM telescope chooser. Please check"
                                    " the ASCOM platform installation.")
         Miscellaneous.show_detailed_error_message("Unable to access the ASCOM telescope "
                                                   "chooser", "Is the ASCOM Platform "
                                                              "installed on this "
                                                              "computer? Please check the "
                                                              "installation.")
Пример #34
0
    def browse_OpenImageFolder(self, lineedit_obj):
        currentdir = lineedit_obj.currentText()
        if len(currentdir) == 0:
            currentdir = os.path.normpath(main_dir)
        ## Custum image folder
        Dialog = m.Dialog_ImageFolder(self.parent, "Select Image Folder",
                                      currentdir)
        return_flag = Dialog.w.exec_()
        if return_flag != 1:
            return False
        open_folder_name = Dialog.GetValue()

        ## Check & open folder
        if len(open_folder_name) == 0:
            return
        open_folder_name = open_folder_name.replace('/', os.sep)
        check_sucess = self.parent.parent.OpenImageFolder(open_folder_name)
        if check_sucess == False:
            id = lineedit_obj.findText(open_folder_name)
            if id >= 0:
                lineedit_obj.setCurrentIndex(id)
                return True
            return False

        lineedit_obj.addItem(open_folder_name)
        id = lineedit_obj.findText(open_folder_name)
        lineedit_obj.setCurrentIndex(id)

        return True
Пример #35
0
    def done(self):
        """
        On exit from the frame viewer, update the stack frame size and send a completion signal.

        :return: -
        """

        # Check if a new stack size was selected.
        if self.stack_size_changed is not None:

            # The number of frames has been set explicitly. Check if it has changed.
            if self.stack_size_changed == 'number':
                if self.spinBox_number_frames.value(
                ) != self.configuration.alignment_points_frame_number:
                    self.configuration.alignment_points_frame_number = self.spinBox_number_frames.value(
                    )
                    self.configuration.alignment_points_frame_percent = -1
                    if self.configuration.global_parameters_protocol_level > 1:
                        Miscellaneous.protocol(
                            "           The user has selected a new stack size: "
                            + str(self.configuration.
                                  alignment_points_frame_number) + " frames.",
                            self.stacked_image_log_file,
                            precede_with_timestamp=False)

            # The percentage of frames has been set. Check if it has changed.
            elif self.spinBox_percentage_frames.value(
            ) != self.configuration.alignment_points_frame_percent:
                self.configuration.alignment_points_frame_number = -1
                self.configuration.alignment_points_frame_percent = \
                    self.spinBox_percentage_frames.value()
                if self.configuration.global_parameters_protocol_level > 1:
                    Miscellaneous.protocol(
                        "           The user has selected a new stack size: " +
                        str(self.configuration.alignment_points_frame_percent)
                        + "% of all frames.",
                        self.stacked_image_log_file,
                        precede_with_timestamp=False)

        # Send a completion message.
        if self.parent_gui is not None:
            self.signal_finished.emit(self.signal_payload)

        # Close the Window.
        self.player_thread.quit()
        plt.close()
        self.close()
Пример #36
0
    def set_landmark(self):
        """
        Let the user select the landmark used for telescope alignment and compute its offset
        from the moon center, including libration and topocentric parallax.

        :return: -
        """

        # Open a gui where the user can select among a collection of landmarks on the moon
        offsets = self.ls.select_landmark(self.me, datetime.now())
        # A landmark has been selected, store and print coordinate offsets.
        if self.ls.landmark_selected:
            (self.ra_offset_landmark, self.de_offset_landmark) = offsets
            if self.configuration.protocol_level > 1:
                Miscellaneous.protocol("Landmark offset from center RA ('): " + str(
                    round(degrees(self.ra_offset_landmark) * 60., 3)) + ", DE ('): " + str(
                    round(degrees(self.de_offset_landmark) * 60., 3)) + ".")
            self.landmark_offset_set = True
        else:
            self.landmark_offset_set = False
Пример #37
0
    def ephemeris_to_telescope_coordinates(self, ra, de):
        """
        Translate celestial equatorial coordinates into coordinates of telescope mount.

        :param ra: Celestial right ascension
        :param de: Celestial declination
        :return: Equatorial mount coordinates (RA, DE)
        """

        correction = self.compute_coordinate_correction()
        # Add corrections to ephemeris position to get telescope coordinates
        telescope_ra = ra + correction[0]
        telescope_de = de + correction[1]
        if self.configuration.protocol_level > 2:
            Miscellaneous.protocol("Translating equatorial to telescope coordinates, "
                                   "correction in RA: " + str(
                round(degrees(correction[0]), 5)) + ", in DE: " + str(
                round(degrees(correction[1]), 5)) + ", Telescope RA: " + str(
                round(degrees(telescope_ra), 5)) + ", Telescope DE: " + str(
                round(degrees(telescope_de), 5)) + " (all in degrees).")
        return telescope_ra, telescope_de
Пример #38
0
    def center_offset_to_telescope_coordinates(self, delta_ra, delta_de):
        """
        Translate offset angles relative to moon center into equatorial coordinates (RA, DE) in
        the coordinate system of the telescope mount.

        :param delta_ra: Center offset angle in ra
        :param delta_de: Center offset angle in de
        :return: Equatorial telescope mount coordinates (RA, DE)
        """

        # Compute current position of the moon.
        self.me.update(datetime.now())
        if self.configuration.protocol_level > 2:
            Miscellaneous.protocol("Translating center offset to equatorial coordinates, "
                                   "center offsets: RA: " + str(
                round(degrees(delta_ra), 5)) + ", DE: " + str(
                round(degrees(delta_de), 5)) + ", moon position (center): RA: " + str(
                round(degrees(self.me.ra), 5)) + ", DE: " + str(
                round(degrees(self.me.de), 5)) + " (all in degrees).")
        # Add the offset to moon center coordinates.
        ra = self.me.ra + delta_ra
        de = self.me.de + delta_de
        # Translate coordinates into telescope system
        return self.ephemeris_to_telescope_coordinates(ra, de)
Пример #39
0
    def __init__(self, configuration, camera_socket, debug=False):
        """
        Initialize the ImageShift object, capture the reference frame and find keypoints in the
        reference frame.

        :param configuration: object containing parameters set by the user
        :param camera_socket: the socket_client object used by the camera
        :param debug: if set to True, display keypoints and matches in Matplotlib windows.
        """

        self.configuration = configuration
        self.camera_socket = camera_socket

        # Initialize instance variables.
        self.shifted_image_array = None
        self.shifted_image = None
        self.shifted_image_kp = None
        self.shifted_image_des = None

        # Get camera and telescope parameters.
        pixel_size = (self.configuration.conf.getfloat("Camera", "pixel size"))
        self.focal_length = (self.configuration.conf.getfloat("Telescope", "focal length"))
        ol_inner_min_pixel = (self.configuration.conf.getint("Camera", "tile overlap pixel"))
        # The still pictures produced by the camera are reduced both in x and y pixel directions
        # by "compression_factor". Set the compression factor such that the overlap between tiles
        # is resolved in a given number of pixels (pixels_in_overlap_width). This resolution should
        # be selected such that the telescope pointing can be determined precisely enough for
        # auto-alignment.
        self.compression_factor = ol_inner_min_pixel / self.configuration.pixels_in_overlap_width
        # Compute the angle corresponding to a single pixel in the focal plane.
        self.pixel_angle = atan(pixel_size / self.focal_length)
        # Compute the angle corresponding to the overlap between tiles.
        self.ol_angle = ol_inner_min_pixel * self.pixel_angle
        # The scale value is the angle corresponding to a single pixel in the compressed camera
        # images.
        self.scale = self.compression_factor * self.pixel_angle
        self.debug = debug

        # During auto-alignment all still images captured are stored in a directory in the user's
        # home directory. If such a directory is found from an old MPM run, delete it first.
        self.image_dir = os.path.join(self.configuration.home,
                                      ".MoonPanoramaMaker_alignment_images")

        # If the directory is old, delete it first.
        if os.path.exists(self.image_dir) and time.time() - os.path.getmtime(
                self.image_dir) > self.configuration.alignment_pictures_retention_time:
            try:
                shutil.rmtree(self.image_dir)
            except:
                raise RuntimeError

        # If the directory does not exist or has just been deleted, create a new one.
        if not os.path.exists(self.image_dir):
                # Create directory for still images. In Windows this operation sometimes fails.
                # Therefore, retry until the operation is successful.
                success = False
                for retry in range(self.configuration.polling_time_out_count):
                    try:
                        os.mkdir(self.image_dir)
                        success = True
                        break
                    except:
                        if self.configuration.protocol_level > 1:
                            Miscellaneous.protocol(
                                "Warning: In imageShift, mkdir failed, retrying...")
                        plt.pause(0.1)
                # Raise a runtime error if all loop iterations were unsuccessful.
                if not success:
                    raise RuntimeError

        # The counter is used to number the alignment images captured during auto-alignment.
        self.alignment_image_counter = 0

        # Create CLAHE and ORB objects.
        self.clahe = cv2.createCLAHE(clipLimit=self.configuration.clahe_clip_limit, tileGridSize=(
            self.configuration.clahe_tile_grid_size, self.configuration.clahe_tile_grid_size))
        self.orb = cv2.ORB_create(WTA_K=self.configuration.orb_wta_k,
                                  nfeatures=self.configuration.orb_nfeatures,
                                  scoreType=cv2.ORB_HARRIS_SCORE,
                                  edgeThreshold=self.configuration.orb_edge_threshold,
                                  patchSize=self.configuration.orb_patch_size,
                                  scaleFactor=self.configuration.orb_scale_factor,
                                  nlevels=self.configuration.orb_n_levels)
        # Create BFMatcher object
        self.bf = cv2.BFMatcher(cv2.NORM_HAMMING2, crossCheck=True)

        try:
            if self.configuration.camera_debug:
                # For debugging purposes: use stored image (already compressed) from observation run
                # Begin with first stored image for every autoalignment initialization.
                self.camera_socket.image_counter = 0
                (reference_image_array, width, height,
                 dynamic) = self.camera_socket.acquire_still_image(1)
            else:
                # Capture the reference image which shows perfect alignment, apply compression.
                (reference_image_array, width, height,
                 dynamic) = self.camera_socket.acquire_still_image(self.compression_factor)

            # Normalize brightness and contrast, and determine keypoints and their descriptors.
            (self.reference_image_array, self.reference_image, self.reference_image_kp,
             self.reference_image_des) = self.normalize_and_analyze_image(reference_image_array,
                                                                    "alignment_reference_image.pgm")
        except:
            raise RuntimeError

        # Draw only keypoints location, not size and orientation
        if self.debug:
            img = cv2.drawKeypoints(self.reference_image_array, self.reference_image_kp,
                                    self.reference_image_array)
            plt.imshow(img)
            plt.show()
    def accept(self):
        """
        This method is invoked when the OK button is pressed. If at least one parameter has been
        changed, all text fields are tested for valid input data. Valid data are stored in the
        configuration object. If a test fails, a dialog prompts the user for correction.

        :return: -
        """
        if self.configuration_changed:
            # Replace the original value with the corresponding entry in the gui text field.
            new_guiding_interval = str(self.input_guiding_interval.text())
            # Check if the float entered is within the given bounds [0., 3.]. If the return value
            # is None, an error was detected. In this case give an example for a correct value.
            if not Miscellaneous.testfloat(new_guiding_interval, 0., 3.):
                Miscellaneous.show_input_error("Guiding interval", "0.2")
                return

            # Repeat the same logic for all parameters.
            new_wait_interval = str(self.input_wait_interval.text())
            if not Miscellaneous.testfloat(new_wait_interval, 0., 20.):
                Miscellaneous.show_input_error("Wait interval", "1.")
                return

            new_pulse_guide_speed_ra = str(self.input_pulse_guide_speed_ra.text())
            if not Miscellaneous.testfloat(new_pulse_guide_speed_ra, 0., 0.1):
                Miscellaneous.show_input_error("Pulse guide speed", "0.001")
                return

            new_pulse_guide_speed_de = str(self.input_pulse_guide_speed_de.text())
            if not Miscellaneous.testfloat(new_pulse_guide_speed_de, 0., 0.1):
                Miscellaneous.show_input_error("Pulse guide speed", "0.001")
                return

            new_telescope_lookup_precision = str(self.input_telescope_lookup_precision.text())
            if not Miscellaneous.testfloat(new_telescope_lookup_precision, 0.1, 10.):
                Miscellaneous.show_input_error("Telescope position lookup precision", "0.5")
                return

        # Special case driver_name: This one is handled by a gui of the ASCOM platform.
        if self.new_driver_name != self.old_driver_name:
            self.configuration_changed = True
            self.telescope_changed = True

        # Close the editing gui.
        self.close()
    def accept(self):
        """
        If the OK button is clicked and the configuration has been changed, test all parameters for
        validity. In case an out-of-bound value is entered, open an error correction dialog window.

        :return: -
        """

        if self.configuration_changed:
            # If the tesselation is changed, most of the work done so far has to be repeated.
            # If not at begin of execution, ask the user if this is really what he/she wants to do.
            if self.initialized and self.tesselation_changed:
                # Ask the user for confirmation.
                quit_msg = "The configuration change will invalidate the videos recorded so far. " \
                           "Do you really want to restart the recording workflow?"
                reply = QtWidgets.QMessageBox.question(self, 'Message', quit_msg,
                                                       QtWidgets.QMessageBox.Yes,
                                                       QtWidgets.QMessageBox.No)
                # Negative reply: Ignore changed inputs and close the editor.
                if reply == QtWidgets.QMessageBox.No:
                    self.reject()

            # Get the input string from the GUI text field.
            input_string = str(self.input_longitude.text())
            # Test the input value if it is within the allowed interval (here [-360., +360.])
            if Miscellaneous.testfloat(input_string, -360., 360.):
                self.c.conf.set("Geographical Position", "longitude", input_string)
            else:
                # The value entered is out of bound, show a valid input value example.
                Miscellaneous.show_input_error("Longitude", "7.39720")
                return

            # Repeat the same logic for the other input fields.
            input_string = str(self.input_latitude.text())
            if Miscellaneous.testfloat(input_string, -90., 90.):
                self.c.conf.set("Geographical Position", "latitude", input_string)
            else:
                Miscellaneous.show_input_error("Latitude", "50.69190")
                return

            input_string = str(self.input_elevation.text())
            if Miscellaneous.testint(input_string, -100, 9000):
                self.c.conf.set("Geographical Position", "elevation", input_string)
            else:
                Miscellaneous.show_input_error("Elevation", "250")
                return

            self.c.conf.set("Geographical Position", "timezone",
                            self.timezone_chooser.currentText())

            input_string = str(self.input_ip_address.text())
            if Miscellaneous.testipaddress(input_string):
                self.c.conf.set("Camera", "ip address", input_string)
            else:
                Miscellaneous.show_input_error("IP address to access FireCapture", "192.168.0.34")
                return

            input_string = str(self.input_focal_length.text())
            if Miscellaneous.testfloat(input_string, 0., 100000.):
                self.c.conf.set("Telescope", "focal length", input_string)
            else:
                Miscellaneous.show_input_error("Focal length", "4670.")
                return

            self.c.conf.set("Telescope", "interface type",
                            str(self.mount_interface_chooser.currentText()))
            self.c.conf.set("Workflow", "protocol level", self.protocol_level_chooser.currentText())
            self.c.set_protocol_level()
            self.c.conf.set("Workflow", "protocol to file",
                            ['True', 'False'][self.protocol_to_file_chooser.currentIndex()])
            self.c.conf.set("Workflow", "focus on star",
                            ['True', 'False'][self.focus_on_star_chooser.currentIndex()])
            self.c.conf.set("Workflow", "limb first",
                            ['True', 'False'][self.limb_first_chooser.currentIndex()])
            self.c.conf.set("Workflow", "camera automation",
                            ['True', 'False'][self.camera_automation_chooser.currentIndex()])

            input_string = str(self.input_camera_trigger_delay.text())
            if Miscellaneous.testfloat(input_string, 0., 60.):
                self.c.conf.set("Workflow", "camera trigger delay", input_string)
            else:
                Miscellaneous.show_input_error("Camera trigger delay", "10.")
                return

            input_string = str(self.input_fig_size_horizontal.text())
            if Miscellaneous.testfloat(input_string, 2., 25.):
                self.c.conf.set("Tile Visualization", "figsize horizontal", input_string)
            else:
                Miscellaneous.show_input_error("Figure size horizontal", "10.")
                return

            input_string = str(self.input_fig_size_vertical.text())
            if Miscellaneous.testfloat(input_string, 2., 25.):
                self.c.conf.set("Tile Visualization", "figsize vertical", input_string)
            else:
                Miscellaneous.show_input_error("Figure size vertical", "10.")
                return

            input_string = str(self.input_label_font_size.text())
            if Miscellaneous.testint(input_string, 6, 16):
                self.c.conf.set("Tile Visualization", "label fontsize", input_string)
            else:
                Miscellaneous.show_input_error("Font size for labels", "11")
                return

            input_string = str(self.input_label_shift.text())
            if Miscellaneous.testfloat(input_string, 0., 1.):
                self.c.conf.set("Tile Visualization", "label shift", input_string)
            else:
                Miscellaneous.show_input_error("Label shift parameter", "0.8")
                return

            input_string = str(self.input_min_autoalign_interval.text())
            if Miscellaneous.testfloat(input_string, 20., 1800.):
                self.c.conf.set("Alignment", "min autoalign interval", input_string)
            else:
                Miscellaneous.show_input_error("Minimum auto-alignment interval", "120.")
                return

            input_string = str(self.input_max_autoalign_interval.text())
            if Miscellaneous.testfloat(input_string, 30., 3600.):
                self.c.conf.set("Alignment", "max autoalign interval", input_string)
            else:
                Miscellaneous.show_input_error("Maximum auto-alignment interval", "900.")
                return

            input_string = str(self.input_max_alignment_error.text())
            if Miscellaneous.testfloat(input_string, 10., 60.):
                self.c.conf.set("Alignment", "max alignment error", input_string)
            else:
                Miscellaneous.show_input_error("Max alignment error", "30.")
                return

            if self.ascomeditor_called:
                # If the AscomEditor was called, new parameters are already checked for validity.
                self.c.conf.set("ASCOM", "guiding interval", self.new_ascom_guiding_interval)
                self.c.conf.set("ASCOM", "wait interval", self.new_ascom_wait_interval)
                self.c.conf.set("ASCOM", "pulse guide speed RA",
                                self.new_ascom_pulse_guide_speed_ra)
                self.c.conf.set("ASCOM", "pulse guide speed DE",
                                self.new_ascom_pulse_guide_speed_de)
                self.c.conf.set("ASCOM", "telescope lookup precision",
                                self.new_ascom_telescope_lookup_precision)
                self.c.conf.set('ASCOM', 'telescope driver', self.ascomeditor.new_driver_name)

            if self.indieditor_called:
                # If the IndiEditor was called, copy back the current new values.
                self.c.conf.set("INDI", "web browser path", self.new_web_browser_path)
                self.c.conf.set("INDI", "server url", self.new_indi_server_url)
                self.c.conf.set("INDI", "pulse guide speed index",
                                self.new_indi_pulse_guide_speed_index)
                self.c.conf.set("INDI", "guiding interval", self.new_indi_guiding_interval)
                self.c.conf.set("INDI", "wait interval", self.new_indi_wait_interval)
                self.c.conf.set("INDI", "telescope lookup precision",
                                self.new_indi_telescope_lookup_precision)

        # All tests passed successfully, and all parameters have been written to the
        # configuration object. Close the GUI window.
        self.close()
    def __init__(self, configuration, de_center, m_diameter, phase_angle, pos_angle):
        """
        Read out parameters from the configuration object and compute the optimal tile coverage.
        
        :param configuration: object containing parameters set by the user
        :param de_center: declination of the moon's center (radians)
        :param m_diameter: diameter of the moon (radians)
        :param phase_angle: phase angle of the sunlit moon phase (0. for New Moon, Pi for Full Moon)
        :param pos_angle: angle (radians) between North and the "North Pole" of the sunlit phase,
        counted counterclockwise
        """

        # Allocate instance variables used in Tile Visualization later.
        self.m_diameter = m_diameter
        self.phase_angle = phase_angle

        # Configuration data
        pixel_size = (configuration.conf.getfloat("Camera", "pixel size"))
        focal_length = (configuration.conf.getfloat("Telescope", "focal length"))
        im_h_pixel = configuration.conf.getint("Camera", "pixel vertical")
        im_w_pixel = (configuration.conf.getint("Camera", "pixel horizontal"))
        ol_outer_pixel = (configuration.conf.getint("Camera", "external margin pixel"))
        ol_inner_min_pixel = (configuration.conf.getint("Camera", "tile overlap pixel"))
        self.limb_first = (configuration.conf.getboolean("Workflow", "limb first"))

        # Height / width of the image, external margin width and tile overlap in radians
        self.im_h = float(im_h_pixel) * atan(pixel_size / focal_length)
        self.im_w = float(im_w_pixel) * atan(pixel_size / focal_length)
        self.ol_outer = float(ol_outer_pixel) * atan(pixel_size / focal_length)
        ol_inner_min = float(ol_inner_min_pixel) * atan(pixel_size / focal_length)

        # Auxiliary parameters used by the "rotate" method in module miscellaneous
        flip_x = 1.
        flip_y = 1.
        scale_factor = 1.

        # Compute the minimum number of tile rows needed to fulfill overlap requirements.
        n_rows = (m_diameter + 2. * self.ol_outer - ol_inner_min) / (self.im_h - ol_inner_min)
        # Round up to next integer.
        n_rows_corrected = int(ceil(n_rows))
        # Increase the vertical tile overlap such that the external margin width is as specified.
        if n_rows_corrected > 1:
            self.ol_inner_v = (n_rows_corrected * self.im_h - m_diameter - 2. * self.ol_outer) / (
                    n_rows_corrected - 1.)
        # Only one row of tiles (very unlikely though)
        else:
            self.ol_inner_v = ol_inner_min

        # Initialize the tile structure: All tiles in one row form a list. These lists are collected
        # in "lists_of_tiles".
        self.lists_of_tiles = []
        # Initialize the maximum number of tiles in a row.
        max_cols = 0

        # Construct each row of tiles. The origin of the (x,y) coordinate system is at the moon
        # center, x pointing right, y up. x and y are in radians.
        m_radius = m_diameter / 2.
        for i in range(n_rows_corrected):
            # Compute the y coordinates for the top and bottom of the row.
            y_top = m_radius + self.ol_outer - i * (self.im_h - self.ol_inner_v)
            y_bottom = y_top - self.im_h
            # Compute the x coordinates where the moon limb crosses the top and bottom of the row.
            x_limb_top = sqrt(m_radius ** 2 - min(y_top ** 2, m_radius ** 2))
            x_limb_bottom = sqrt(m_radius ** 2 - min(y_bottom ** 2, m_radius ** 2))
            # The row of tiles does not contain the x axis. The sunlit phase attains its maximum
            # and minimum x values at the top or bottom.
            if y_top * y_bottom > 0.:
                x_max = max(x_limb_top, x_limb_bottom)
                x_min = min(x_limb_top * cos(phase_angle), x_limb_bottom * cos(phase_angle))
            # The row of tiles straddles the x axis: the maximal x value of the phase is the
            # moon's radius.
            else:
                x_max = m_radius
                # Terminator left of y axix: the easy case.
                if cos(phase_angle) < 0.:
                    x_min = m_radius * cos(phase_angle)
                # Terminator to the right of y axis: The minimum x value is attained either at the
                # top or bottom.
                else:
                    x_min = min(x_limb_top * cos(phase_angle), x_limb_bottom * cos(phase_angle))

            # Construct the row of tiles. It must span the x interval [x_min, x_max].
            row_of_tiles = []
            # As above for the y coordinate, compute the minimum number of tiles and round up.
            n_cols = (x_max - x_min + 2. * self.ol_outer - ol_inner_min) / (
                        self.im_w - ol_inner_min)
            n_cols_corrected = int(ceil(n_cols))
            # Update the maximal number of tiles in a row.
            max_cols = max(max_cols, n_cols_corrected)
            # If there is more than one tile in the row: Increase the horizontal tile overlap in
            # this row so that the outer margin is as specified.
            if n_cols_corrected > 1:
                ol_inner_h = (n_cols_corrected * self.im_w - x_max + x_min - 2. * self.ol_outer) / (
                        n_cols_corrected - 1.)
            else:
                ol_inner_h = ol_inner_min
            # For each tile of this row: Collect all data for this tile in a dictionary.
            for j in range(n_cols_corrected):
                tile = {}
                tile['row_index'] = i
                tile['column_index'] = j
                tile['column_total'] = n_cols_corrected
                tile['x_right'] = x_max + self.ol_outer - j * (self.im_w - ol_inner_h)
                tile['x_left'] = tile['x_right'] - self.im_w
                tile['y_top'] = y_top
                tile['y_bottom'] = y_bottom
                tile['x_center'] = (tile['x_right'] + tile['x_left']) / 2.
                tile['y_center'] = (tile['y_top'] + tile['y_bottom']) / 2.
                # rotate the (x,y) coordinates to get displacements in (RA,DE) relative to the
                # moon center. Note the approximate correction of the RA displacement because of
                # the moon's declination.
                [tile['delta_ra_center'], tile['delta_de_center']] = (
                    Miscellaneous.rotate(pos_angle, de_center, scale_factor, flip_x, flip_y,
                                         tile['x_center'], tile['y_center']))
                # Append the tile to its row.
                row_of_tiles.append(tile)

            # A row of tiles is completed, append it to the global structure.
            self.lists_of_tiles.append(row_of_tiles)

        # Put all tiles in a sequential order. The numbering goes through columns of tiles, always
        # from top to bottom. Depending on parameter "limb first", the process starts at the sunlit
        # limb or at the terminator.
        self.list_of_tiles_sorted = []
        # Start at the sunlit limb.
        if self.limb_first:
            for j in range(max_cols, 0, -1):
                for i in range(n_rows_corrected):
                    # Not in all rows there are "max_cols" tiles.
                    if j <= len(self.lists_of_tiles[i]):
                        self.list_of_tiles_sorted.append(
                            self.lists_of_tiles[i][len(self.lists_of_tiles[i]) - j])

        # Start at the terminator.
        else:
            for j in range(max_cols):
                for i in range(n_rows_corrected):
                    if j < len(self.lists_of_tiles[i]):
                        self.list_of_tiles_sorted.append(
                            self.lists_of_tiles[i][len(self.lists_of_tiles[i]) - j - 1])

        # Compute the (RA,DE) offsets from moon center for the midpoint on the sunlit limb. The
        # coordinates of this point are (m_radius, 0.) in the (x,y) coordinate system. Rotate into
        # (RA,DE) system.
        [self.delta_ra_limb_center, self.delta_de_limb_center] = (
            Miscellaneous.rotate(pos_angle, de_center, scale_factor, flip_x, flip_y, m_radius, 0.))
    def accept(self):
        """
        This method is invoked when the OK button is pressed. If at least one parameter has been
        changed, all text fields are tested for valid input data. Valid data are stored in the
        configuration object. If a test fails, a dialog prompts the user for correction.

        :return: -
        """
        if self.configuration_changed:
            # Replace the original value with the corresponding entry in the gui text field.
            new_pixel_size = str(self.input_pixel_size.text())
            # Check if the float entered is within the given bounds [0., 0.02]. If the return value
            # is None, an error was detected. In this case give an example for a correct value.
            if not Miscellaneous.testfloat(new_pixel_size, 0., 0.02):
                Miscellaneous.show_input_error("Pixel size (mm)", "0.00375")
                return
            else:
                # Set the corresponding entry in the configuration object to the valuee ntered.
                self.c.conf.set(self.section_name, 'pixel size', new_pixel_size)

            # Repeat the same logic for all parameters.
            new_pixel_horizontal = str(self.input_pixel_horizontal.text())
            if not Miscellaneous.testint(new_pixel_horizontal, 1, 20000):
                Miscellaneous.show_input_error("Pixel count horizontal", "1280")
                return
            else:
                self.c.conf.set(self.section_name, 'pixel horizontal', new_pixel_horizontal)

            new_pixel_vertical = str(self.input_pixel_vertical.text())
            if not Miscellaneous.testint(new_pixel_vertical, 1, 20000):
                Miscellaneous.show_input_error("Pixel count vertical", "960")
                return
            else:
                self.c.conf.set(self.section_name, 'pixel vertical', new_pixel_vertical)

            new_repetition_count = str(self.input_repetition_count.text())
            if not Miscellaneous.testint(new_repetition_count, 1, 10):
                Miscellaneous.show_input_error("Repetition count", "3")
                return
            else:
                self.c.conf.set(self.section_name, 'repetition count', new_repetition_count)

            new_external_margin_pixel = str(self.input_external_margin.text())
            if not Miscellaneous.testint(new_external_margin_pixel, 1, 10000):
                Miscellaneous.show_input_error("External margin pixel", "300")
                return
            else:
                self.c.conf.set(self.section_name, 'external margin pixel',
                                new_external_margin_pixel)

            new_tile_overlap_pixel = str(self.input_tile_overlap.text())
            if not Miscellaneous.testint(new_tile_overlap_pixel, 1, 5000):
                Miscellaneous.show_input_error("Tile overlap pixels", "150")
                return
            else:
                self.c.conf.set(self.section_name, 'tile overlap pixel', new_tile_overlap_pixel)

            # Copy all entries of the current camera model into the "Camera" section of the
            # configuration object. This section is displayed in the main configuration gui
            # under the heading "Camera".
            self.c.copy_camera_configuration(self.new_name)

        # Close the editing gui.
        self.close()
    def accept(self):
        """
        This method is invoked when the OK button is pressed. If at least one parameter has been
        changed, all text fields are tested for valid input data. Valid data are stored in the
        configuration object. If a test fails, a dialog prompts the user for correction.

        :return: -
        """

        if self.configuration_changed:
            # Read the new camera name from the corresponding entry in the gui text field.
            new_name = str(self.input_camera_name.text())
            # Test if there is already a camera with the same name in the configuration object.
            if new_name in self.camlist:
                Miscellaneous.show_input_error("Brand / Name (duplicate)", "Name not in list")
                return
            # Test if an empty name (invalid) is given.
            elif new_name == '':
                Miscellaneous.show_input_error("Brand / Name", "ZWO ASI120MM-S")
                return

            # Read the value for the new variable from the corresponding gui text field.
            new_pixel_size = str(self.input_pixel_size.text())
            # Check if the float entered is within the given bounds [0., 0.02]. If the return value
            # is None, an error was detected. In this case give an example for a correct value.
            if not Miscellaneous.testfloat(new_pixel_size, 0., 0.02):
                Miscellaneous.show_input_error("Pixel size (mm)", "0.00375")
                return

            # Repeat the same logic for all parameters.
            new_pixel_horizontal = str(self.input_pixel_horizontal.text())
            if not Miscellaneous.testint(new_pixel_horizontal, 1, 20000):
                Miscellaneous.show_input_error("Pixel count horizontal", "1280")
                return

            new_pixel_vertical = str(self.input_pixel_vertical.text())
            if not Miscellaneous.testint(new_pixel_vertical, 1, 20000):
                Miscellaneous.show_input_error("Pixel count vertical", "960")
                return

            new_repetition_count = str(self.input_repetition_count.text())
            if not Miscellaneous.testint(new_repetition_count, 1, 10):
                Miscellaneous.show_input_error("Repetition count", "3")
                return

            new_external_margin_pixel = str(self.input_external_margin.text())
            if not Miscellaneous.testint(new_external_margin_pixel, 1, 10000):
                Miscellaneous.show_input_error("External margin pixel", "300")
                return

            new_tile_overlap_pixel = str(self.input_tile_overlap.text())
            if not Miscellaneous.testint(new_tile_overlap_pixel, 1, 5000):
                Miscellaneous.show_input_error("Tile overlap pixels", "150")
                return

            # Create a new section in the configuration object for this camera model and store
            # the parameters there.
            section_name = 'Camera ' + new_name
            self.c.conf.add_section(section_name)
            self.c.conf.set(section_name, 'name', new_name)
            self.c.conf.set(section_name, 'pixel size', new_pixel_size)
            self.c.conf.set(section_name, 'pixel horizontal', new_pixel_horizontal)
            self.c.conf.set(section_name, 'pixel vertical', new_pixel_vertical)
            self.c.conf.set(section_name, 'repetition count', new_repetition_count)
            self.c.conf.set(section_name, 'external margin pixel', new_external_margin_pixel)
            self.c.conf.set(section_name, 'tile overlap pixel', new_tile_overlap_pixel)

            # Copy all entries of the current camera model into the "Camera" section of the
            # configuration object. This section is displayed in the main configuration gui under
            # the heading "Camera".
            self.c.copy_camera_configuration(new_name)

        # Close the input gui.
        self.close()
Пример #45
0
    def align(self, alignment_manual=True):
        """
        Determine the current error in telescope pointing, either with the help of the user
        (manual mode) or automatically (auto-alignment).

        :param alignment_manual: True if the telescope has been aimed at landmark by the user
        :return: In case alignment_manual=False (auto-alignment), return the relative alignment
                 error. The deviation of the current positioning as compared to the expected
                 position, based on the previous alignment, is determined. The quotient of this
                 deviation and the width of the overlap between tiles is returned. If it is too
                 large, a complete panorama coverage cannot be guaranteed. In case of
                 manual_alignment=True, return None.
        """

        # Alignment is only possible after a landmark has been selected.
        if not self.landmark_offset_set:
            if self.configuration.protocol_level > 0:
                Miscellaneous.protocol("Error in alignment: Landmark offset not set.")
            raise RuntimeError("Error: Landmark offset not set.")

        # Manual alignment: The telescope is aimed at the current location of the landmark. Look
        # up its position and proceed to alignment computation.
        if alignment_manual:
            # The telescope position is delivered by the mount driver
            (ra_landmark, de_landmark) = self.tel.lookup_tel_position()
            relative_alignment_error = None

        # Auto-alignment: No assumption on the current telescope pointing can be made.
        else:
            # Automatic alignment: check if auto-alignment has been initialized
            if not self.autoalign_initialized:
                raise RuntimeError("Error: Attempt to do an auto-alignment before initialization.")
            # Move telescope to expected coordinates of alignment point
            (ra_landmark, de_landmark) = (self.compute_telescope_coordinates_of_landmark())
            self.tel.slew_to(ra_landmark, de_landmark)
            time.sleep(self.configuration.conf.getfloat("ASCOM", "wait interval"))
            try:
                # Measure shift against reference frame
                (x_shift, y_shift, in_cluster, outliers) = self.im_shift.shift_vs_reference()
                if self.configuration.protocol_level > 1:
                    Miscellaneous.protocol("New alignment frame analyzed, x_shift: " + str(
                        round(x_shift / self.im_shift.pixel_angle, 1)) + ", y_shift: " + str(
                        round(y_shift / self.im_shift.pixel_angle,
                              1)) + " (pixels), # consistent shifts: " + str(
                        in_cluster) + ", # outliers: " + str(outliers) + ".")
            except RuntimeError as e:
                if self.configuration.protocol_level > 0:
                    Miscellaneous.protocol("Exception in auto-alignment: " + str(e))
                raise RuntimeError(str(e))
            global_shift = sqrt(x_shift ** 2 + y_shift ** 2)
            relative_alignment_error = global_shift / self.shift_angle
            # Translate shifts measured in camera image into equatorial coordinates
            scale_factor = 1.
            # In tile construction (where the rotate function had been designed for) x is pointing
            # right and y upwards. Here, x is pointing right and y downwards. Therefore, the y flip
            # has to be reversed.
            (ra_shift, de_shift) = Miscellaneous.rotate(self.me.pos_angle_pole, self.me.de,
                                                        scale_factor, self.flip_x,
                                                        -1. * self.flip_y, x_shift, y_shift)
            if self.configuration.protocol_level > 2:
                Miscellaneous.protocol("Alignment shift rotated to RA/DE: RA: " + str(
                    round(ra_shift / self.im_shift.pixel_angle, 1)) + ", DE: " + str(
                    round(de_shift / self.im_shift.pixel_angle, 1)) + " (pixels).")
            # The shift is computed as "current frame - reference". Add coordinate shifts to current
            # mount position to get mount setting where landmark is located as on reference frame.
            ra_landmark += ra_shift
            de_landmark += de_shift

        # From here on, manual and auto-alignment can be treated the same. The current mount
        # position is given by(ra_landmark, de_landmark).
        current_time = datetime.now()
        # Set the time of the alignment point with an accuracy better than a second.
        self.alignment_time = self.current_time_seconds(current_time)

        # Update ephemeris of moon and sun
        self.me.update(current_time)

        # Correction = telescope position minus updated ephemeris position of
        # landmark
        self.ra_correction = ra_landmark - (self.me.ra + self.ra_offset_landmark)
        self.de_correction = de_landmark - (self.me.de + self.de_offset_landmark)

        if self.configuration.protocol_level > 0:
            Miscellaneous.protocol("Computing new alignment, current RA correction ('): " + str(
                round(degrees(self.ra_correction) * 60.,
                      3)) + ", current DE correction ('): " + str(
                round(degrees(self.de_correction) * 60., 3)) + ".")

        if self.configuration.protocol_level > 2:
            Miscellaneous.protocol("More alignment info: moon center RA: " + str(
                round(degrees(self.me.ra), 5)) + ", moon center DE: " + str(
                round(degrees(self.me.de), 5)) + ", landmark RA: " + str(
                round(degrees(ra_landmark), 5)) + ", landmark DE: " + str(
                round(degrees(de_landmark), 5)) + " (all in degrees).")

        # Store a new alignment point
        alignment_point = {}
        alignment_point['time_string'] = str(current_time)[11:19]
        alignment_point['time_seconds'] = self.alignment_time
        alignment_point['ra_correction'] = self.ra_correction
        alignment_point['de_correction'] = self.de_correction
        self.alignment_points.append(alignment_point)

        self.is_aligned = True

        # If more than one alignment point is stored, enable drift dialog and compute drift rate
        # of telescope mount.
        if len(self.alignment_points) > 1:
            self.drift_dialog_enabled = True
            if self.default_last_drift:
                self.last_index = len(self.alignment_points) - 1
                self.compute_drift_rate()
        return relative_alignment_error
Пример #46
0
    def initialize_auto_align(self, camera_socket):
        """
        Establish the relation between the directions of (x,y) coordinates in an idealized pixel
        image of the Moon (x positive to the east, y positive southwards) and the (x,y) coordinates
        of the normalized plane in which the tile construction is done (x positive to the right,
        y positive upwards). Take into account potential mirror inversion in the optical system.

        :param camera_socket: interface to the camera to capture videos and still images
        :return: fraction of alignment error as compared to width of overlap between tiles
        """

        self.autoalign_initialized = False

        try:
            # Capture an alignment reference frame
            self.im_shift = ImageShift(self.configuration, camera_socket, debug=self.debug)
        except RuntimeError:
            if self.configuration.protocol_level > 0:
                Miscellaneous.protocol(
                    "Auto-alignment initialization failed in capturing alignment reference frame.")
            raise RuntimeError

        if self.configuration.protocol_level > 1:
            Miscellaneous.protocol("Alignment reference frame captured.")

        # The shift_angle is the overlap width between panorama tiles (in radians).
        self.shift_angle = self.im_shift.ol_angle
        # Three positions in the sky are defined: right shift in x direction, zero shift, and
        # downward shift in y direction. (x,y) are the pixel coordinates in the still images
        # captured with the video camera. All shifts are relative to the current coordinates of
        # the landmark.
        shift_vectors = [[self.shift_angle, 0.], [0., 0.], [0., self.shift_angle]]
        xy_shifts = []
        for shift in shift_vectors:
            # Compute current coordinates of landmark, including corrections for alignment and drift
            (ra_landmark, de_landmark) = (self.compute_telescope_coordinates_of_landmark())
            # Transform (x,y) coordinates into (ra,de) coordinates. The y-flip has to be set to -1.
            # because the rotate function assumes the y coordinate to point up, whereas the y pixel
            # coordinate is pointing down (see comment in method align.
            (shift_angle_ra, shift_angle_de) = Miscellaneous.rotate(self.me.pos_angle_pole,
                                                                    self.me.de, 1., 1., -1.,
                                                                    shift[0], shift[1])
            # Drive the telescope to the computed position in the sky.
            self.tel.slew_to(ra_landmark + shift_angle_ra, de_landmark + shift_angle_de)
            # Wait until the telescope orientation has stabilized.
            time.sleep(self.configuration.conf.getfloat("ASCOM", "wait interval"))
            try:
                # Capture a still image of the area around landmark and determine the shift versus
                # the reference frame.
                (x_shift, y_shift, in_cluster, outliers) = self.im_shift.shift_vs_reference()
            # If the image was not good enough for automatic shift determination, disable auto-
            # alignment.
            except RuntimeError as e:
                if self.configuration.protocol_level > 2:
                    Miscellaneous.protocol(str(e))
                raise RuntimeError
            if self.configuration.protocol_level > 2:
                Miscellaneous.protocol("Frame captured for auto-alignment, x_shift: " + str(
                    round(x_shift / self.im_shift.pixel_angle, 1)) + ", y_shift: " + str(
                    round(y_shift / self.im_shift.pixel_angle,
                          1)) + " (pixels), # consistent shifts: " + str(
                    in_cluster) + ", # outliers: " + str(outliers) + ".")
            xy_shifts.append([x_shift, y_shift])
        # Subtract second position from first and third position and reverse the vector. Reason for
        # the reversal: The shift has been applied to the mount pointing. The shift measured in the
        # image is the opposite of the mount shift.
        shift_vector_0_measured = [xy_shifts[1][0] - xy_shifts[0][0],
                                   xy_shifts[1][1] - xy_shifts[0][1]]
        shift_vector_2_measured = [xy_shifts[1][0] - xy_shifts[2][0],
                                   xy_shifts[1][1] - xy_shifts[2][1]]

        # Compare measured shifts in x and y with the expected directions to find out if images
        # are mirror-inverted in x or y.
        self.flip_x = np.sign(shift_vector_0_measured[0])
        self.flip_y = np.sign(shift_vector_2_measured[1])
        if self.configuration.protocol_level > 2:
            if self.flip_x < 0:
                Miscellaneous.protocol("Auto-alignment, image flipped horizontally.")
            else:
                Miscellaneous.protocol("Auto-alignment, image not flipped horizontally.")
            if self.flip_y < 0:
                Miscellaneous.protocol("Auto-alignment, image flipped vertically.")
            else:
                Miscellaneous.protocol("Auto-alignment, image not flipped vertically.")
        # Determine how much the measured shifts deviate from the expected shifts in the focal
        # plane. If the difference is too large, auto-alignment initialization is interpreted as
        # not successful.
        error_x = abs(abs(shift_vector_0_measured[0]) - self.shift_angle) / self.shift_angle
        error_y = abs(abs(shift_vector_2_measured[1]) - self.shift_angle) / self.shift_angle
        error = max(error_x, error_y)
        focal_length_x = abs(
            shift_vector_0_measured[0]) / self.shift_angle * self.im_shift.focal_length
        focal_length_y = abs(
            shift_vector_2_measured[1]) / self.shift_angle * self.im_shift.focal_length
        if self.configuration.protocol_level > 1:
            Miscellaneous.protocol("Focal length measured in x direction:  " + str(
                round(focal_length_x, 1)) + ", in y direction: " + str(
                round(focal_length_y, 1)) + " (mm).")
        if error > self.configuration.align_max_autoalign_error:
            if self.configuration.protocol_level > 0:
                Miscellaneous.protocol(
                    "Auto-alignment initialization failed, focal length error in x: " + str(
                        round(error_x * 100., 1)) + ", in y: " + str(
                        round(error_y * 100., 1)) + " (percent).")
            raise RuntimeError
        else:
            if self.configuration.protocol_level > 0:
                Miscellaneous.protocol("Auto-alignment successful, focal length error in x: " + str(
                    round(error_x * 100., 1)) + ", in y: " + str(
                    round(error_y * 100., 1)) + " (percent).")
        self.autoalign_initialized = True
        # Return the relative error as compared with tile overlap width.
        return error