def stack_frames(self): """ Compute the shifted contributions of all frames to all alignment points and add them to the appropriate alignment point stacking buffers. :return: - """ # First find out if there are holes between AP patches. self.prepare_for_stack_blending() # Initialize the array for shift distribution statistics. self.shift_distribution = full( (self.configuration.alignment_points_search_width * 2, ), 0, dtype=np_int) self.shift_failure_counter = 0 # If multi-level correlation AP matching is selected, prepare frame-independent data # structures used by this particular search algorithm. if self.configuration.alignment_points_method == 'MultiLevelCorrelation': # Set the two-level reference boxes for all APs. self.alignment_points.set_reference_boxes_correlation() # Compute the "weight matrix" used in the first correlation phase. It penalizes search # results far away from the center. search_width_second_phase = 4 max_search_width_first_phase = int( (self.configuration.alignment_points_search_width - search_width_second_phase) / 2) search_width_first_phase = max_search_width_first_phase extent = 2 * search_width_first_phase + 1 weight_matrix_first_phase = empty((extent, extent), dtype=float32) for y in range(extent): for x in range(extent): weight_matrix_first_phase[ y, x] = 1. - self.configuration.alignment_points_penalty_factor * ( (y / search_width_first_phase - 1)**2 + (x / search_width_first_phase - 1)**2) else: weight_matrix_first_phase = None # In debug mode: Prepare for de-warp visualization. if self.debug: self.create_image_window_signal.emit() # If brightness normalization is switched on, prepare for adjusting frame brightness. if self.configuration.frames_normalization: median_brightness = median(self.frames.frames_average_brightness) # print ("min: " + str(min(self.frames.frames_average_brightness)) + ", median: " # + str(median_brightness) + ", max: " # + str(max(self.frames.frames_average_brightness))) # Go through the list of all frames. for frame_index in range(self.frames.number): # If brightness normalization is switched on, change the brightness of this frame to # the median of all frames. if self.configuration.frames_normalization: frame = self.frames.frames(frame_index) * median_brightness / \ (self.frames.frames_average_brightness[frame_index] + 1.e-7) else: frame = self.frames.frames(frame_index) frame_mono_blurred = self.frames.frames_mono_blurred(frame_index) # After every "signal_step_size"th frame, send a progress signal to the main GUI. if self.progress_signal is not None and frame_index % self.signal_step_size == 1: self.progress_signal.emit( "Stack frames", int(round(10 * frame_index / self.frames.number) * 10)) # Look up the constant shifts of the given frame with respect to the mean frame. dy = self.align_frames.dy[frame_index] dx = self.align_frames.dx[frame_index] # Go through all alignment points for which this frame was found to be among the best. for alignment_point_index in self.frames.used_alignment_points[ frame_index]: alignment_point = self.alignment_points.alignment_points[ alignment_point_index] # Compute the local warp shift for this frame. self.my_timer.start('Stacking: compute AP shifts') [ shift_y, shift_x ], success = self.alignment_points.compute_shift_alignment_point( frame_mono_blurred, frame_index, alignment_point_index, de_warp=self.configuration.alignment_points_de_warp, weight_matrix_first_phase=weight_matrix_first_phase) # Increment the counter corresponding to the 2D warp shift. if success: self.shift_distribution[int( round(sqrt(shift_y**2 + shift_x**2)))] += 1 else: self.shift_failure_counter += 1 # The total shift consists of three components: different coordinate origins for # current frame and mean frame, global shift of current frame, and the local warp # shift at this alignment point. The first two components are accounted for by dy, # dx. total_shift_y = int(round(dy - shift_y)) total_shift_x = int(round(dx - shift_x)) self.my_timer.stop('Stacking: compute AP shifts') # In debug mode: visualize shifted patch of the first AP and compare it with the # corresponding patch of the reference frame. if self.debug and not alignment_point_index: frame_mono_blurred = self.frames.frames_mono_blurred( frame_index) y_low = alignment_point['patch_y_low'] y_high = alignment_point['patch_y_high'] x_low = alignment_point['patch_x_low'] x_high = alignment_point['patch_x_high'] reference_patch = (self.alignment_points.mean_frame[ y_low:y_high, x_low:x_high]).astype(uint16) reference_patch = resize(reference_patch, None, fx=float(self.scale_factor), fy=float(self.scale_factor)) try: # Cut out the globally stabilized and the de-warped patches frame_stabilized = frame_mono_blurred[y_low + dy:y_high + dy, x_low + dx:x_high + dx] frame_stabilized = resize(frame_stabilized, None, fx=float(self.scale_factor), fy=float(self.scale_factor)) font = FONT_HERSHEY_SIMPLEX fontScale = 0.5 fontColor = (0, 255, 0) lineType = 1 putText(frame_stabilized, 'stabilized: ' + str(dy) + ', ' + str(dx), (5, 25), font, fontScale, fontColor, lineType) frame_dewarped = frame_mono_blurred[ y_low + total_shift_y:y_high + total_shift_y, x_low + total_shift_x:x_high + total_shift_x] frame_dewarped = resize(frame_dewarped, None, fx=float(self.scale_factor), fy=float(self.scale_factor)) putText( frame_dewarped, 'de-warped: ' + str(shift_y) + ', ' + str(shift_x), (5, 25), font, fontScale, fontColor, lineType) # Compose the three patches into a single image and send it to the # visualization window. composed_image = Miscellaneous.compose_image( [ frame_stabilized, reference_patch, frame_dewarped ], border=self.border) self.update_image_window_signal.emit(composed_image) except Exception as e: print(str(e)) # Insert a delay to keep the current frame long enough in the visualization # window. sleep(self.image_delay) # Add the shifted alignment point patch to the AP's stacking buffer. self.my_timer.start('Stacking: remapping and adding') self.remap_rigid(frame, alignment_point['stacking_buffer'], total_shift_y, total_shift_x, alignment_point['patch_y_low'], alignment_point['patch_y_high'], alignment_point['patch_x_low'], alignment_point['patch_x_high']) self.my_timer.stop('Stacking: remapping and adding') # If there are holes between AP patches, add this frame's contribution (if any) to the # averaged background image. if self.number_stacking_holes > 0 and \ frame_index in self.align_frames.quality_sorted_indices[ :self.alignment_points.stack_size]: self.my_timer.start('Stacking: computing background') # Treat the case that the background is computed for specific patches only. if self.background_patches: if self.frames.color: for patch in self.background_patches: self.averaged_background[patch['patch_y_low']:patch['patch_y_high'], patch['patch_x_low']:patch['patch_x_high'], :] += \ frame[patch['patch_y_low'] + self.align_frames.dy[frame_index] : patch['patch_y_high'] + self.align_frames.dy[frame_index], patch['patch_x_low'] + self.align_frames.dx[frame_index] : patch['patch_x_high'] + self.align_frames.dx[frame_index], :] else: for patch in self.background_patches: self.averaged_background[patch['patch_y_low']:patch['patch_y_high'], patch['patch_x_low']:patch['patch_x_high']] += \ frame[patch['patch_y_low'] + self.align_frames.dy[frame_index] : patch['patch_y_high'] + self.align_frames.dy[frame_index], patch['patch_x_low'] + self.align_frames.dx[frame_index] : patch['patch_x_high'] + self.align_frames.dx[frame_index]] # The complete background image is computed. else: if self.frames.color: self.averaged_background += frame[ self.align_frames.dy[frame_index]:self.dim_y + self.align_frames.dy[frame_index], self.align_frames.dx[frame_index]:self.dim_x + self.align_frames.dx[frame_index], :] else: self.averaged_background += frame[ self.align_frames.dy[frame_index]:self.dim_y + self.align_frames.dy[frame_index], self.align_frames.dx[frame_index]:self.dim_x + self.align_frames.dx[frame_index]] self.my_timer.stop('Stacking: computing background') if self.progress_signal is not None: self.progress_signal.emit("Stack frames", 100) # Compute counters for shift distribution analysis. shift_counter = sum(self.shift_distribution) self.shift_entries_total = shift_counter + self.shift_failure_counter if self.shift_entries_total: self.shift_failure_percent = round( 100. * self.shift_failure_counter / self.shift_entries_total, 3) else: # If the value is <0, the percentage is not printed. self.shift_failure_percent = -1. # In debug mode: Close de-warp visualization window. if self.debug: self.terminate_image_window_signal.emit() # If a background image is being computed, divide the buffer by the number of contributions. if self.number_stacking_holes > 0: self.my_timer.start('Stacking: computing background') self.averaged_background /= self.alignment_points.stack_size self.my_timer.stop('Stacking: computing background')
def stack_frames(self): """ Compute the shifted contributions of all frames to all alignment points and add them to the appropriate alignment point stacking buffers. :return: - """ # First find out if there are holes between AP patches. self.prepare_for_stack_blending() # Initialize the array for shift distribution statistics. self.shift_distribution = full( (self.configuration.alignment_points_search_width * 2, ), 0, dtype=np_int) # In debug mode: Prepare for de-warp visualization. if self.debug: self.create_image_window_signal.emit() # Go through the list of all frames. for frame_index in range(self.frames.number): frame = self.frames.frames(frame_index) frame_mono_blurred = self.frames.frames_mono_blurred(frame_index) # After every "signal_step_size"th frame, send a progress signal to the main GUI. if self.progress_signal is not None and frame_index % self.signal_step_size == 1: self.progress_signal.emit( "Stack frames", int((frame_index / self.frames.number) * 100.)) # Look up the constant shifts of the given frame with respect to the mean frame. dy = self.align_frames.dy[frame_index] dx = self.align_frames.dx[frame_index] # Go through all alignment points for which this frame was found to be among the best. for alignment_point_index in self.frames.used_alignment_points[ frame_index]: alignment_point = self.alignment_points.alignment_points[ alignment_point_index] # Compute the local warp shift for this frame. self.my_timer.start('Stacking: compute AP shifts') [shift_y, shift_x ] = self.alignment_points.compute_shift_alignment_point( frame_mono_blurred, frame_index, alignment_point_index, de_warp=self.configuration.alignment_points_de_warp) # Increment the counter corresponding to the 2D warp shift. self.shift_distribution[int( round(sqrt(shift_y**2 + shift_x**2)))] += 1 # The total shift consists of three components: different coordinate origins for # current frame and mean frame, global shift of current frame, and the local warp # shift at this alignment point. The first two components are accounted for by dy, # dx. total_shift_y = int(round(dy - shift_y)) total_shift_x = int(round(dx - shift_x)) self.my_timer.stop('Stacking: compute AP shifts') # In debug mode: visualize shifted patch of the first AP and compare it with the # corresponding patch of the reference frame. if self.debug and not alignment_point_index: frame_mono_blurred = self.frames.frames_mono_blurred( frame_index) y_low = alignment_point['patch_y_low'] y_high = alignment_point['patch_y_high'] x_low = alignment_point['patch_x_low'] x_high = alignment_point['patch_x_high'] reference_patch = (self.alignment_points.mean_frame[ y_low:y_high, x_low:x_high]).astype(uint16) reference_patch = resize(reference_patch, None, fx=float(self.scale_factor), fy=float(self.scale_factor)) try: # Cut out the globally stabilized and the de-warped patches frame_stabilized = frame_mono_blurred[y_low + dy:y_high + dy, x_low + dx:x_high + dx] frame_stabilized = resize(frame_stabilized, None, fx=float(self.scale_factor), fy=float(self.scale_factor)) font = FONT_HERSHEY_SIMPLEX fontScale = 0.5 fontColor = (0, 255, 0) lineType = 1 putText(frame_stabilized, 'stabilized: ' + str(dy) + ', ' + str(dx), (5, 25), font, fontScale, fontColor, lineType) frame_dewarped = frame_mono_blurred[ y_low + total_shift_y:y_high + total_shift_y, x_low + total_shift_x:x_high + total_shift_x] frame_dewarped = resize(frame_dewarped, None, fx=float(self.scale_factor), fy=float(self.scale_factor)) putText( frame_dewarped, 'de-warped: ' + str(shift_y) + ', ' + str(shift_x), (5, 25), font, fontScale, fontColor, lineType) # Compose the three patches into a single image and send it to the # visualization window. composed_image = Miscellaneous.compose_image( [ frame_stabilized, reference_patch, frame_dewarped ], border=self.border) self.update_image_window_signal.emit(composed_image) except Exception as e: print(str(e)) # Insert a delay to keep the current frame long enough in the visualization # window. sleep(self.image_delay) # Add the shifted alignment point patch to the AP's stacking buffer. self.my_timer.start('Stacking: remapping and adding') self.remap_rigid(frame, alignment_point['stacking_buffer'], total_shift_y, total_shift_x, alignment_point['patch_y_low'], alignment_point['patch_y_high'], alignment_point['patch_x_low'], alignment_point['patch_x_high']) self.my_timer.stop('Stacking: remapping and adding') # If there are holes between AP patches, add this frame's contribution (if any) to the # averaged background image. if self.number_stacking_holes > 0 and \ frame_index in self.align_frames.quality_sorted_indices[ :self.alignment_points.stack_size]: self.my_timer.start('Stacking: computing background') # Treat the case that the background is computed for specific patches only. if self.background_patches: if self.frames.color: for patch in self.background_patches: self.averaged_background[patch['patch_y_low']:patch['patch_y_high'], patch['patch_x_low']:patch['patch_x_high'], :] += \ frame[patch['patch_y_low'] + self.align_frames.dy[frame_index] : patch['patch_y_high'] + self.align_frames.dy[frame_index], patch['patch_x_low'] + self.align_frames.dx[frame_index] : patch['patch_x_high'] + self.align_frames.dx[frame_index], :] else: for patch in self.background_patches: self.averaged_background[patch['patch_y_low']:patch['patch_y_high'], patch['patch_x_low']:patch['patch_x_high']] += \ frame[patch['patch_y_low'] + self.align_frames.dy[frame_index] : patch['patch_y_high'] + self.align_frames.dy[frame_index], patch['patch_x_low'] + self.align_frames.dx[frame_index] : patch['patch_x_high'] + self.align_frames.dx[frame_index]] # The complete background image is computed. else: if self.frames.color: self.averaged_background += frame[ self.align_frames.dy[frame_index]:self.dim_y + self.align_frames.dy[frame_index], self.align_frames.dx[frame_index]:self.dim_x + self.align_frames.dx[frame_index], :] else: self.averaged_background += frame[ self.align_frames.dy[frame_index]:self.dim_y + self.align_frames.dy[frame_index], self.align_frames.dx[frame_index]:self.dim_x + self.align_frames.dx[frame_index]] self.my_timer.stop('Stacking: computing background') if self.progress_signal is not None: self.progress_signal.emit("Stack frames", 100) # In debug mode: Close de-warp visualization window. if self.debug: self.terminate_image_window_signal.emit() # If a background image is being computed, divide the buffer by the number of contributions. if self.number_stacking_holes > 0: self.my_timer.start('Stacking: computing background') self.averaged_background /= self.alignment_points.stack_size self.my_timer.stop('Stacking: computing background')