def __init__(self, image1, image2, align_config, detector_config=None):
        """
        Takes two images and uses feature detection to provide a best fit alignment. The scale of the images will be
        normalized by resizing image1 to the same resolution as image2.  Note that this does not mean the images
        will be the same size!
        :param image1: This image will be rescaled to the same resolution as image2.
        :param image2: The image used to align the sample.
        :param align_config: Configuration object for this process.
        :param detector_config: Configuration object for the feature detector.
        """
        log = logging.getLogger(".".join([__name__, self.__class__.__name__]))
        log.addFilter(logconfig.ThreadContextFilter())
        assert (align_config is not None)
        # Create images with associated real sizes
        px_size_1 = align_config.pixel_size_1.value()
        px_size_2 = align_config.pixel_size_2.value()
        self._resolution = px_size_2  # The resolution of the second image will be the working resolution
        self._scale_factor = px_size_1 / px_size_2

        log.info("Image 1 original size: %d x %d (%f um/pixel)",
                 image1.width(), image1.height(), px_size_1)
        log.info("Image 2 original size: %d x %d (%f um/pixel)",
                 image2.width(), image2.height(), px_size_2)
        extra = {'scale_factor': str(self._scale_factor)}
        log = logging.LoggerAdapter(log, extra)
        log.info("Scale Factor calculated as " + str(self._scale_factor))
        log.debug(extra)

        self._image1 = SizedImage.from_image(image1, px_size_1)
        self._image2 = SizedImage.from_image(image2, px_size_2)

        self._align_config = align_config
        self._detector_config = detector_config

        self._rescale_image_1()
Example #2
0
 def _check_is_file(path):
     if not isfile(path):
         log = logging.getLogger(".".join([__name__]))
         log.addFilter(logconfig.ThreadContextFilter())
         log.error("Could not find the file, file may not been saved: " +
                   path)
         exit(1)
Example #3
0
    def run(self):
        log = logging.getLogger(".".join([__name__, self.__class__.__name__]))
        log.addFilter(logconfig.ThreadContextFilter())
        try:
            total_start = time.time()
            parser_manager = ParserManager()
            script_path = os.path.dirname(os.path.abspath(__file__))
            parser_manager.set_script_path(script_path)
            parser_manager.build_parser()

            logconfig.set_additional_handler(
                parser_manager.get_log_file_path())

            config_directory = parser_manager.get_config_dir()

            log.info('used config directory: ' + config_directory +
                     ', path to script: ' + script_path)

            scale_override = parser_manager.get_scale_override()
            to_json_flag = parser_manager.get_to_json()

            service = CrystalMatch(config_directory,
                                   scale_override=scale_override)
            service_results = service.perform_match(parser_manager)

            total_time = time.time() - total_start
            service_results.log_final_result(total_time)
            service_results.print_results(to_json_flag)

        except IOError as e:

            log.error(e)
Example #4
0
    def calculate_transform(self, matches):
        if len(matches) == 0:
            return None

        method = self._method
        if method == self.TRANSLATION:
            transform, mask = self._calculate_median_translation(matches)
        elif method == self.HOMOGRAPHY:
            transform, mask = self._calculate_homography_transform(matches)
        elif method in self.AFFINE_METHODS:
            transform, mask = self._calculate_affine_transform(matches)
        else:
            log = logging.getLogger(".".join([__name__]))
            log.addFilter(logconfig.ThreadContextFilter())
            log.error(
                TransformCalculationError(
                    "Unrecognised transform method type"))
            raise TransformCalculationError(
                "Unrecognised transform method type")

        if transform is None:
            self._mark_all_matches_unused(matches)
        else:
            self._set_matches_reprojection_error(matches, transform)
            self._mark_unused_matches(matches, mask)

        return transform
