Пример #1
0
    def average_frame(self, frames, shifts):
        """
        Compute an averaged frame from a list of (monochrome) frames along with their
        corresponding shift values.

        :param frames: List of frames to be averaged
        :param shifts: List of shifts for all frames. Each shift is given as [shift_y, shift_x].
        :return: The averaged frame
        """

        if self.intersection_shape is None:
            raise WrongOrderingError(
                "Method 'average_frames' is called before 'align_frames'")

        # "number_frames" are to be averaged.
        number_frames = len(frames)

        # Create an empty numpy buffer. The first dimension is the frame index, the second and
        # third dimenstions are the y and x coordinates.
        buffer = empty([
            number_frames,
            self.intersection_shape[0][1] - self.intersection_shape[0][0],
            self.intersection_shape[1][1] - self.intersection_shape[1][0]
        ])

        # For each frame, cut out the intersection area and copy it to the buffer.
        for idx, frame in enumerate(frames):
            buffer[idx, :, :] = frame[
                self.intersection_shape[0][0] -
                shifts[idx][0]:self.intersection_shape[0][1] - shifts[idx][0],
                self.intersection_shape[1][0] -
                shifts[idx][1]:self.intersection_shape[1][1] - shifts[idx][1]]
        # Compute the mean frame by averaging over the first index.
        self.mean_frame = mean(buffer, axis=0)
        return self.mean_frame
Пример #2
0
    def align_frames(self):
        """
        Compute the displacement of all frames relative to the sharpest frame using the alignment
        rectangle.

        :return: -
        """

        if self.x_low_opt is None:
            raise WrongOrderingError(
                "Method 'align_frames' is called before 'select_alignment_rect'"
            )

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

        # 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_mono[self.frame_ranks_max_index][
            self.y_low_opt:self.y_high_opt, self.x_low_opt:self.x_high_opt]
        self.reference_window_shape = self.reference_window.shape
        for idx, frame in enumerate(self.frames_mono):

            # For the sharpest frame the displacement is 0 because it is used as the reference.
            if idx == self.frame_ranks_max_index:
                self.frame_shifts.append([0, 0])

            # For all other frames: Cut out the alignment patch and compute its translation
            # relative to the reference.
            else:
                frame_window = self.frames_mono[idx][
                    self.y_low_opt:self.y_high_opt,
                    self.x_low_opt:self.x_high_opt]
                self.frame_shifts.append(
                    Miscellaneous.translation(self.reference_window,
                                              frame_window,
                                              self.reference_window_shape))

        # 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]
                                   ]]
        self.intersection_number_pixels = (self.intersection_shape[0][1] -
                                           self.intersection_shape[0][0]) * \
                                          (self.intersection_shape[1][1] -
                                           self.intersection_shape[1][0])
Пример #3
0
    def set_roi(self, y_min, y_max, x_min, x_max):
        """
        Make the stacking region snmaller than the intersection size. Be careful: The pixel
        indices in this method refer to the shape of the intersection of all frames, i.e., the
        shape of the full mean frame. In general, the original frames are somewhat larger.

        If all four index bounds are zero, set the ROI to the full frame.

        :param y_min: Lower y pixel bound
        :param y_max: Upper y pixel bound
        :param x_min: Lower x pixel bound
        :param x_max: Upper x pixel bound
        :return: The new averaged frame, restricted to the ROI
        """

        if self.intersection_shape is None:
            raise WrongOrderingError("Method 'set_roi' is called before 'align_frames'")

        # On the first call, keep a copy of the full mean frame and original intersection shape.
        if not self.ROI_set:
            self.mean_frame_original = self.mean_frame.copy()
            self.intersection_shape_original = self.intersection_shape.copy()

        if y_min == 0 and y_max == 0 and x_min == 0 and x_max == 0:
            y_min = 0
            y_max = self.intersection_shape_original[0][1] - \
                self.intersection_shape_original[0][0]
            x_min = 0
            x_max = self.intersection_shape_original[1][1] - \
                self.intersection_shape_original[1][0]
        elif y_min < 0 or y_max > self.intersection_shape_original[0][1] - \
                self.intersection_shape_original[0][0] or \
                x_min < 0 or x_max > self.intersection_shape_original[1][1] - \
                self.intersection_shape_original[1][0] or \
                y_min >= y_max or x_min >= x_max:
            raise ArgumentError("Invalid ROI index bounds specified")

        # Reduce the intersection shape and mean frame to the ROI.
        self.intersection_shape = [[y_min+self.intersection_shape_original[0][0],
                                    y_max+self.intersection_shape_original[0][0]],
                                   [x_min+self.intersection_shape_original[1][0],
                                    x_max+self.intersection_shape_original[1][0]]]

        # Re-compute global offsets of current frame relative to reference frame.
        self.dy = [self.intersection_shape[0][0] - self.frame_shifts[idx][0] for idx in range(self.frames.number)]
        self.dx = [self.intersection_shape[1][0] - self.frame_shifts[idx][1] for idx in range(self.frames.number)]

        self.ROI_set = True

        self.mean_frame = self.mean_frame_original[y_min:y_max, x_min:x_max]

        return self.mean_frame
    def frames_mono_blurred_laplacian(self, index):
        """
        Look up a Laplacian-of-Gaussian of a frame object with a given index.

        :param index: Frame index
        :return: LoG of a frame with index "index".
        """

        if not 0 <= index < self.number:
            raise ArgumentError("Frame index " + str(index) + " is out of bounds")
        if self.frames_monochrome_blurred_laplacian is not None:
            # print("Accessing LoG number " + str(index))
            return self.frames_monochrome_blurred_laplacian[index]
        else:
            raise WrongOrderingError("Attempt to look up a LoG frame version before computing it")
    def frames_mono(self, index):
        """
        Look up the monochrome version of the frame object with a given index.

        :param index: Frame index
        :return: Monochrome frame with index "index".
        """

        if not 0 <= index < self.number:
            raise ArgumentError("Frame index " + str(index) + " is out of bounds")
        if self.frames_monochrome is not None:
            # print("Accessing frame monochrome " + str(index))
            return self.frames_monochrome[index]
        else:
            raise WrongOrderingError("Attempt to look up a monochrome frame before computing it")
    def frames_mono_blurred(self, index):
        """
        Look up a Gaussian-blurred frame object with a given index.

        :param index: Frame index
        :return: Gaussian-blurred frame with index "index".
        """

        if not 0 <= index < self.number:
            raise ArgumentError("Frame index " + str(index) + " is out of bounds")
        if self.frames_monochrome_blurred is not None:
            # print("Accessing frame with Gaussian blur " + str(index))
            return self.frames_monochrome_blurred[index]
        else:
            raise WrongOrderingError("Attempt to look up a Gaussian-blurred frame version before"
                                     " computing it")
