Esempio n. 1
0
    def center_of_gravity(frame):
        """
        Comppute (y, x) pixel coordinates of the center of gravity for a given monochrome frame.
        Raise an error if the computed cog is outside the frame index bounds.

        :param frame: Monochrome frame (2D numpy array)
        :return: Integer pixel coordinates (center_y, center_x) of center of gravity
        """

        # Convert the grayscale image to binary image, where all pixels
        # brighter than the half the maximum image brightness are set to 1,
        # and all others are set to 0.
        thresh = threshold(frame, frame.max() / 2, 1, THRESH_BINARY)[1]

        # Calculate moments of binary image
        M = moments(thresh)

        # Calculate coordinates for center of gravity and round pixel
        # coordinates to the nearest integers.
        cog_x = round(M["m10"] / M["m00"])
        cog_y = round(M["m01"] / M["m00"])

        # If the computed center of gravity is outside the frame bounds, raise an error (should be
        # impossible).
        if not 0 < cog_y < frame.shape[0] or not 0 < cog_x < frame.shape[1]:
            raise InternalError("Center of gravity coordinates [" +
                                str(cog_y) + ", " + str(cog_x) +
                                "] of reference frame are out of bounds")

        return cog_y, cog_x
Esempio n. 2
0
 def field(self):
     result = self.raw_field()
     for line in result:
         for i in range(len(line)):
             lst = line[i]
             if (len(lst)) > 1:
                 raise InternalError("Too many objects in cell: {0}".format(lst))
             if line[i]:
                 line[i] = line[i][0]
             else:
                 line[i] = None
     return result
Esempio n. 3
0
    def __init__(self, job_name):
        """
        Initialize a Job object, given its name. The following instance variables are set:
           - name: Path name string coming from the file chooser.
           - file_name: File name string without path.
           - type: Either 'video', or 'image' for stacking jobs, or 'postproc' for postprocessing.
           - bayer_option_selected: Initialized to 'Auto detect color' for file types for which
                                    debayering is supported. Otherwise None.
           - bayer_pattern: Initialized to None

        :param job_name: Name of the job (str)
        """

        self.name = job_name
        path = Path(self.name)
        self.file_name = path.name

        # Bayer patterns are only defined for type 'video'.
        self.bayer_pattern = None
        self.bayer_option_selected = None

        # Set the type of the job based on the file name extension.
        image_extensions = ['.tif', '.tiff', '.fit', '.fits', '.jpg', '.png']
        video_extensions = ['.avi', '.mov', '.mp4', '.ser']

        if path.is_file():
            extension = path.suffix.lower()
            if extension in video_extensions:
                self.type = 'video'
                self.bayer_option_selected = 'Auto detect color'
            elif extension in image_extensions:
                self.type = 'postproc'
            else:
                raise InternalError("Unsupported file type '" + extension +
                                    "' specified for job")
        elif path.is_dir():
            self.type = 'image'
        else:
            raise InternalError(
                "Cannot decide if input file is video or image directory")
Esempio n. 4
0
    def accept(self):
        """
        If the OK button is clicked and the job list has been changed update the job list in the
        parent object.

        :return: -
        """

        image_extensions = ['.tif', '.tiff', '.fit', '.fits', '.jpg', '.png']
        video_extensions = ['.avi', '.ser']
        # Set the job types of all current jobs on the list.
        self.job_types = []
        for job in self.job_names:
            if Path(job).is_file():
                extension = Path(job).suffix.lower()
                if extension in video_extensions:
                    self.job_types.append('video')
                elif extension in image_extensions:
                    self.job_types.append('postproc')
                else:
                    raise InternalError("Unsupported file type '" + extension +
                                        "' specified for job")
            elif Path(job).is_dir():
                self.job_types.append('image')
            else:
                raise InternalError(
                    "Cannot decide if input file is video or image directory")

        # Update the job list and reset the current job index to the first entry.
        self.parent_gui.job_names = self.job_names
        self.parent_gui.job_types = self.job_types
        self.parent_gui.job_number = len(self.job_names)
        self.parent_gui.job_index = 0
        self.parent_gui.activity = "Read frames"
        self.parent_gui.activate_gui_elements(
            [self.parent_gui.ui.box_automatic], True)
        self.parent_gui.update_status()
        self.close()