Example #5
0
    def images_to_stack(self):
        """Function which finds the maximum of mean FFT values provided.
        It uses the maximum value to pick a subset of images from an initial set.
        The subset is later used by the stacking algorithm (pyramid) to create the all-in-focus-image.
        The number of images to stack is defined by IMG_TO_STACK"""
        log = logging.getLogger(".".join([__name__, self.__class__.__name__]))
        log.addFilter(logconfig.ThreadContextFilter())

        ffts = []
        for s in self.fft_img:
            ffts.append(s.getFFT())
        max_fft_value = max(ffts)
        max_fft_value_index = ffts.index(max_fft_value)
        best_fft_img = self.fft_img[max_fft_value_index] # the sequence of images is the same as the sequence of ffts
        best_fft_img_num = best_fft_img.get_image_number()
        log.info("Best score was %f for %s" % (max_fft_value, best_fft_img.name))
        range = self.find_range(best_fft_img_num)

        extra = {'best_fft_val': round(max_fft_value, 4),
                 'best_fft_img_num': best_fft_img_num,
                 'stack_num': self.config.number_to_stack.value()}
        log = logging.LoggerAdapter(log, extra)
        log.info("Stacking " + str(self.config.number_to_stack.value()) + " images " +
                 " First img: " + str(range[0]) + " last img: " + str(range[-1]))

        images = []
        for s in self.fft_img:
            if s.get_image_number() in range:
                self.fft_images_to_stack.append(s)
                images.append(s.get_image())
        return images
 def _log_matching_time(time):
     log = logging.getLogger(".".join([__name__]))
     log.addFilter(logconfig.ThreadContextFilter())
     extra = {'matching_time': time}
     log = logging.LoggerAdapter(log, extra)
     log.info("Matching Complete")
     log.debug(extra)
Example #7
0
    def get_scale_override(self):
        scale = self.get_args().scale
        log = logging.getLogger(".".join([__name__]))
        log.addFilter(logconfig.ThreadContextFilter())

        if scale is not None:
            try:
                scales = scale.split(":")
                assert (len(scales) == 2)
                return float(scales[0]), float(scales[1])
            except AssertionError:
                log.error(
                    AssertionError(
                        "Scale flag requires two values separated by a colon':'. Value given: "
                        + str(scale)))
                raise AssertionError(
                    "Scale flag requires two values separated by a colon':'. Value given: "
                    + str(scale))

            except ValueError:
                log.error(
                    "Scale must be given as a pair of float values separated by a colon (':'). Value given: "
                    + str(scale))
                raise ValueError(
                    "Scale must be given as a pair of float values separated by a colon (':'). Value given: "
                    + str(scale))
        return None
Example #8
0
 def parse_selected_points_from_args(self):
     """Parse the selected points list provided by the command line for validity and returns a list of Point objects.
     :param args: Command line arguments provided by argument parser - must contain 'selected_points'
     :return: List of Selected Points.
      """
     log = logging.getLogger(".".join([__name__]))
     log.addFilter(logconfig.ThreadContextFilter())
     selected_points = []
     if self.get_args().selected_points:
         point_expected_format = re.compile("[0-9]+,[0-9]+")
         sel_points = self.get_args().selected_points
         for point_string in self.get_args().selected_points:
             point_string = point_string.strip('()')
             match_results = point_expected_format.match(point_string)
             # Check the regex matches the entire string
             # DEV NOTE: can use re.full_match in Python v3
             if match_results is not None and match_results.span(
             )[1] == len(point_string):
                 x, y = map(int, point_string.strip('()').split(','))
                 selected_points.append(Point(x, y))
             else:
                 log.warning(
                     "Selected point with invalid format will be ignored - '"
                     + point_string + "'")
     return selected_points
Example #9
0
    def fuse(self, kernel_size):
        """Function which fuses each level of the pyramid using appropriate fusion operators
        the output is one pyramid containing fused levels"""
        log = logging.getLogger(".".join([__name__, self.__class__.__name__]))
        log.addFilter(logconfig.ThreadContextFilter())

        base_level_fused = self.get_fused_base(kernel_size)
        depth = self.collection[0].get_depth()
        fused = Pyramid(0,depth)
        fused.add_lower_resolution_level(base_level_fused)
        layers = len(self.collection)
        region_kernel = self.get_region_kernel()
        parameters = []
        for level in range(depth - 2, -1, -1):
            sh = self.collection[0].get_level(level).get_array().shape
            laplacians = np.zeros((layers, sh[0], sh [1]), dtype=np.float64)
            for layer in range(0, layers):
                new_level = self.collection[layer].get_level(level).get_array()
                laplacians[layer] = new_level
            param = (laplacians, region_kernel,level)
            parameters.append(param)
        pool = Pool()
        results = pool.map_async(fused_laplacian, parameters)
        bunch = results.get()
        pool.close()
        pool.join()

        fused.add_bunch_of_levels(bunch)

        fused.sort_levels()
        return fused