Пример #7
0
    def average_frame(self, average_frame_number=None, color=False):
        """
        Compute an averaged frame from the best (monochrome) frames.

        :param average_frame_number: Number of best frames to be averaged. If None, the number is
                                     computed from the configuration parameter
                                      "align_frames_average_frame_percent"
        :param color: If True, compute an average of the original (color) images. Otherwise use the
                      monochrome frame versions.
        :return: The averaged frame
        """

        if self.intersection_shape is None:
            raise WrongOrderingError(
                "Method 'average_frames' is called before 'align_frames'")

        # Compute global offsets of current frame relative to intersection frame. Start with
        # Initializing lists which for each frame give the dy and dx displacements between the
        # reference frame and current frame.
        self.dy = [
            self.intersection_shape[0][0] - self.frame_shifts[idx][0]
            for idx in range(self.frames.number)
        ]
        self.dx = [
            self.intersection_shape[1][0] - self.frame_shifts[idx][1]
            for idx in range(self.frames.number)
        ]

        # If the number of frames is not specified explicitly, compute it from configuration.
        if average_frame_number is not None:
            self.average_frame_number = average_frame_number
        else:
            self.average_frame_number = max(
                ceil(self.frames.number *
                     self.configuration.align_frames_average_frame_percent /
                     100.), 1)

        shifts = [
            self.frame_shifts[i]
            for i in self.quality_sorted_indices[:self.average_frame_number]
        ]

        # Create an empty numpy buffer. The first and second dimensions are the y and x
        # coordinates. For color frames add a third dimension. Add all frames to the buffer.
        if color:
            self.mean_frame = zeros([
                self.intersection_shape[0][1] - self.intersection_shape[0][0],
                self.intersection_shape[1][1] - self.intersection_shape[1][0],
                3
            ],
                                    dtype=float32)
            for idx in range(self.average_frame_number):
                self.mean_frame += self.frames.frames(self.quality_sorted_indices[idx]) \
                    [self.intersection_shape[0][0] - shifts[idx][0]:
                    self.intersection_shape[0][1] - shifts[idx][0],
                    self.intersection_shape[1][0] - shifts[idx][1]:
                    self.intersection_shape[1][1] - shifts[idx][1], :]
        else:
            self.mean_frame = zeros([
                self.intersection_shape[0][1] - self.intersection_shape[0][0],
                self.intersection_shape[1][1] - self.intersection_shape[1][0]
            ],
                                    dtype=float32)
            for idx in range(self.average_frame_number):
                self.mean_frame += self.frames.frames_mono(self.quality_sorted_indices[idx]) \
                    [self.intersection_shape[0][0] - shifts[idx][0]:
                    self.intersection_shape[0][1] - shifts[idx][0],
                    self.intersection_shape[1][0] - shifts[idx][1]:
                    self.intersection_shape[1][1] - shifts[idx][1]]

        # Compute the mean frame by dividing by the number of frames, and convert values to 16bit.
        if self.frames.dt0 == uint8:
            scaling = 256. / self.average_frame_number
        elif self.frames.dt0 == uint16:
            scaling = 1. / self.average_frame_number
        else:
            raise NotSupportedError(
                "Attempt to compute the average frame from images with type"
                " neither uint8 nor uint16")

        self.mean_frame = (self.mean_frame * scaling).astype(int32)

        return self.mean_frame
Пример #8
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))