Esempio n. 5
0
    def center_of_gravity(frame):
        """
        Comppute (y, x) pixel coordinates of the center of gravity for a given monochrome frame.
        Raise an error if the computed cog is outside the frame index bounds.

        :param frame: Monochrome frame (2D numpy array)
        :return: Integer pixel coordinates (center_y, center_x) of center of gravity
        """

        # The following is the old algorithm (up to Version 0.8.5). It does not work well for
        # planets on a bright sky background.
        #
        # Convert the grayscale image to binary image, where all pixels
        # brighter than half the maximum image brightness are set to 1,
        # and all others are set to 0.
        # thresh = threshold(frame, frame.max()/2, 1, THRESH_BINARY)[1]

        # This new code sets the threshold between the minimal brightness (background) and
        # the maximal brightness (object). The hope is that this way the threshold is far away from
        # background noise. Also, no binary image is created, but brightness variations are allowed
        # to influence the center of gravity. This gives brighter parts of the image more weight,
        # which results in a slightly better precision.
        minVal, maxVal, minLoc, maxLoc = minMaxLoc(frame)
        brightness_threshold = int((minVal+maxVal)/2)
        thresh = clip(frame, brightness_threshold, None)[:,:]-brightness_threshold

        # Calculate moments of binary image
        M = moments(thresh)

        # Calculate coordinates for center of gravity and round pixel
        # coordinates to the nearest integers.
        cog_x = round(M["m10"] / M["m00"])
        cog_y = round(M["m01"] / M["m00"])

        # If the computed center of gravity is outside the frame bounds, raise an error (should be
        # impossible).
        if not 0 < cog_y < frame.shape[0] or not 0 < cog_x < frame.shape[1]:
            raise InternalError(
                "Center of gravity coordinates [" + str(cog_y) + ", " + str(
                    cog_x) + "] of reference frame are out of bounds")

        return cog_y, cog_x
Esempio n. 6
0
    def best_frame_indices_in_empty_areas(self, index_y, index_x):
        """
        For a quality area without any alignment point, find the closest quality area with
        alignment points and return its list of frame indices ranked by the local frame quality in
        decreasing order.

        :param index_y: y coordinate of the quality area in the rectangular grid of quality areas
        :param index_x: x coordinate of the quality area in the rectangular grid of quality areas
        :return: frame index list, ranked by the image quality at the closest quality area with
                 alignment points
        """

        # Go though circles with increasing radius "distance" around the current quality area.
        for distance in arange(1, max(self.y_dim, self.x_dim)):
            circle = Miscellaneous.circle_around(index_x, index_y, distance)
            for (compare_x, compare_y) in circle:
                # If the coordinates are within the quality area grid, and if the area at this
                # location has a non-empty list of alignment points, return its list.
                if 0 <= compare_x < self.x_dim and 0 <= compare_y < self.y_dim and \
                        self.quality_areas[compare_y][compare_x]['alignment_point_indices']:
                    return self.quality_areas[compare_y][compare_x]['best_frame_indices']
        # This should never happen, because it means that there is not any quality area with an
        # alignment point.
        raise InternalError("No quality area contains any alignment point")