Example #10
0
def fused_laplacian(parameters):
    """On other levels of the pyramid one fusion operator: region energy is used"""
    laplacians = parameters[0]
    region_kernel = parameters[1]
    level = parameters[2]

    layers = laplacians.shape[0]
    region_energies = np.zeros(laplacians.shape[:3], dtype=np.float64)

    for layer in range(layers):
        gray_lap = PyramidLevel(laplacians[layer], layer, level)
        region_energies[layer] = gray_lap.region_energy(region_kernel)

    best_re = np.argmax(region_energies, axis=0)
    fused = np.zeros(laplacians.shape[1:], dtype=laplacians.dtype)

    for layer in range(layers):
        fused += np.where(best_re[:, :] == layer, laplacians[layer], 0)

    log = logging.getLogger(".".join([__name__]))
    log.addFilter(logconfig.ThreadContextFilter())
    log.debug("Level: " + str(level) + " fused!")

    fused_level = PyramidLevel(fused,0,level)
    return fused_level
Example #11
0
 def _create_default_detector(detector, adaptation):
     """ Create a detector of the specified type with all the default parameters"""
     name = adaptation + detector
     try:
         detector = cv2.FeatureDetector_create(name)
     except AttributeError:
         log = logging.getLogger(".".join([__name__]))
         log.addFilter(logconfig.ThreadContextFilter())
         log.error(OpenCvVersionError(_OPENCV_VERSION_ERROR))
         raise OpenCvVersionError(_OPENCV_VERSION_ERROR)
     return detector
 def _rescale_image_1(self):
     # Resize image A so it has the same size per pixel as image B
     if self._scale_factor != 1:
         self._image1 = self._image1.rescale(self._scale_factor)
         log = logging.getLogger(".".join(
             [__name__, self.__class__.__name__]))
         log.addFilter(logconfig.ThreadContextFilter())
         log.info(
             "Rescaling image 1 by scale factor " +
             str(self._scale_factor) + ", new size: %d x %d",
             self._image1.width(), self._image1.height())
Example #13
0
 def _create_default_extractor(extractor):
     """ Note: SIFT descriptors for a keypoint are an array of 128 integers; SURF descriptors are an
     array of 64 floats (in range -1 to 1); BRISK uses 64 integers, all others are arrays of 32 ints
     (in range 0 to 255). """
     try:
         extractor = cv2.DescriptorExtractor_create(extractor)
     except AttributeError:
         log = logging.getLogger(".".join([__name__]))
         log.addFilter(logconfig.ThreadContextFilter())
         log.error(OpenCvVersionError(_OPENCV_VERSION_ERROR))
         raise OpenCvVersionError(_OPENCV_VERSION_ERROR)
     return extractor
    def perform_match(self, parser_manager):
        """
        Perform image alignment and crystal matching returning a results object.
        :param .
        :return: ServiceResult object.
        """
        log = logging.getLogger(".".join([__name__, self.__class__.__name__]))
        log.addFilter(logconfig.ThreadContextFilter())
        extra = self._config_align.all_to_json()
        extra.update(self._config_crystal.all_to_json())
        log = logging.LoggerAdapter(log, extra)
        log.info("Matching Started")
        log.debug(extra)

        input_poi = parser_manager.parse_selected_points_from_args()
        beamline_image = parser_manager.get_focused_image()
        parser_manager.save_focused_image(beamline_image)
        focused_image_path = parser_manager.get_focused_image_path()
        formulatrix_image_path = parser_manager.get_formulatrix_image_path()
        job_id = parser_manager.get_job_id()

        # Create the images
        image1 = Image.from_file(formulatrix_image_path)
        image2 = beamline_image

        # Create results object
        service_result = ServiceResult(job_id, formulatrix_image_path,
                                       focused_image_path)

        # Perform alignment
        try:

            aligned_images, scaled_poi = self._perform_alignment(
                image1, image2, input_poi)
            service_result.set_image_alignment_results(aligned_images)

            # Perform Crystal Matching - only proceed if we have a valid alignment
            if aligned_images.alignment_status_code(
            ) == ALIGNED_IMAGE_STATUS_OK:
                match_results = self._perform_matching(aligned_images,
                                                       scaled_poi,
                                                       parser_manager)

                service_result.append_crystal_matching_results(match_results)

        except Exception as e:
            if sys.version_info[0] < 3:
                log.error("ERROR: " + e.message)
            else:
                log.error("ERROR: " + str(e))
            service_result.set_err_state(e)

        return service_result
