def __init__(self): self._timer: utils.Timer = utils.Timer() self._application: application.Application = application.Application.instance( ) self._source_np: Optional[np.ndarray] = None self._source_cp: Optional[cp.ndarray] = None self.offset: tuples.PixelPoint = tuples.PixelPoint(0, 0) # portal arrays that only change when window is resized self.frame_shape: Optional[tuples.ImageShape] = None self._pan: tuples.PixelPoint = tuples.PixelPoint(0, 0) self._rotation_degrees: float = 0.0 self._scale: float = 1.0 self._scale_point: tuples.PixelPoint = tuples.PixelPoint(0, 0) self._use_gpu: bool = True # numpy transformations self._matrix: Optional[np.ndarray] = None self._vector: Optional[np.ndarray] = None self._source_frame_matrix: Optional[np.ndarray] = None self._source_frame_vector: Optional[np.ndarray] = None # numpy and cupy frame array (both prepared in advance) self._frame_pixels_np: Optional[np.ndarray] = None self._frame_pixels_cp: Optional[cp.ndarray] = None # result (GPU or CPU) self._frame_rgba: Optional[np.ndarray] = None
def total_pan(self) -> tuples.PixelPoint: if self.released_pan_delta is None: return tuples.PixelPoint(x=self.mouse_pan_delta.x, y=self.mouse_pan_delta.y) else: return tuples.PixelPoint( x=self.released_pan_delta.x + self.mouse_pan_delta.x, y=self.released_pan_delta.y + self.mouse_pan_delta.y)
def _fill_boxes(self): rows, cols = self.box_value.shape for row in range(rows): for col in range(cols): if self.box_same[row, col]: # define inside of box bottom_left = tuples.PixelPoint(x=col * self.mesh_step + 1, y=row * self.mesh_step + 1) top_right = tuples.PixelPoint( x=(col + 1) * self.mesh_step - 1, y=(row + 1) * self.mesh_step - 1) self.server.fill_box_request(bottom_left, top_right, self.box_value[row, col])
def source_to_transformed_frame(self, source_point: tuples.PixelPoint): # always uses numpy as just one point source_np = np.array([source_point.x, source_point.y], dtype=float) frame_np = np.matmul(self._source_frame_matrix, source_np) + self._source_frame_vector frame_point = tuples.PixelPoint(x=frame_np[0], y=frame_np[1]) return frame_point
def center_frame_point(self) -> Optional[tuples.ImageShape]: frame_shape = self.frame_shape if frame_shape is None: return None else: center_frame_point = tuples.PixelPoint( x=(self.frame_shape.x - 1) * 0.5, y=(self.frame_shape.y - 1) * 0.5) return center_frame_point
def get_complex_from_frame_point( self, frame_shape: tuples.ImageShape, frame_point: tuples.PixelPoint) -> complex: # print(f"frame_shape: {frame_shape}") # print(f"frame_point: {frame_point}") # print(f"shape: {self.shape}") center_diff = tuples.PixelPoint( x=frame_point.x - 0.5 * (frame_shape.x - 1), y=frame_point.y - 0.5 * (frame_shape.y - 1)) return self.get_complex_from_center_diff(center_diff)
def _calc_offset( self, previous_shape: tuples.ImageShape, new_shape: tuples.ImageShape, pan: Optional[tuples.PixelPoint] = None) -> tuples.PixelPoint: # possibly should be floats, especially if pan is None x = int((previous_shape.x - new_shape.x) / 2.0) y = int((previous_shape.y - new_shape.y) / 2.0) if pan is not None: x += pan.x y += pan.y return tuples.PixelPoint(x, y)
def _update_offset(self): """Called once canvas or frame is updated""" canvas_shape = self._canvas_source.shape frame_shape = self._frame.frame_shape # print(f"canvas_shape: {canvas_shape}") # print(f"frame_shape: {frame_shape}") if canvas_shape is not None and frame_shape is not None: current_frame_offset = self._frame.offset # offset when frame centered in canvas offset = tuples.PixelPoint( x=int((canvas_shape.x - frame_shape.x) / 2.0), y=int((canvas_shape.y - frame_shape.y) / 2.0)) # print(f"offset: {offset}") if current_frame_offset is None or offset != current_frame_offset: self._frame.set_offset(offset)
def add_border(self, mandel: mandelbrot.Mandel): border_size = 14 * 4 * 10 # add 5 large boxes in all directions current_shape = mandel.shape new_shape = tuples.ImageShape(current_shape.x + border_size * 2, current_shape.y + border_size * 2) self.new_mandel = mandel.lite_copy(shape=new_shape, has_border=True) offset = tuples.PixelPoint(x=-border_size, y=-border_size) job = mandelbrot.MandelJob( compute_manager=self._compute_manager, new_mandel=self.new_mandel, prev_mandel=mandel, offset=offset, display_progress=False, # background job save_history=False) self._calc_thread_manager.request_job(job, queue_as=thread.QueueAs.ENQUEUE)
def get_source_point_from_complex( self, z: complex) -> Optional[tuples.PixelPoint]: dz = z - self.centre # take dot products x_dist = dz.real * self.x_unit.real + dz.imag * self.x_unit.imag y_dist = dz.real * self.y_unit.real + dz.imag * self.y_unit.imag x_pixels_from_center = x_dist / self.size_per_gap y_pixels_from_center = y_dist / self.size_per_gap x_pixel = 0.5 * float(self.shape.x - 1) + x_pixels_from_center y_pixel = 0.5 * float(self.shape.y - 1) + y_pixels_from_center x = round(x_pixel) y = round(y_pixel) if 0 <= x <= self.shape.x - 1 and 0 <= y <= self.shape.y - 1: return tuples.PixelPoint(x, y) else: return None
def top_right(self) -> tuples.PixelPoint: return tuples.PixelPoint(x=self.x_max, y=self.y_min)
def bottom_left(self) -> tuples.PixelPoint: return tuples.PixelPoint(x=self.x_min, y=self.y_min)
def mouse_pan_delta(self) -> Optional[tuples.PixelPoint]: if self.pan_start is not None and self.pan_end is not None: return tuples.PixelPoint(x=self.pan_start.x - self.pan_end.x, y=self.pan_start.y - self.pan_end.y) else: return tuples.PixelPoint(0, 0)
def _reset_transform(self): self._pan = tuples.PixelPoint(0, 0) self._rotation_degrees = 0 self._scale = 1.0 self._scale_point = tuples.PixelPoint(0, 0)
def _calculate_transform(self): """takes 0.0001s""" # purely numpy # functions identity = transform.Transform.identity flip_y = transform.Transform.flip_y translate = transform.Transform.translate scale = transform.Transform.scale rotate_degrees = transform.Transform.rotate_degrees # values # source_x = float(self._source.shape[1]) source_y = float(self._source_np.shape[0]) frame_x = float(self.frame_shape.x) frame_y = float(self.frame_shape.y) # cartesian offset when source and portal were first generated, x and y are both positive offset_x = float(self.offset.x) offset_y = float(self.offset.y) # goal is to get from frame_image co-ordinates to transformed source_image co-ordinates # want to do all the transforms in portal cartesian co-ordinates # path portal-image -> portal-cartesian -> portal-cartesian-transformed -> source-cartesian -> source-image # can't directly chain transforms.Affine2D so just deal in matrices # x unchanged, flip y (inverse of itself) frame_image_to_frame_cartesian = flip_y(frame_y) if self._rotation_degrees != 0.0: center_x, centre_y = frame_x * 0.5, frame_y * 0.5 # 1) move center to origin # 2) rotate # 3) move origin back to center # so in reserve order: frame_transform = translate(center_x, centre_y) @ \ rotate_degrees(self._rotation_degrees) @ \ translate(-center_x, -centre_y) elif self._scale != 1.0: center_x, centre_y = frame_x * 0.5, frame_y * 0.5 # 1) move scale_point to origin # 2) scale # 3) move origin back to center # so in reserve order: frame_transform = translate(self._scale_point.x, self._scale_point.y) @ \ scale(self._scale) @ \ translate(-center_x, -centre_y) elif self._pan != tuples.PixelPoint(0, 0): frame_transform = translate(self._pan.x, self._pan.y) else: frame_transform = identity() frame_cartesian_to_source_cartesian = translate(offset_x, offset_y) # x unchanged, flip y (inverse of itself) source_cartesian_to_source_image = flip_y(source_y) # combine matrices using multiplication, so in reserve order: frame_image_to_source_image = source_cartesian_to_source_image @ \ frame_cartesian_to_source_cartesian @ \ frame_transform @ \ frame_image_to_frame_cartesian # final transform for frame_image to source_image self._matrix = frame_image_to_source_image[0:2, 0:2] self._vector = frame_image_to_source_image[0:2, 2] # source_point to frame_point transform source_cartesian_to_frame_cartesian = translate(-offset_x, -offset_y) inv_frame_transform = np.linalg.inv(frame_transform) # combine matrices using multiplication, so in reserve order: source_cartesian_to_frame_point = inv_frame_transform @ \ source_cartesian_to_frame_cartesian self._source_frame_matrix = source_cartesian_to_frame_point[0:2, 0:2] self._source_frame_vector = source_cartesian_to_frame_point[0:2, 2]
def _frame_image_to_frame_cartesian(self, x: int, y: int) -> tuples.PixelPoint: frame_y = self._view_state.frame_shape.y frame_point = tuples.PixelPoint(x=x, y=frame_y - y) return frame_point
def on_resized(self, new_image_shape: tuples.ImageShape) -> tuples.ImageShape: self.clear_graph() min_length = min(new_image_shape.x, new_image_shape.y) self._image_shape = tuples.PixelPoint(x=min_length, y=min_length) return self._image_shape
def run(self) -> np.ndarray: bottom_left = tuples.PixelPoint(0, 0) top_right = tuples.PixelPoint(x=self.shape.x, y=self.shape.y) self.server.box_request(bottom_left, top_right) self.server.serve() return self.server.iteration_cpu