def _initialize_ovv_im(self, shape): """ Initialize an overview image, i.e. a black DataArray with corresponding metadata. shape (int, int, int): XYC tuple returns: DataArray of shape XYC: a new DataArray, black, with PIXEL_SIZE and POS metadata to fit the stage (active) range (float, float): mpp value """ # Initialize the size of the ovv image with the MD_POS_ACTIVE_RANGE, # fallback to the stage size. # It it's too "big" (> 5cm) fallback to OVV_SHAPE. stg_md = self.main_data.stage.getMetadata() def get_range(an): ax_def = self.main_data.stage.axes[an] rng = None if hasattr(ax_def, "range"): rng = ax_def.range try: rng = stg_md[MD_POS_ACTIVE_RANGE][an] except KeyError: pass except Exception: logging.exception("Failed to get active range for axis %s", an) return rng rng_x = get_range("x") rng_y = get_range("y") mpp = max(MAX_OVV_SIZE / shape[0], MAX_OVV_SIZE / shape[1]) pos = self.m_view.view_pos.value if rng_x is not None and rng_y is not None: max_x = rng_x[1] - rng_x[0] max_y = rng_y[1] - rng_y[0] if max_x < MAX_OVV_SIZE and max_y < MAX_OVV_SIZE: mpp = max(max_x / shape[0], max_y / shape[1]) pos = sum(rng_x) / 2, sum(rng_y) / 2 ovv_im = DataArray(numpy.zeros(shape, dtype=numpy.uint8)) ovv_im.metadata[MD_DIMS] = "YXC" ovv_im.metadata[MD_PIXEL_SIZE] = (mpp, mpp) ovv_im.metadata[MD_POS] = pos return ovv_im, mpp
def constructCube(self, images): # images is a list of 3 dim data arrays. ret = [] for image in images: stack = numpy.dstack(image) stack = numpy.swapaxes(stack, 1, 2) ret.append(stack[0]) # Add back metadata metadata3d = copy.copy(images[0].metadata) # Extend pixel size to 3D ps_x, ps_y = metadata3d[model.MD_PIXEL_SIZE] ps_z = self.zstep.value # Computer cube centre c_x, c_y = metadata3d[model.MD_POS] c_z = self.zstart.value + (self.zstep.value * self.numberOfAcquisitions.value) / 2 metadata3d[model.MD_POS] = (c_x, c_y, c_z) # For a negative pixel size, convert to a positive and flip the z axis if ps_z < 0: ret = numpy.flipud(ret) ps_z = -ps_z metadata3d[model.MD_PIXEL_SIZE] = (ps_x, ps_y, abs(ps_z)) metadata3d[model.MD_DIMS] = "ZYX" ret = DataArray(ret, metadata3d) return ret
def assembleZCube(images, zlevels): """ Construct xyz cube from a z stack of images :param images: (list of DataArray of shape YX) list of z ordered images :param zlevels: (list of float) list of focus positions :return: (DataArray of shape ZYX) the data array of the xyz cube """ # images is a list of 3 dim data arrays. # Will fail on purpose if the images contain more than 2 dimensions ret = numpy.array([im.reshape(im.shape[-2:]) for im in images]) # Add back metadata metadata3d = copy.copy(images[0].metadata) # Extend pixel size to 3D ps_x, ps_y = metadata3d[model.MD_PIXEL_SIZE] ps_z = (zlevels[-1] - zlevels[0]) / (len(zlevels) - 1) if len(zlevels) > 1 else 1e-6 # Compute cube centre c_x, c_y = metadata3d[model.MD_POS] c_z = (zlevels[0] + zlevels[-1]) / 2 # Assuming zlevels are ordered metadata3d[model.MD_POS] = (c_x, c_y, c_z) # For a negative pixel size, convert to a positive and flip the z axis if ps_z < 0: ret = numpy.flipud(ret) ps_z = -ps_z metadata3d[model.MD_PIXEL_SIZE] = (ps_x, ps_y, ps_z) metadata3d[model.MD_DIMS] = "ZYX" ret = DataArray(ret, metadata3d) return ret
def _initialize_ovv_im(self, shape): """ Initialize an overview image, i.e. a black DataArray with corresponding metadata. shape: XYC tuple returns: DataArray of shape XYC, mpp value """ # Initialize the size of the ovv image with the stage size if the stage is small (< 5cm), # otherwise fall back to OVV_SHAPE ax_x = self.main_data.stage.axes["x"] ax_y = self.main_data.stage.axes["y"] mpp = max(MAX_OVV_SIZE / shape[0], MAX_OVV_SIZE / shape[1]) if hasattr(ax_x, "range") and hasattr(ax_y, "range"): max_x = ax_x.range[1] - ax_x.range[0] max_y = ax_y.range[1] - ax_y.range[0] if max_x < MAX_OVV_SIZE and max_y < MAX_OVV_SIZE: mpp = max(max_x / shape[0], max_y / shape[1]) ovv_im = DataArray(numpy.zeros(shape, dtype=numpy.uint8)) ovv_im.metadata[MD_DIMS] = "YXC" ovv_im.metadata[MD_PIXEL_SIZE] = (mpp, mpp) ovv_im.metadata[MD_POS] = self.m_view.view_pos.value return ovv_im, mpp
def _acquireStreamCompressedZStack(self, i, ix, iy, stream): """ Acquire a compressed zstack image for the given stream. The method does the following: - Move focus over the list of zlevels - For each focus level acquire image of the stream - Construct xyz cube for the acquired zstack - Compress the cube into a single image using 'maximum intensity projection' :return DataArray: Acquired da for the current tile stream """ zstack = [] for z in self._zlevels: logging.debug(f"Moving focus for tile {ix}x{iy} to {z}.") stream.focuser.moveAbsSync({'z': z}) da = self._acquireStreamTile(i, ix, iy, stream) zstack.append(da) if self._future._task_state == CANCELLED: raise CancelledError() logging.debug( f"Zstack acquisition for tile {ix}x{iy}, stream {stream.name} finished, compressing data into a single image." ) # Convert zstack into a cube fm_cube = assembleZCube(zstack, self._zlevels) # Save the cube on disk if a log path exists if self._log_path: self._save_tiles(ix, iy, fm_cube, stream_cube_id=self._streams.index(stream)) if self._focusing_method == FocusingMethod.MAX_INTENSITY_PROJECTION: # Compress the cube into a single image (using maximum intensity projection) mip_image = numpy.amax(fm_cube, axis=0) if self._future._task_state == CANCELLED: raise CancelledError() logging.debug( f"Zstack compression for tile {ix}x{iy}, stream {stream.name} finished." ) return DataArray(mip_image, copy.copy(zstack[0].metadata)) else: # TODO: support stitched Z-stacks # For now, the init will raise NotImplementedError in such case logging.warning("Zstack returned as-is, while it is not supported") return fm_cube
def test_nanana(self): self.app.test_frame.SetSize((500, 500)) self.app.test_frame.Center() self.app.test_frame.Layout() # old_canvas = DraggableCanvas(self.panel) tab = self.create_simple_tab_model() mpp = FloatContinuous(10e-6, range=(1e-3, 1), unit="m/px") tab.focussedView.value.mpp = mpp view = tab.focussedView.value canvas = miccanvas.DblMicroscopeCanvas(self.panel) shape = (5, 5, 4) rgb = numpy.empty(shape, dtype=numpy.uint8) rgb[::2, ...] = [ [255, 0, 0, 255], [0, 255, 0, 255], [255, 255, 0, 255], [255, 0, 255, 255], [0, 0, 255, 255] ][:shape[1]] rgb[1::2, ...] = [ [127, 0, 0, 255], [0, 127, 0, 255], [127, 127, 0, 255], [127, 0, 127, 255], [0, 0, 127, 255] ][:shape[1]] rgb[..., [0, 1, 2, 3]] = rgb[..., [2, 1, 0, 3]] darray = DataArray(rgb) canvas.setView(view, tab) self.add_control(canvas, flags=wx.EXPAND, proportion=1) test.gui_loop() # Set the mpp again, because the on_size handler will have recalculated it view.mpp.value = 1 images = [(darray, (0.0, 0.0), (2, 2), True, None, None, None, None, "nanana")] canvas.set_images(images) canvas.scale = 1 canvas.update_drawing() test.gui_loop(0.1)
def generate_img_data(width, height, depth, alpha=255): """ Create an image of the given dimensions """ shape = (height, width, depth) rgb = numpy.empty(shape, dtype=numpy.uint8) if width > 100 or height > 100: tl = random_color(alpha=alpha) tr = random_color(alpha=alpha) bl = random_color(alpha=alpha) br = random_color(alpha=alpha) rgb = numpy.zeros(shape, dtype=numpy.uint8) rgb[..., -1, 0] = numpy.linspace(tr[0], br[0], height) rgb[..., -1, 1] = numpy.linspace(tr[1], br[1], height) rgb[..., -1, 2] = numpy.linspace(tr[2], br[2], height) rgb[..., 0, 0] = numpy.linspace(tl[0], bl[0], height) rgb[..., 0, 1] = numpy.linspace(tl[1], bl[1], height) rgb[..., 0, 2] = numpy.linspace(tl[2], bl[2], height) for i in xrange(height): sr, sg, sb = rgb[i, 0, :3] er, eg, eb = rgb[i, -1, :3] rgb[i, :, 0] = numpy.linspace(int(sr), int(er), width) rgb[i, :, 1] = numpy.linspace(int(sg), int(eg), width) rgb[i, :, 2] = numpy.linspace(int(sb), int(eb), width) if depth == 4: rgb[..., 3] = min(255, max(alpha, 0)) else: for w in xrange(width): for h in xrange(height): rgb[h, w] = random_color((230, 230, 255), alpha) return DataArray(rgb)
def xtest_calc_img_buffer_rect(self): # Setting up test frame self.app.test_frame.SetSize((500, 500)) self.app.test_frame.Center() self.app.test_frame.Layout() test.gui_loop() test.gui_loop() tab = self.create_simple_tab_model() view = tab.focussedView.value # Changes in default values might affect other test, so we need to know self.assertEqual(view.mpp.value, 1e-6, "The default mpp value has changed!") cnvs = miccanvas.DblMicroscopeCanvas(self.panel) cnvs.fit_view_to_next_image = False # Create a even black background, so we can test pixel values cnvs.background_brush = wx.BRUSHSTYLE_SOLID self.add_control(cnvs, flags=wx.EXPAND, proportion=1) test.gui_loop(0.01) # Changes in default values might affect other test, so we need to know self.assertEqual(cnvs.scale, 1, "Default canvas scale has changed!") cnvs.setView(view, tab) # Setting the view, calls _onMPP with the view.mpp value # mpwu / mpp = scale => 1 (fixed, default) / view.mpp (1e-5) self.assertEqual(cnvs.scale, 1 / view.mpp.value) # Make sure the buffer is set at the right size expected_size = tuple(s + 2 * 512 for s in self.app.test_frame.ClientSize) self.assertEqual(cnvs._bmp_buffer_size, expected_size) ############ Create test image ############### img = generate_img_data(100, 100, 4) # 100 pixels is 1e-4 meters img.metadata[model.MD_PIXEL_SIZE] = (1e-6, 1e-6) img.metadata[model.MD_POS] = im_pos = (0, 0) img.metadata[model.MD_DIMS] = "YXC" im_scale = img.metadata[model.MD_PIXEL_SIZE][0] self.assertEqual(im_scale, img.metadata[model.MD_PIXEL_SIZE][0]) stream1 = RGBStream("s1", img) view.addStream(stream1) # Verify view mpp and canvas scale self.assertEqual(view.mpp.value, 1e-6, "Default mpp value has changed!") self.assertEqual(cnvs.scale, 1 / view.mpp.value, "Canvas scale should not have changed!") cnvs.update_drawing() # We're going to control the render size of the image using the # following meter per pixel values mpps = [1e-6, 1e-7, 1e-8] #, 1e-9, 1e-10] # They should set the canvas scales to the following values exp_scales = [1e6, 1e7, 1e8] #, 1e9, 1e10] exp_b_rect = [ (711, 697, 100.0, 100.0), # (261, 247, 1000.0, 1000.0), # (-4239, -4253, 10000.0, 10000.0), ] for mpp, scale, rect in zip(mpps, exp_scales, exp_b_rect): view.mpp.value = mpp self.assertAlmostEqual(scale, cnvs.scale) calc_rect = cnvs._calc_img_buffer_rect(img.shape[:2], im_scale, im_pos) for ev, v in zip(rect, calc_rect): self.assertAlmostEqual(ev, v) test.gui_loop(0.1) stream1 = RGBStream("stream_one", img) # Set the mpp again, because the on_size handler will recalculate it view.mpp._value = 1 # Dummy image shape = (200, 201, 4) rgb = numpy.empty(shape, dtype=numpy.uint8) rgb[...] = 255 darray = DataArray(rgb) logging.getLogger().setLevel(logging.DEBUG) buffer_rect = (0, 0) + cnvs._bmp_buffer_size logging.debug("Buffer size is %s", buffer_rect) im_scales = [0.00001, 0.33564, 0.9999, 1, 1.3458, 2, 3.0, 101.0, 333.5] im_centers = [(0.0, 0.0), (-1.5, 5.2), (340.0, -220.0), (-20.0, -1.0)] canvas.scale = 0.5 # Expected rectangles for the given image scales and canvas scale 0.5 rects = [ (611.9994975, 611.9995, 0.001005, 0.001), (595.13409, 595.218, 33.73182, 33.564), (561.755025, 562.005, 100.48995000000001, 99.99), (561.75, 562.0, 100.5, 100.0), (544.37355, 544.71, 135.2529, 134.58), (511.5, 512.0, 201.0, 200.0), (461.25, 462.0, 301.5, 300.0), (-4463.25, -4438.0, 10150.5, 10100.0), (-16146.375, -16063.0, 33516.75, 33350.0), ] for im_center in im_centers: logging.debug("Center: %s", im_center) for im_scale, rect in zip(im_scales, rects): logging.debug("Scale: %s", im_scale) b_rect = cnvs._calc_img_buffer_rect(darray.shape[:2], im_scale, im_center) for v in b_rect: self.assertIsInstance(v, float) rect = (rect[0] + im_center[0] * cnvs.scale, rect[1] + im_center[1] * cnvs.scale, rect[2], rect[3]) # logging.debug(b_rect) for b, r in zip(b_rect, rect): self.assertAlmostEqual(b, r) canvas.scale = 1.0 # Expected rectangle size for the given image scales and canvas scale 1 rects = [ (611.998995, 611.999, 0.00201, 0.002), (578.26818, 578.436, 67.46364, 67.128), (511.51005, 512.01, 200.97990000000001, 199.98), (511.5, 512.0, 201.0, 200.0), (476.7471, 477.41999999999996, 270.5058, 269.16), (411.0, 412.0, 402.0, 400.0), (310.5, 312.0, 603.0, 600.0), (-9538.5, -9488.0, 20301.0, 20200.0), (-32904.75, -32738.0, 67033.5, 66700.0), ] for im_center in im_centers: logging.debug("Center: %s", im_center) for im_scale, rect in zip(im_scales, rects): logging.debug("Scale: %s", im_scale) b_rect = cnvs._calc_img_buffer_rect(darray.shape[:2], im_scale, im_center) for v in b_rect: self.assertIsInstance(v, float) # logging.debug(b_rect) rect = (rect[0] + im_center[0] * cnvs.scale, rect[1] + im_center[1] * cnvs.scale, rect[2], rect[3]) # logging.debug(b_rect) for b, r in zip(b_rect, rect): self.assertAlmostEqual(b, r) canvas.scale = 2.3 # Expected rectangles for the given image scales and canvas scale 2.3 rects = [ (611.9976885, 611.9977, 0.0046229999999999995, 0.0046), (534.416814, 534.8028, 155.166372, 154.3944), (380.873115, 382.023, 462.25377, 459.95399999999995), (380.85, 382.0, 462.29999999999995, 459.99999999999994), (300.91833, 302.466, 622.16334, 619.068), (149.70000000000005, 152.00000000000006, 924.5999999999999, 919.9999999999999), (-81.44999999999993, -78.0, 1386.8999999999999, 1380.0), (-22734.149999999998, -22618.0, 46692.299999999996, 46460.0), (-76476.525, -76093.0, 154177.05, 153410.0), ] for im_center in im_centers: logging.debug("Center: %s", im_center) for im_scale, rect in zip(im_scales, rects): logging.debug("Scale: %s", im_scale) b_rect = cnvs._calc_img_buffer_rect(darray.shape[:2], im_scale, im_center) for v in b_rect: self.assertIsInstance(v, float) # logging.debug(b_rect) rect = (rect[0] + im_center[0] * cnvs.scale, rect[1] + im_center[1] * cnvs.scale, rect[2], rect[3]) # logging.debug(b_rect) for b, r in zip(b_rect, rect): self.assertAlmostEqual(b, r) logging.getLogger().setLevel(logging.ERROR)