Example #15
0
 def _check_make_dirs(self, directory):
     if not exists(directory) or not isdir(directory):
         log = logging.getLogger(".".join([__name__]))
         log.addFilter(logconfig.ThreadContextFilter())
         try:
             makedirs(directory)
             chmod(directory, self.LOG_DIR_PERMISSION)
             log.info("Directory created: " + directory)
         except OSError:
             log.error(
                 "Could not create find/create directory, path may be invalid: "
                 + directory)
             exit(1)
Example #16
0
def entropy_diviation(parameters):
    """On the top level of the pyramid (the one with the lowest resolution) two fusion operators:
    entropy and deviation are used"""
    pyramid_layer = parameters[0]
    kernel_size = parameters[1]

    gray_image = pyramid_layer
    gray_image.entropy(kernel_size)
    gray_image.deviation(kernel_size)

    log = logging.getLogger(".".join([__name__]))
    log.addFilter(logconfig.ThreadContextFilter())
    log.debug("Calculated entropy/div for top level of layer: " + str(gray_image.get_layer_number()))
    return gray_image
Example #17
0
    def log_final_result(self, total_time):
        log = logging.getLogger(".".join([__name__]))
        log.addFilter(logconfig.ThreadContextFilter())
        extra = self._exit_code.to_json_array_with_names('exit_code_num', 'exit_code')
        extra.update({'input_image': self._image_path_formulatrix,
                      'output_image': self._image_path_beamline,
                      'total_time': total_time})
        if self._job_id and self._job_id != "":
            extra.update({'job_id': self._job_id})


        log = logging.LoggerAdapter(log, extra)
        log.info("Crystal Match Complete")
        log.debug(extra)
 def _check_config(self):
     log = logging.getLogger(".".join([__name__, self.__class__.__name__]))
     log.addFilter(logconfig.ThreadContextFilter())
     """ Raises an exception if configuration has not been properly set. """
     if self._align_config is None:
         log.error(
             ImageAlignmentError(
                 "Must set Alignment config before performing alignment"))
         raise ImageAlignmentError(
             "Must set Alignment config before performing alignment")
     elif self._detector_config is None:
         log.error("Must set Detector config before performing alignment")
         raise ImageAlignmentError(
             "Must set Detector config before performing alignment")
Example #19
0
    def _get_detector(self):
        """ Get the selected detector and raise and exception if it is disabled. """
        detector = self._align_config.align_detector.value()
        enabled = self._detector_config.is_detector_enabled(detector)
        if not enabled:
            log = logging.getLogger(".".join(
                [__name__, self.__class__.__name__]))
            log.addFilter(logconfig.ThreadContextFilter())
            log.error(
                "Cannot perform image alignment as detector '{}' is disabled.".
                format(detector))
            raise ImageAlignmentError(
                "Cannot perform image alignment as detector '{}' is disabled.".
                format(detector))

        return detector
Example #20
0
    def read_ftt_images(self):
        """Function which starts fft calculation for each input image name.
        Multiprocessing is used to speed up the calculation.
        One process for one input image name."""
        log = logging.getLogger(".".join([__name__, self.__class__.__name__]))
        log.addFilter(logconfig.ThreadContextFilter())
        parameters = []
        for idx, file_obj in enumerate(self._image_file_list):
            #first image has index 0
            param = (file_obj.name, idx)
            parameters.append(param)

        pool = Pool()
        results = pool.map_async(fft, parameters)
        self.fft_images = results.get()
        pool.close()
        pool.join()
