def test_gradient_z(): test = cle.push_zyx( np.asarray([[[0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0], [1, 1, 1, 0, 0], [1, 1, 1, 0, 0], [1, 1, 1, 0, 0], [0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 0]]])) reference = cle.push_zyx( np.asarray([[[0, 0, 0, 0, 0], [1, 0, 0, -1, 0], [1, 0, 0, -1, 0], [1, 0, 0, -1, 0], [0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0], [-1, 0, 0, 1, 0], [-1, 0, 0, 1, 0], [-1, 0, 0, 1, 0], [0, 0, 0, 0, 0]]])) result = cle.create(test) cle.gradient_z(test, result) a = cle.pull_zyx(result) b = cle.pull_zyx(reference) print(a) assert (np.array_equal(a, b))
def test_extend_labeling_via_voronoi(): gpu_input = cle.push( np.asarray([[ [0, 0, 0, 0, 3, 3], [0, 4, 0, 0, 3, 3], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 2, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0], ]])) gpu_reference = cle.push( np.asarray([[ [4, 4, 4, 3, 3, 3], [4, 4, 4, 3, 3, 3], [4, 4, 4, 3, 3, 3], [2, 2, 2, 1, 1, 1], [2, 2, 2, 1, 1, 1], [2, 2, 2, 1, 1, 1], ]])) gpu_output = cle.extend_labeling_via_voronoi(gpu_input) a = cle.pull_zyx(gpu_output) b = cle.pull_zyx(gpu_reference) print(a) print(b) assert (np.array_equal(a, b))
def test_flip(): test = cle.push_zyx(np.asarray([ [0, 0, 0, 0, 0], [0, 1, 2, 0, 0], [0, 1, 2, 0, 0], [0, 1, 3, 0, 0], [0, 0, 0, 0, 0] ])) reference = cle.push_zyx(np.asarray([ [0, 0, 0, 0, 0], [0, 0, 2, 1, 0], [0, 0, 2, 1, 0], [0, 0, 3, 1, 0], [0, 0, 0, 0, 0] ])) result = cle.create(test) cle.flip(test, result, True, False, False) print(result) a = cle.pull_zyx(result) b = cle.pull_zyx(reference) assert (np.array_equal(a, b))
def test_generate_distance_matrix(): gpu_input = cle.push(np.asarray([ [0, 0, 0, 0, 0], [0, 1, 0, 3, 0], [0, 0, 0, 0, 0], [0, 0, 2, 0, 0], [0, 0, 0, 0, 4] ])) gpu_reference = cle.push(np.asarray([ [0., 0. , 0. , 0. , 0. ], [0., 0. , 2.236068 , 2. , 4.2426405 ], [0., 2.236068 , 0. , 2.236068 , 2.236068 ], [0., 2. , 2.236068 , 0. , 3.1622777 ], [0., 4.2426405, 2.236068 , 3.1622777 , 0. ] ])) gpu_pointlist = cle.labelled_spots_to_pointlist(gpu_input) gpu_distance_matrix = cle.generate_distance_matrix(gpu_pointlist, gpu_pointlist) a = cle.pull_zyx(gpu_distance_matrix) b = cle.pull_zyx(gpu_reference) print(a) print(b) assert (np.allclose(a, b, 0.001))
def test_dilate_box_slice_by_slice(): test = cle.push_zyx( np.asarray([[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]])) reference = cle.push_zyx( np.asarray([[[0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0], [1, 1, 1, 0, 0], [1, 1, 1, 0, 0], [1, 1, 1, 0, 0], [0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 0]]])) result = cle.create(test) cle.dilate_box_slice_by_slice(test, result) a = cle.pull_zyx(result) b = cle.pull_zyx(reference) print(a) assert (np.array_equal(a, b))
def test_labelled_spots_to_pointlist(): gpu_input = cle.push(np.asarray([ [0, 0, 0, 0, 0], [0, 1, 0, 3, 0], [0, 0, 0, 0, 0], [0, 0, 2, 0, 0], [0, 0, 0, 0, 4] ])) gpu_reference = cle.push(np.asarray([ [1, 1], [3, 2], [1, 3], [4, 4] ])) gpu_output = cle.labelled_spots_to_pointlist(gpu_input) a = cle.pull_zyx(gpu_output) b = cle.pull_zyx(gpu_reference) print(a) print(b) assert (np.array_equal(a, b))
def test_voronoi_labeling(): gpu_input = cle.push( np.asarray([[ [0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0], ]])) gpu_reference = cle.push( np.asarray([[ [1, 1, 1, 2, 2, 2], [1, 1, 1, 2, 2, 2], [1, 1, 1, 2, 2, 2], [3, 3, 3, 4, 4, 4], [3, 3, 3, 4, 4, 4], [3, 3, 3, 4, 4, 4], ]])) gpu_output = cle.voronoi_labeling(gpu_input) a = cle.pull_zyx(gpu_output) b = cle.pull_zyx(gpu_reference) print(a) print(b) assert (np.array_equal(a, b))
def test_connected_components_labeling_box(): gpu_input = cle.push(np.asarray([[[1, 0, 1], [1, 0, 0], [0, 0, 1]]])) gpu_reference = cle.push(np.asarray([[[1, 0, 2], [1, 0, 0], [0, 0, 3]]])) gpu_output = cle.connected_components_labeling_box(gpu_input) a = cle.pull_zyx(gpu_output) b = cle.pull_zyx(gpu_reference) print(a) print(b) assert (np.array_equal(a, b))
def test_maximum_x_projection_of_pointlist(): positions_and_values = cle.push_zyx( np.asarray([[0, 0, 2, 3, 5], [0, 1, 3, 2, 6]])) reference = cle.push_zyx(np.asarray([[5], [6]])) result = cle.maximum_x_projection(positions_and_values) a = cle.pull_zyx(result) b = cle.pull_zyx(reference) print(a) print(b) assert (np.array_equal(a, b))
def test_multiply_image_and_coordinate(): test1 = cle.push_zyx( np.asarray([[0, 0, 0, 0, 0], [1, 1, 1, 1, 1], [2, 2, 2, 2, 2]])) reference = cle.push_zyx( np.asarray([[0, 0, 0, 0, 0], [0, 1, 2, 3, 4], [0, 2, 4, 6, 8]])) result = cle.create(test1) cle.multiply_image_and_coordinate(test1, result, 0) a = cle.pull_zyx(result) b = cle.pull_zyx(reference) print(a) assert (np.array_equal(a, b))
def test_close_index_gaps_in_label_maps(): gpu_input = cle.push(np.asarray([[[1, 2, 3], [1, 6, 6], [7, 8, 9]]])) gpu_output = cle.create_like(gpu_input) gpu_reference = cle.push(np.asarray([[[1, 2, 3], [1, 4, 4], [5, 6, 7]]])) cle.close_index_gaps_in_label_map(gpu_input, gpu_output) a = cle.pull_zyx(gpu_output) b = cle.pull_zyx(gpu_reference) print(a) print(b) assert (np.array_equal(a, b))
def test_histogram_against_scikit_image(): from skimage.data import camera image = camera() from skimage import exposure hist, bc = exposure.histogram(image.ravel(), 256, source_range='image') print(str(hist)) gpu_image = cle.push(image) gpu_hist = cle.histogram(gpu_image, num_bins=256) print(str(cle.pull_zyx(gpu_hist))) assert (np.allclose(hist, cle.pull_zyx(gpu_hist)))
def test_generate_touch_matrix(): gpu_input = cle.push( np.asarray([[1, 1, 0, 0, 0], [1, 1, 0, 3, 0], [0, 2, 2, 3, 0], [0, 2, 2, 0, 0], [0, 0, 0, 0, 4]])) gpu_reference = cle.push( np.asarray([[0, 1, 1, 1, 1], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]])) gpu_touch_matrix = cle.generate_touch_matrix(gpu_input) a = cle.pull_zyx(gpu_touch_matrix) b = cle.pull_zyx(gpu_reference) print(a) print(b) assert (np.allclose(a, b, 0.001))
def workflow(input: Image, sigma=3, threshold : float = 30) -> Labels: if input: # push image to GPU memory and show it gpu_input = cle.push_zyx(input.data) # Spot detection # After some noise removal/smoothing, we perform a local maximum detection # gaussian blur gpu_blurred = cle.gaussian_blur(gpu_input, sigma_x=sigma, sigma_y=sigma, sigma_z=0) # detect maxima gpu_detected_maxima = cle.detect_maxima_box(gpu_blurred) # Spot curation # Now, we remove spots with values below a certain intensity and label the remaining spots # threshold gpu_thresholded = cle.greater_constant(gpu_blurred, constant= threshold * 10) # mask gpu_masked_spots = cle.mask(gpu_detected_maxima, gpu_thresholded) # label spots gpu_labelled_spots = cle.connected_components_labeling_box(gpu_masked_spots) number_of_spots = cle.maximum_of_all_pixels(gpu_labelled_spots) print("Number of detected spots: " + str(number_of_spots)) # Expanding labelled spots # Next, we spatially extend the labelled spots by applying a maximum filter. # label map closing number_of_dilations = 10 number_of_erosions = 4 flip = cle.create_like(gpu_labelled_spots) flop = cle.create_like(gpu_labelled_spots) flag = cle.create([1,1,1]) cle.copy(gpu_labelled_spots, flip) for i in range (0, number_of_dilations) : cle.onlyzero_overwrite_maximum_box(flip, flag, flop) cle.onlyzero_overwrite_maximum_diamond(flop, flag, flip) flap = cle.greater_constant(flip, constant= 1) for i in range(0, number_of_erosions): cle.erode_box(flap, flop) cle.erode_sphere(flop, flap) gpu_labels = cle.mask(flip, flap) output = cle.pull_zyx(gpu_labels) return output
def clij_filter(input: Image, sigma=2, threshold: float = 300) -> Image: if input: # push image to GPU memory and show it gpu_input = cle.push_zyx(input.data) gpu_output = mesh_data(gpu_input, sigma, threshold) output = cle.pull_zyx(gpu_output) return output
def test_detect_maxima_box(): gpu_input = cle.push( np.asarray([[0, 0, 0, 0, 0], [1, 0, 0, 2, 0], [1, 2, 1, 3, 2], [0, 1, 0, 2, 0], [0, 0, 0, 0, 0]])) gpu_output = cle.create_like(gpu_input) gpu_reference = cle.push( np.asarray([[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 1, 0, 1, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]])) cle.detect_maxima_box(gpu_input, gpu_output) a = cle.pull_zyx(gpu_output) b = cle.pull_zyx(gpu_reference) print(a) print(b) assert (np.array_equal(a, b))
def block_enum(source, blocksize): flagged_indices = cle.push_zyx(source) max_label = source.shape[1] - 1 block_sums = cle.create([1, int((int(max_label) + 1) / blocksize) + 1]) cle.sum_reduction_x(flagged_indices, block_sums, blocksize) # distribute new numbers new_indices = cle.create([1, int(max_label) + 1]) cle.block_enumerate(flagged_indices, block_sums, new_indices, blocksize) return cle.pull_zyx(new_indices)
def process_image(input: Image, sigma: float = 5) -> Labels: if input: # push image to GPU input = cle.push_zyx(input.data) # process the mage blurred = cle.gaussian_blur(input, sigma_x=sigma, sigma_y=sigma) binary = cle.threshold_otsu(blurred) labels = cle.connected_components_labeling_box(binary) # pull result back output = cle.pull_zyx(labels) return output
def test_subtract_image_from_scalar(): test1 = cle.push_zyx(np.asarray([ [0, 0], [1, 1], [2, 2] ])) reference = cle.push_zyx(np.asarray([ [5, 5], [4, 4], [3, 3] ])) result = cle.create(test1) cle.subtract_image_from_scalar(test1, result, 5) a = cle.pull_zyx(result) b = cle.pull_zyx(reference) print(a) assert (np.array_equal(a, b))
def label(input1: Image, operation: Label) -> Image: if input1 is not None: cle_input1 = cle.push_zyx(input1.data) output = cle.create_like(cle_input1) operation(cle_input1, output) output = cle.pull_zyx(output) # workaround to cause a auto-contrast in the viewer after returning the result if Gui.global_last_filter_applied is not None: viewer.layers.remove_selected() Gui.global_last_filter_applied = operation return output
def test_multiply_image_and_scalar(): test1 = cle.push_zyx(np.asarray([ [0, 0, 0, 0, 0], [1, 1, 1, 1, 1], [2, 2, 2, 2, 2] ])) reference = cle.push_zyx(np.asarray([ [0, 0, 0, 0, 0], [2, 2, 2, 2, 2], [4, 4, 4, 4, 4] ])) result = cle.create(test1) cle.multiply_image_and_scalar(test1, result, 2) a = cle.pull_zyx(result) b = cle.pull_zyx(reference) print(a) assert (np.array_equal(a, b))
def filter(input: Image, operation: Filter, x: float = 1, y: float = 1, z: float = 0) -> Image: if input: cle_input = cle.push_zyx(input.data) output = cle.create_like(cle_input) operation(cle_input, output, x, y, z) output = cle.pull_zyx(output) # workaround to cause a auto-contrast in the viewer after returning the result if Gui.global_last_filter_applied is not None: viewer.layers.remove_selected() Gui.global_last_filter_applied = operation return output
def main(): import napari from skimage.io import imread import pyclesperanto_prototype as cle from napari_pyclesperanto_assistant._gui._Assistant import Assistant import sys if len(sys.argv) > 1: filename = str(sys.argv[1]) image = imread(filename) else: # make some artificial cell image filename = "undefined.tif" labels = cle.artificial_tissue_2d(width=512, height=512, delta_x=48, delta_y=32, random_sigma_x=6, random_sigma_y=6) membranes = cle.detect_label_edges(labels) eroded = cle.maximum_sphere(membranes, radius_x=3, radius_y=3) blurred = cle.gaussian_blur(eroded, sigma_x=3, sigma_y=3) image = cle.pull_zyx(blurred) #image = imread('https://samples.fiji.sc/blobs.png') #image = imread('C:/structure/data/lund_000500_resampled.tif') #filename = 'data/Lund_000500_resampled-cropped.tif' #filename = str(Path(__file__).parent) + '/data/CalibZAPWfixed_000154_max-16.tif' print("Available GPUs: " + str(cle.available_device_names())) cle.select_device("rtx") print("Used GPU: " + str(cle.get_device())) with napari.gui_qt(): # create a viewer and add some image viewer = napari.Viewer() layer = viewer.add_image(image, metadata={'filename': filename}) from napari_pyclesperanto_assistant import napari_plugin napari_plugin(viewer)
height = 1024 depth = 71 voxel_size = [3, 0.6934, 0.6934] #image = np.empty((71, 1024, 512), np.uint16) import beetlesafari as bs img_arr = bs.imread_raw(filename, width, height, depth) import pyclesperanto_prototype as cle print("Shape before resampling: " + str(img_arr.shape)) buffer = cle.push_zyx(img_arr) resampled = cle.resample(buffer, factor_x=voxel_size[2], factor_y=voxel_size[1], factor_z=voxel_size[0]) img_arr = cle.pull_zyx(resampled) print("Shape after resampling: " + str(img_arr.shape)) # print(img_arr) # Start up napari import napari with napari.gui_qt(): viewer = napari.Viewer() viewer.add_image(img_arr, name='Tribolium')
def game_step(self): """Forwards the game by one step and computes the new playground Returns ------- an image with the current state of the game """ # check player positions self.player1_position = self._check_player_position(self.player1_position) self.player2_position = self._check_player_position(self.player2_position) # move puck self.puck_x = self.puck_x + self.puck_delta_x self.puck_y = self.puck_y + self.puck_delta_y # check puck_position if self.puck_y < 0 or self.puck_y > self.height: self.puck_delta_y = -self.puck_delta_y self.puck_y = self.puck_y + self.puck_delta_y # puck at player 1 if self.puck_x <= self.player1_x: self.puck_delta_x = -self.puck_delta_x self.puck_x = self.puck_x + self.puck_delta_x if abs(self.puck_y - self.player1_position) > self.bar_radius: # player 2 scores self.player2_score = self.player2_score + 1 self._level_up() else: self.puck_delta_y = (self.puck_y - self.player1_position) / self.bar_radius * 5 # puck at player 2 if self.puck_x >= self.player2_x: self.puck_delta_x = -self.puck_delta_x self.puck_x = self.puck_x + self.puck_delta_x if abs(self.puck_y - self.player2_position) > self.bar_radius: # player 1 scores self.player1_score = self.player1_score + 1 self._level_up() else: self.puck_delta_y = (self.puck_y - self.player2_position) / self.bar_radius * 5 # show game status self.result_label.setText(str(self.player1_score) + ":" + str(self.player2_score)) # draw playground cle.set(self.playground, 0.1) # draw player 1 cle.draw_box(self.playground, self.player1_x, self.player1_position - self.bar_radius, 0, 3, self.bar_radius * 2, 0) # draw player 2 cle.draw_box(self.playground, self.player2_x, self.player2_position - self.bar_radius, 0, 3, self.bar_radius * 2, 0) # draw puck cle.draw_sphere(self.playground, self.puck_x, self.puck_y, 0, 5, 3, 1) # return playground image = cle.pull_zyx(self.playground) return image
def game_step(self): """Forwards the game by one step and computes the new playground Returns ------- an image with the current state of the game """ # check player positions self.player1_y, self.player1_z = self._check_player_position( self.player1_y, self.player1_z) self.player2_y, self.player2_z = self._check_player_position( self.player2_y, self.player2_z) # move puck self.puck_x = self.puck_x + self.puck_delta_x self.puck_y = self.puck_y + self.puck_delta_y self.puck_z = self.puck_z + self.puck_delta_z # check puck_position if self.puck_y < 0 or self.puck_y > self.height: self.puck_delta_y = -self.puck_delta_y self.puck_y = self.puck_y + self.puck_delta_y if self.puck_z < 0 or self.puck_z > self.depth: self.puck_delta_z = -self.puck_delta_z self.puck_z = self.puck_z + self.puck_delta_z # puck at player 1 if self.puck_x <= self.player1_x: self.puck_delta_x = -self.puck_delta_x self.puck_x = self.puck_x + self.puck_delta_x if abs(self.puck_y - self.player1_y) > self.bar_radius or abs( self.puck_z - self.player1_z) > self.bar_radius: # player 2 scores self.player2_score = self.player2_score + 1 self._level_up() else: self.puck_delta_y = (self.puck_y - self.player1_y) / self.bar_radius * 5 self.puck_delta_z = (self.puck_z - self.player1_z) / self.bar_radius * 5 # puck at player 2 if self.puck_x >= self.player2_x: self.puck_delta_x = -self.puck_delta_x self.puck_x = self.puck_x + self.puck_delta_x if abs(self.puck_y - self.player2_y) > self.bar_radius or abs( self.puck_z - self.player2_z) > self.bar_radius: # player 1 scores self.player1_score = self.player1_score + 1 self._level_up() else: self.puck_delta_y = (self.puck_y - self.player2_y) / self.bar_radius * 5 self.puck_delta_z = (self.puck_z - self.player2_z) / self.bar_radius * 5 # show game status self.result_label.setText( str(self.player1_score) + ":" + str(self.player2_score)) # draw playground cle.set(self.playground, 0.0) # draw player 1 cle.draw_box(self.playground, self.player1_x, self.player1_y - self.bar_radius, self.player1_z - self.bar_radius, 3, self.bar_radius * 2, self.bar_radius * 2) # draw player 2 cle.draw_box(self.playground, self.player2_x, self.player2_y - self.bar_radius, self.player2_z - self.bar_radius, 3, self.bar_radius * 2, self.bar_radius * 2) # draw puck cle.draw_sphere(self.playground, self.puck_x, self.puck_y, self.puck_z, 5, 3, 3, 1) # put z-color coding on playground cle.multiply_images(self.playground, self.gradient, self.view) # return playground image = cle.pull_zyx(self.view) return image
def game_step(self): """Forwards the game by one step and computes the new playground Returns ------- an image with the current state of the game """ self.result_label.setText( str(self.player1_score) + " : " + str(self.player2_score)) # move player 1 result = self.move_player(self.player1_positions, self.player1_delta_x, self.player1_delta_y, self.player1_score) if result is None: return cle.pull_zyx(self.playground) self.player1_positions = result # move player 2 result = self.move_player(self.player2_positions, self.player2_delta_x, self.player2_delta_y, self.player2_score) if result is None: return cle.pull_zyx(self.playground) self.player2_positions = result # go through food positions and check if a player ate it new_food_positions = [] for pos in self.food_positions: if pos == self.player1_positions[0]: self.player1_score = self.player1_score + self.food_calories elif pos == self.player2_positions[0]: self.player2_score = self.player2_score + self.food_calories else: new_food_positions.append(pos) self.food_positions = new_food_positions # seed new food from time to time if len(self.food_positions) < self.maximum_food_available: random_x = (int(np.random.random_sample() * self.width / self.pixel_size - 2) + 1) * self.pixel_size random_y = (int(np.random.random_sample() * self.height / self.pixel_size - 2) + 1) * self.pixel_size if [random_x, random_y] not in self.player1_positions and \ [random_x, random_y] not in self.player2_positions and \ [random_x, random_y] not in self.food_positions: self.food_positions.append([random_x, random_y]) # draw playground frame cle.draw_box(self.temp, 0, 0, 0, self.width, self.height, 1, 4) cle.draw_box(self.temp, 1, 1, 0, self.width - 3, self.height - 3, 1, 0) # draw players and food self.draw_positions(self.player1_positions, 2) self.draw_positions(self.player2_positions, 7) self.draw_positions(self.food_positions, 10) cle.maximum_sphere(self.temp, self.playground, self.pixel_size / 2, self.pixel_size / 2) # return playground image = cle.pull_zyx(self.playground) return image
# Start napari viewer with napari.gui_qt(): viewer = napari.Viewer() # Load image image = imread("C:/Users/rober/AppData/Local/Temp/temp1605606091856.tif") # Push temp1605606091856.tif to GPU memory image1 = cle.push_zyx(image) # Copy image2 = cle.create_like(image1) cle.copy(image1, image2) # show result viewer.add_image(cle.pull_zyx(image2), scale=(1.0, 1.0)) # Gaussian Blur2D image3 = cle.create_like(image2) sigma_x = 16.0 sigma_y = 16.0 cle.gaussian_blur(image2, image3, sigma_x, sigma_y) # show result viewer.add_image(cle.pull_zyx(image3), scale=(1.0, 1.0)) # Greater image4 = cle.create_like(image2) cle.greater(image2, image3, image4) # show result viewer.add_image(cle.pull_zyx(image4), scale=(1.0, 1.0))