Esempio n. 7
0
    def align_frames(self):
        """
        Compute the displacement of all frames relative to the sharpest frame using the alignment
        rectangle.

        :return: -
        """

        if self.configuration.align_frames_mode == "Surface":
            # For "Surface" mode the alignment rectangle has to be selected first.
            if self.x_low_opt is None:
                raise WrongOrderingError(
                    "Method 'align_frames' is called before 'select_alignment_rect'"
                )

            # From the sharpest frame cut out the alignment rectangle. The shifts of all other frames
            # will be computed relativ to this patch.
            if self.configuration.align_frames_method == "MultiLevelCorrelation":
                # MultiLevelCorrelation uses two reference windows with different resolution. Also,
                # please note that the data type is float32 in this case.
                reference_frame = self.frames.frames_mono_blurred(
                    self.frame_ranks_max_index).astype(float32)
                self.reference_window = reference_frame[
                    self.y_low_opt:self.y_high_opt,
                    self.x_low_opt:self.x_high_opt]
                # For the first phase a box with half the resolution is constructed.
                self.reference_window_first_phase = self.reference_window[::
                                                                          2, ::
                                                                          2]
            else:
                # For all other methods, the reference window is of type int32.
                reference_frame = self.frames.frames_mono_blurred(
                    self.frame_ranks_max_index).astype(int32)
                self.reference_window = reference_frame[
                    self.y_low_opt:self.y_high_opt,
                    self.x_low_opt:self.x_high_opt]

            self.reference_window_shape = self.reference_window.shape

        elif self.configuration.align_frames_mode == "Planet":
            # For "Planetary" mode compute the center of gravity for the reference image.
            cog_reference_y, cog_reference_x = AlignFrames.center_of_gravity(
                self.frames.frames_mono_blurred(self.frame_ranks_max_index))

        else:
            raise NotSupportedError("Frame alignment mode '" +
                                    self.configuration.align_frames_mode +
                                    "' not supported")

        # Initialize a list which for each frame contains the shifts in y and x directions.
        self.frame_shifts = [None] * self.frames.number

        # Initialize a counter of processed frames for progress bar signalling. It is set to one
        # because in the loop below the optimal frame is not counted.
        number_processed = 1

        # Loop over all frames. Begin with the sharpest (reference) frame
        for idx in chain(reversed(range(self.frame_ranks_max_index + 1)),
                         range(self.frame_ranks_max_index,
                               self.frames.number)):

            if idx == self.frame_ranks_max_index:
                # For the sharpest frame the displacement is 0 because it is used as the reference.
                self.frame_shifts[idx] = [0, 0]
                # Initialize two variables which keep the shift values of the previous step as
                # the starting point for the next step. This reduces the search radius if frames are
                # drifting.
                dy_min_cum = dx_min_cum = 0

            # For all other frames: Compute the global shift, using the "blurred" monochrome image.
            else:
                # After every "signal_step_size"th frame, send a progress signal to the main GUI.
                if self.progress_signal is not None and number_processed % self.signal_step_size == 1:
                    self.progress_signal.emit(
                        "Align all frames",
                        int(
                            round(10 * number_processed / self.frames.number) *
                            10))

                frame = self.frames.frames_mono_blurred(idx)

                # In Planetary mode the shift of the "center of gravity" of the image is computed.
                # This algorithm cannot fail.
                if self.configuration.align_frames_mode == "Planet":

                    cog_frame = AlignFrames.center_of_gravity(frame)
                    self.frame_shifts[idx] = [
                        cog_reference_y - cog_frame[0],
                        cog_reference_x - cog_frame[1]
                    ]

                # In Surface mode various methods can be used to measure the shift from one frame
                # to the next. The method "Translation" is special: Using phase correlation it is
                # the only method not based on a local search algorithm. It is treated differently
                # here because it does not require a re-shifting of the alignment patch.
                elif self.configuration.align_frames_method == "Translation":
                    # The shift is computed with cross-correlation. Cut out the alignment patch and
                    # compute its translation relative to the reference.
                    frame_window = self.frames.frames_mono_blurred(
                        idx)[self.y_low_opt:self.y_high_opt,
                             self.x_low_opt:self.x_high_opt]
                    self.frame_shifts[idx] = Miscellaneous.translation(
                        self.reference_window, frame_window,
                        self.reference_window_shape)

                # Now treat all "Surface" mode cases using local search algorithms. In each case
                # the result is the shift vector [dy_min, dx_min]. The search can fail (if within
                # the search radius no optimum is found). If that happens for at least one frame,
                # an exception is raised. The workflow thread then tries again using another
                # alignment patch.
                else:

                    if self.configuration.align_frames_method == "MultiLevelCorrelation":
                        # The shift is computed in two phases: First on a coarse pixel grid,
                        # and then on the original grid in a small neighborhood around the optimum
                        # found in the first phase.
                        shift_y_local_first_phase, shift_x_local_first_phase, \
                        success_first_phase, shift_y_local_second_phase, \
                        shift_x_local_second_phase, success_second_phase = \
                            Miscellaneous.multilevel_correlation(
                            self.reference_window_first_phase, frame,
                            self.configuration.frames_gauss_width,
                            self.reference_window, self.y_low_opt - dy_min_cum,
                                                          self.y_high_opt - dy_min_cum,
                                                          self.x_low_opt - dx_min_cum,
                                                          self.x_high_opt - dx_min_cum,
                            self.configuration.align_frames_search_width,
                            weight_matrix_first_phase=None)

                        success = success_first_phase and success_second_phase
                        if success:
                            [dy_min, dx_min] = [
                                shift_y_local_first_phase +
                                shift_y_local_second_phase,
                                shift_x_local_first_phase +
                                shift_x_local_second_phase
                            ]

                    elif self.configuration.align_frames_method == "RadialSearch":
                        # Spiral out from the shift position of the previous frame and search for the
                        # local optimum.
                        [dy_min,
                         dx_min], dev_r = Miscellaneous.search_local_match(
                             self.reference_window,
                             frame,
                             self.y_low_opt - dy_min_cum,
                             self.y_high_opt - dy_min_cum,
                             self.x_low_opt - dx_min_cum,
                             self.x_high_opt - dx_min_cum,
                             self.configuration.align_frames_search_width,
                             self.configuration.align_frames_sampling_stride,
                             sub_pixel=False)

                        # The search was not successful if a zero shift was reported after more
                        # than two search cycles.
                        success = len(dev_r) <= 2 or dy_min != 0 or dx_min != 0

                    elif self.configuration.align_frames_method == "SteepestDescent":
                        # Spiral out from the shift position of the previous frame and search for the
                        # local optimum.
                        [dy_min, dx_min
                         ], dev_r = Miscellaneous.search_local_match_gradient(
                             self.reference_window, frame,
                             self.y_low_opt - dy_min_cum,
                             self.y_high_opt - dy_min_cum,
                             self.x_low_opt - dx_min_cum,
                             self.x_high_opt - dx_min_cum,
                             self.configuration.align_frames_search_width,
                             self.configuration.align_frames_sampling_stride,
                             self.dev_table)

                        # The search was not successful if a zero shift was reported after more
                        # than two search cycles.
                        success = len(dev_r) <= 2 or dy_min != 0 or dx_min != 0

                    else:
                        raise NotSupportedError(
                            "Frame alignment method " +
                            configuration.align_frames_method +
                            " not supported")

                    # If the local search was unsuccessful, quit the frame loop with an error.
                    if not success:
                        raise InternalError("frame " + str(idx))

                    # Update the cumulative shift values to be used as starting point for the
                    # next frame.
                    dy_min_cum += dy_min
                    dx_min_cum += dx_min
                    self.frame_shifts[idx] = [dy_min_cum, dx_min_cum]

                    # If the alignment window gets too close to a frame edge, move it away from
                    # that edge by half the border width. First check if the reference window still
                    # fits into the shifted frame.
                    if self.shape[0] - abs(
                            dy_min_cum) - 2 * self.configuration.align_frames_search_width - \
                            self.configuration.align_frames_border_width < \
                            self.reference_window_shape[0] or self.shape[1] - abs(
                            dx_min_cum) - 2 * self.configuration.align_frames_search_width - \
                            self.configuration.align_frames_border_width < \
                            self.reference_window_shape[1]:
                        raise ArgumentError(
                            "Frame stabilization window does not fit into"
                            " intersection")

                    new_reference_window = False
                    # Start with the lower y edge.
                    while self.y_low_opt - dy_min_cum < \
                            self.configuration.align_frames_search_width + \
                            self.configuration.align_frames_border_width / 2:
                        self.y_low_opt += ceil(
                            self.configuration.align_frames_border_width / 2.)
                        self.y_high_opt += ceil(
                            self.configuration.align_frames_border_width / 2.)
                        new_reference_window = True
                    # Now the upper y edge.
                    while self.y_high_opt - dy_min_cum > self.shape[
                        0] - self.configuration.align_frames_search_width - \
                            self.configuration.align_frames_border_width / 2:
                        self.y_low_opt -= ceil(
                            self.configuration.align_frames_border_width / 2.)
                        self.y_high_opt -= ceil(
                            self.configuration.align_frames_border_width / 2.)
                        new_reference_window = True
                    # Now the lower x edge.
                    while self.x_low_opt - dx_min_cum < \
                            self.configuration.align_frames_search_width + \
                            self.configuration.align_frames_border_width / 2:
                        self.x_low_opt += ceil(
                            self.configuration.align_frames_border_width / 2.)
                        self.x_high_opt += ceil(
                            self.configuration.align_frames_border_width / 2.)
                        new_reference_window = True
                    # Now the upper x edge.
                    while self.x_high_opt - dx_min_cum > self.shape[
                        1] - self.configuration.align_frames_search_width - \
                            self.configuration.align_frames_border_width / 2:
                        self.x_low_opt -= ceil(
                            self.configuration.align_frames_border_width / 2.)
                        self.x_high_opt -= ceil(
                            self.configuration.align_frames_border_width / 2.)
                        new_reference_window = True

                    # If the window was moved, update the "reference window(s)".
                    if new_reference_window:
                        if self.configuration.align_frames_method == "MultiLevelCorrelation":
                            self.reference_window = reference_frame[
                                self.y_low_opt:self.y_high_opt,
                                self.x_low_opt:self.x_high_opt]
                            # For the first phase a box with half the resolution is constructed.
                            self.reference_window_first_phase = self.reference_window[::
                                                                                      2, ::
                                                                                      2]
                        else:
                            self.reference_window = reference_frame[
                                self.y_low_opt:self.y_high_opt,
                                self.x_low_opt:self.x_high_opt]

                # This frame is processed, go to next one.
                number_processed += 1

        if self.progress_signal is not None:
            self.progress_signal.emit("Align all frames", 100)

        # Compute the shape of the area contained in all frames in the form [[y_low, y_high],
        # [x_low, x_high]]
        self.intersection_shape = [[
            max(b[0] for b in self.frame_shifts),
            min(b[0] for b in self.frame_shifts) + self.shape[0]
        ],
                                   [
                                       max(b[1] for b in self.frame_shifts),
                                       min(b[1] for b in self.frame_shifts) +
                                       self.shape[1]
                                   ]]
    def align_frames(self):
        """
        Compute the displacement of all frames relative to the sharpest frame using the alignment
        rectangle.

        :return: -
        """

        if self.configuration.align_frames_mode == "Surface":
            # For "Surface" mode the alignment rectangle has to be selected first.
            if self.x_low_opt is None:
                raise WrongOrderingError(
                    "Method 'align_frames' is called before 'select_alignment_rect'"
                )

            # From the sharpest frame cut out the alignment rectangle. The shifts of all other frames
            #  will be computed relativ to this patch.
            self.reference_window = self.frames.frames_mono_blurred(
                self.frame_ranks_max_index)[
                    self.y_low_opt:self.y_high_opt,
                    self.x_low_opt:self.x_high_opt].astype(int32)
            self.reference_window_shape = self.reference_window.shape

        elif self.configuration.align_frames_mode == "Planet":
            # For "Planetary" mode compute the center of gravity for the reference image.
            cog_reference_y, cog_reference_x = AlignFrames.center_of_gravity(
                self.frames.frames_mono_blurred(self.frame_ranks_max_index))

        else:
            raise NotSupportedError("Frame alignment mode '" +
                                    self.configuration.align_frames_mode +
                                    "' not supported")

        # Initialize a list which for each frame contains the shifts in y and x directions.
        self.frame_shifts = [None] * self.frames.number

        # Initialize lists with info on failed frames.
        self.dev_r_list = []
        self.failed_index_list = []

        # Initialize a counter of processed frames for progress bar signalling. It is set to one
        # because in the loop below the optimal frame is not counted.
        number_processed = 1

        # Loop over all frames. Begin with the sharpest (reference) frame
        for idx in chain(reversed(range(self.frame_ranks_max_index + 1)),
                         range(self.frame_ranks_max_index,
                               self.frames.number)):

            if idx == self.frame_ranks_max_index:
                # For the sharpest frame the displacement is 0 because it is used as the reference.
                self.frame_shifts[idx] = [0, 0]
                # Initialize two variables which keep the shift values of the previous step as
                # the starting point for the next step. This reduces the search radius if frames are
                # drifting.
                dy_min_cum = dx_min_cum = 0

            # For all other frames: Compute the global shift, using the "blurred" monochrome image.
            else:
                # After every "signal_step_size"th frame, send a progress signal to the main GUI.
                if self.progress_signal is not None and number_processed % self.signal_step_size == 1:
                    self.progress_signal.emit(
                        "Align all frames",
                        int((number_processed / self.frames.number) * 100.))

                frame = self.frames.frames_mono_blurred(idx)

                if self.configuration.align_frames_mode == "Planet":
                    # In Planetary mode the shift of the "center of gravity" of the image is
                    # computed. This algorithm cannot fail.
                    cog_frame = AlignFrames.center_of_gravity(frame)
                    self.frame_shifts[idx] = [
                        cog_reference_y - cog_frame[0],
                        cog_reference_x - cog_frame[1]
                    ]
                    number_processed += 1
                    continue

                # In "Surface" mode three alignment algorithms can be chosen from. In each case
                # the result is the shift vector [dy_min, dx_min]. The second and third algorithm
                # do a local search. It can fail if within the search radius no minimum is found.
                # The first algorithm (cross-correlation) can fail as well, but in this case there
                # is no indication that this happened.
                elif self.configuration.align_frames_method == "Translation":
                    # The shift is computed with cross-correlation. Cut out the alignment patch and
                    # compute its translation relative to the reference.
                    frame_window = self.frames.frames_mono_blurred(
                        idx)[self.y_low_opt:self.y_high_opt,
                             self.x_low_opt:self.x_high_opt]
                    self.frame_shifts[idx] = Miscellaneous.translation(
                        self.reference_window, frame_window,
                        self.reference_window_shape)
                    continue

                elif self.configuration.align_frames_method == "RadialSearch":
                    # Spiral out from the shift position of the previous frame and search for the
                    # local optimum.
                    [dy_min, dx_min], dev_r = Miscellaneous.search_local_match(
                        self.reference_window,
                        frame,
                        self.y_low_opt - dy_min_cum,
                        self.y_high_opt - dy_min_cum,
                        self.x_low_opt - dx_min_cum,
                        self.x_high_opt - dx_min_cum,
                        self.configuration.align_frames_search_width,
                        self.configuration.align_frames_sampling_stride,
                        sub_pixel=False)
                elif self.configuration.align_frames_method == "SteepestDescent":
                    # Spiral out from the shift position of the previous frame and search for the
                    # local optimum.
                    [dy_min, dx_min
                     ], dev_r = Miscellaneous.search_local_match_gradient(
                         self.reference_window, frame,
                         self.y_low_opt - dy_min_cum,
                         self.y_high_opt - dy_min_cum,
                         self.x_low_opt - dx_min_cum,
                         self.x_high_opt - dx_min_cum,
                         self.configuration.align_frames_search_width,
                         self.configuration.align_frames_sampling_stride,
                         self.dev_table)
                else:
                    raise NotSupportedError("Frame alignment method " +
                                            configuration.align_frames_method +
                                            " not supported")

                # Update the cumulative shift values to be used as starting point for the
                # next frame.
                dy_min_cum += dy_min
                dx_min_cum += dx_min
                self.frame_shifts[idx] = [dy_min_cum, dx_min_cum]

                # In "Surface" mode shift computation can fail if no minimum is found within
                # the pre-defined search radius.
                if len(dev_r) > 2 and dy_min == 0 and dx_min == 0:
                    self.failed_index_list.append(idx)
                    self.dev_r_list.append(dev_r)
                    continue

                # If the alignment window gets too close to a frame edge, move it away from
                # that edge by half the border width. First check if the reference window still
                # fits into the shifted frame.
                if self.shape[0] - abs(
                        dy_min_cum) - 2 * self.configuration.align_frames_search_width - \
                        self.configuration.align_frames_border_width < \
                        self.reference_window_shape[0] or self.shape[1] - abs(
                        dx_min_cum) - 2 * self.configuration.align_frames_search_width - \
                        self.configuration.align_frames_border_width < \
                        self.reference_window_shape[1]:
                    raise ArgumentError(
                        "Frame stabilization window does not fit into"
                        " intersection")
                new_reference_window = False
                # Start with the lower y edge.
                while self.y_low_opt - dy_min_cum < \
                        self.configuration.align_frames_search_width + \
                        self.configuration.align_frames_border_width / 2:
                    self.y_low_opt += ceil(
                        self.configuration.align_frames_border_width / 2.)
                    self.y_high_opt += ceil(
                        self.configuration.align_frames_border_width / 2.)
                    new_reference_window = True
                # Now the upper y edge.
                while self.y_high_opt - dy_min_cum > self.shape[
                    0] - self.configuration.align_frames_search_width - \
                        self.configuration.align_frames_border_width / 2:
                    self.y_low_opt -= ceil(
                        self.configuration.align_frames_border_width / 2.)
                    self.y_high_opt -= ceil(
                        self.configuration.align_frames_border_width / 2.)
                    new_reference_window = True
                # Now the lower x edge.
                while self.x_low_opt - dx_min_cum < \
                        self.configuration.align_frames_search_width + \
                        self.configuration.align_frames_border_width / 2:
                    self.x_low_opt += ceil(
                        self.configuration.align_frames_border_width / 2.)
                    self.x_high_opt += ceil(
                        self.configuration.align_frames_border_width / 2.)
                    new_reference_window = True
                # Now the upper x edge.
                while self.x_high_opt - dx_min_cum > self.shape[
                    1] - self.configuration.align_frames_search_width - \
                        self.configuration.align_frames_border_width / 2:
                    self.x_low_opt -= ceil(
                        self.configuration.align_frames_border_width / 2.)
                    self.x_high_opt -= ceil(
                        self.configuration.align_frames_border_width / 2.)
                    new_reference_window = True
                # If the window was moved, update the "reference_window".
                if new_reference_window:
                    self.reference_window = self.frames.frames_mono_blurred(
                        self.frame_ranks_max_index)[
                            self.y_low_opt:self.y_high_opt,
                            self.x_low_opt:self.x_high_opt].astype(int32)

                number_processed += 1

        if self.progress_signal is not None:
            self.progress_signal.emit("Align all frames", 100)

        # Compute the shape of the area contained in all frames in the form [[y_low, y_high],
        # [x_low, x_high]]
        self.intersection_shape = [[
            max(b[0] for b in self.frame_shifts),
            min(b[0] for b in self.frame_shifts) + self.shape[0]
        ],
                                   [
                                       max(b[1] for b in self.frame_shifts),
                                       min(b[1] for b in self.frame_shifts) +
                                       self.shape[1]
                                   ]]

        if len(self.failed_index_list) > 0:
            raise InternalError("No valid shift computed for " +
                                str(len(self.failed_index_list)) +
                                " frames: " + str(self.failed_index_list))