Example #21
0
def fft(param):
    "Function that reads an image of a given name and  starts fft calculation."
    #read as soon as it appears
    name = param[0]
    count = param[1]
    img_color = cv2.imread(name)
    img = cv2.cvtColor(img_color.astype(np.float32), cv2.COLOR_BGR2GRAY)
    image_fft = ImageFFT(img, count, name)
    level = Fourier(img).runFFT()
    image_fft.setFFT(level)
    log = logging.getLogger(".".join([__name__]))
    log.addFilter(logconfig.ThreadContextFilter())
    extra = ({'fft': image_fft.getFFT()})
    log = logging.LoggerAdapter(log, extra)
    log.info("Finished calculating fft for:" + name)
    log.debug(extra)
    return image_fft
    def composite(self):
        log = logging.getLogger(".".join([__name__, self.__class__.__name__]))
        log.addFilter(logconfig.ThreadContextFilter())
        extra = self._config.all_to_json()
        log = logging.LoggerAdapter(log, extra)
        log.info("Focusstack Started, first image, " +
                 self._image_file_list[0].name)
        log.debug(extra)

        start_t = time.time()

        t1 = time.time()
        man = ImageFFTManager(self._image_file_list)
        man.read_ftt_images()
        sd = SharpnessDetector(man.get_fft_images(), self._config)

        images = sd.images_to_stack()
        self.fft_images = sd.get_fft_images_to_stack()

        t2 = time.time() - t1

        #add extra field to the log
        extra = {'FTT_time': t2}
        log = logging.LoggerAdapter(log, extra)
        log.info("FFT calculation finished")
        log.debug(extra)
        images = np.array(images, dtype=images[0].dtype)

        #TODO:Implement alignment algo
        #aligned_images, gray_images = self.align(images)

        #stacked_image = pyramid(aligned_images, self._config).get_pyramid_fusion()
        stacked_image = PyramidManager(images,
                                       self._config).get_pyramid_fusion()

        stacked_image = cv2.convertScaleAbs(stacked_image)
        backtorgb = cv2.cvtColor(stacked_image, cv2.COLOR_GRAY2RGB)

        calculation_time = time.time() - start_t
        extra = {'stack_time': calculation_time}
        log = logging.LoggerAdapter(log, extra)
        log.info("Stacking Finished")
        log.debug(extra)

        return Image(backtorgb)
Example #23
0
    def _perform_matching(self, aligned_images, selected_points,
                          parser_manager):
        log = logging.getLogger(".".join([__name__]))
        log.addFilter(logconfig.ThreadContextFilter())
        time_start = time.time()
        matcher = CrystalMatcher(aligned_images, self._config_detector)
        matcher.set_fft_images_to_stack(
            parser_manager.get_fft_images_to_stack())
        matcher.set_from_crystal_config(self._config_crystal)

        crystal_match_results = matcher.match(selected_points)
        time_end = time.time() - time_start
        extra = {'matching_time': time_end}
        log = logging.LoggerAdapter(log, extra)
        log.info("Matching Complete")
        log.debug(extra)

        return crystal_match_results
    def run(self):
        log = logging.getLogger(".".join([__name__, self.__class__.__name__]))
        log.addFilter(logconfig.ThreadContextFilter())
        try:
            t1 = time.time()
            parser = self._get_argument_parser()
            args = parser.parse_args()
            self._process_output_file_path(args.output)
            log.info("Focusstack started, first image, " + args.image_stack[0].name)

            stacker = FocusStack(args.image_stack, args.config)

            focused_image = stacker.composite()
            focused_image.save(args.output)
            calculation_time = time.time() - t1

            extra = {'stack_time': calculation_time}
            log = logging.LoggerAdapter(log, extra)
            log.info("Crystal Match Focusstack finished")

        except IOError as e:
            log.error(e)
    def _log_alignment_status(aligned):
        log = logging.getLogger(".".join([__name__]))
        log.addFilter(logconfig.ThreadContextFilter())
        status = "Unknown"

        if aligned.is_alignment_good():
            status = CrystalMatchStatus(2, "Good Alignment")
        elif aligned.is_alignment_poor():
            status = CrystalMatchStatus(1, "Poor Alignment")
        elif aligned.is_alignment_bad():
            status = CrystalMatchStatus(0, "Alignment failed!")

        json_array = status.to_json_array_with_names('align_stat_num',
                                                     'align_stat')
        json_array.update({'align_score': aligned.overlap_metric()})

        alignment_transform_scale, alignment_transform_offset = aligned.get_alignment_transform(
        )

        match_result = aligned.feature_match_result
        if match_result is not None:
            match_time = '{:.4f}'.format(match_result.time_match())
            transform_time = '{:.4f}'.format(match_result.time_transform())
            scale = '{:.4f}'.format(alignment_transform_scale)
            transform_x = '{:.4f}'.format(alignment_transform_offset.x)
            transform_y = '{:.4f}'.format(alignment_transform_offset.y)
            json_array.update({
                'align_time': match_time,
                'align_trnsf_time:': transform_time,
                'align_scale': scale,
                'align_trnsf_x': transform_x,
                'align_trnsf_y': transform_y
            })  # updates if exists, else adds
        log = logging.LoggerAdapter(log, json_array)
        log.info("Alignment Complete, status: " + status.__str__())
        log.debug(json_array)
Example #26
0
    def print_to_log(self, crystal_id=None):
        """
        Print the current configuration of the CrystalMatch object to the log information. Base information given by
        INFO with detailed info using DEBUG flag.
        :param crystal_id: Optionally print the id for the crystal - can be string or number
        """
        log = logging.getLogger(".".join([__name__]))
        log.addFilter(logconfig.ThreadContextFilter())
        extra = self._status.to_json_array_with_names('match_stat_num',
                                                      'match_stat')

        user_pos_x_px = "{:.2f}".format(self.get_poi_image_1().x)
        user_pos_y_px = "{:.2f}".format(self.get_poi_image_1().y)
        user_pos_x_um = "{:.6f}".format(self.get_poi_image_1_real().x)
        user_pos_y_um = "{:.6f}".format(self.get_poi_image_1_real().y)

        extra.update({
            'user_pos_x_px': user_pos_x_px,
            'user_pos_y_px': user_pos_y_px,
            'user_pos_x_um': user_pos_x_um,
            'user_pos_y_um': user_pos_y_um
        })

        if self.is_success():
            match_mean_error = "{:.4f}".format(
                self._feature_match_result.mean_transform_error())
            match_time = "{:.4f}".format(
                self._feature_match_result.time_match())
            match_transform = "{:.4f}".format(
                self._feature_match_result.time_transform())

            beam_pos_x_px = "{:.2f}".format(self.get_poi_image_2_matched().x)
            beam_pos_y_px = "{:.2f}".format(self.get_poi_image_2_matched().y)
            beam_pos_x_um = "{:.6f}".format(
                self.get_poi_image_2_matched_real().x)
            beam_pos_y_um = "{:.6f}".format(
                self.get_poi_image_2_matched_real().y)

            beam_pos_z = str(self.get_poi_z_level())

            crystal_movement_x_px = "{:.2f}".format(self.get_delta().x)
            crystal_movement_y_px = "{:.2f}".format(self.get_delta().y)
            crystal_movement_x_um = "{:.6f}".format(self.get_delta_real().x)
            crystal_movement_y_um = "{:.6f}".format(self.get_delta_real().y)

            extra.update({
                'match_mean_error': match_mean_error,
                'match_time': match_time,
                'match_transform': match_transform,
                'beam_pos_x_px': beam_pos_x_px,
                'beam_pos_y_px': beam_pos_y_px,
                'beam_pos_x_um': beam_pos_x_um,
                'beam_pos_y_um': beam_pos_y_um,
                'beam_pos_z': beam_pos_z,
                'crystal_movement_x_px': crystal_movement_x_px,
                'crystal_movement_y_px': crystal_movement_y_px,
                'crystal_movement_x_um': crystal_movement_x_um,
                'crystal_movement_y_um': crystal_movement_y_um
            })
        log = logging.LoggerAdapter(log, extra)

        if crystal_id is not None:
            log.info("*** Crystal Match " + str(crystal_id) + " ***")
            log.debug(extra)
        else:
            log.info("*** Crystal Match ***")
            log.debug(extra)