def psf_rescale_centre_skew_pad( psf: np.ndarray, dz_ratio_galvo_stage: float, centre: Collection[float], output_shape: Collection[int], deskewfactor: Optional[float] = None, interpolation: int = 3, ) -> Tuple[np.ndarray, np.ndarray]: """ TODO: use psf_rescale_centre_skew_pad_twostep instead Given a * psf: volume (numpy array) * centre: centre coordinate of bead in volume * dz_ratio_galvo_stage: scaling factor along z axis (this accounts for different z scaling between galvo and stage) * output_shape: desired output shape of array * deskewfactor: if not None (default), will skew the psf for direct deconvolution on the skewed data. Returns: tuple (processed_psf, transform_matrix) """ scale_psf = scale_pixel_z(dz_ratio_galvo_stage) shift = shift_centre(2 * np.array(centre)) unshift = unshift_centre(output_shape) if deskewfactor: skew = inv(deskew_mat(deskewfactor)) else: skew = np.eye(4) # no skew, identity logger.debug(f"deskew factor {deskewfactor}") combined_transform = unshift @ skew @ scale_psf @ shift processed_psf = affine_transform( psf, inv(combined_transform), output_shape=output_shape, order=interpolation ) return processed_psf, combined_transform
def psf_rescale_centre_skew_pad_twostep( psf: np.ndarray, dz_ratio_galvo_stage: float, centre: Collection[float], output_shape: Collection[int], deskewfactor: Optional[float] = None, interpolation: int = 3, ) -> np.ndarray: """Rescale, skew and centre a PSF to match the z-spacing and skew of the data This implementation uses two sequential affine transforms (scale, skew) to achieve this as a single transform might cause interpolation between voxels that correspond to different physical spacing. Parameters ---------- psf : np.ndarray point spread function (volume scan of single bead) dz_ratio_galvo_stage : float scaling factor along z axis (this accounts for different z scaling between galvo and stage) centre : Collection[float] centre coordinate of bead in volume output_shape : Collection[int] desired output shape of processed PSF array deskewfactor : Optional[float], optional if not None (default), will skew the psf for direct deconvolution on the skewed data. interpolation : int, optional interpolation order Returns ------- np.ndarray processed PSF """ # Step 1: Scaling to same z step as image data to deconvole scale_psf = scale_pixel_z(dz_ratio_galvo_stage) scaled_shape = np.array(psf.shape) * np.array( (1.0 / dz_ratio_galvo_stage, 1.0, 1.0)) scaled_shape = scaled_shape.astype(np.int) scaled = affine_transform(psf, inv(scale_psf), output_shape=scaled_shape) # Step 2: Skewing according to deskewfactor # adjust bead centre coordinate to scaled coordinates scaled_centre = np.array(centre) * (dz_ratio_galvo_stage, 1.0, 1.0) shift = shift_centre(2 * np.array(scaled_centre)) unshift = unshift_centre(output_shape) if deskewfactor: skew = inv(deskew_mat(deskewfactor)) else: skew = np.eye(4) # no skew, identity logger.debug(f"deskew factor {deskewfactor}") skew_shift = unshift @ skew @ shift processed_psf = affine_transform(scaled, inv(skew_shift), output_shape=output_shape, order=interpolation) return processed_psf
def get_rotate_to_coverslip_function(orig_shape: Collection[int], dz_stage: float, xypixelsize: float, angle: float, interp_order: int = 1) -> Callable: """Generate a function that rotates a deskewed volume to coverslip coordinates Parameters ---------- orig_shape : Iterable[int] shape of the original raw volume (not the shape of the deskewed volume!) dz_stage : float, optional stage step size in z direction xypixelsize : float, optional pixel size in object space (use same units as for dz_stage) angle : float, optional light sheet angle relative to stage in degrees interp_order : int, optional interpolation order to use for affine transform (default is 1) Returns ------- Callable function f that rotates a deskewed volume to coverslip coordinates f(np.array: vol) -> np.array Notes ----- The returned function performs the rotation in two seperate affine transformation steps to avoid aliasing (see [1]_). References ---------- [1] https://github.com/VolkerH/Lattice_Lightsheet_Deskew_Deconv/issues/22 """ dz = np.sin(angle * np.pi / 180.0) * dz_stage dx = xypixelsize deskewfactor = np.cos(angle * np.pi / 180.0) * dz_stage / dx dzdx_aspect = dz / dx logger.debug(f"rotate function: dx: {dx}") logger.debug(f"rotate function: dz: {dz}") logger.debug(f"rotate function: deskewfactor: {deskewfactor}") logger.debug(f"rotate function: dzdx_aspect: {dzdx_aspect}") # shift volume such that centre is at (0,0,0) for rotations # Build deskew matrix skew = deskew_mat(deskewfactor) shape_after_skew = get_output_dimensions(skew, orig_shape) # matrix to scale z to obtain isotropic pixels scale = scale_pixel_z(dzdx_aspect) shape_after_scale = get_output_dimensions(scale, shape_after_skew) shift_scaled = shift_centre(shape_after_scale) # rotation matrix rot = rot_around_y(-angle) # determine final output shape for an all-in-one (deskew/scale/rot) transform # (which is not actually applied) shift = shift_centre(orig_shape) combined = rot @ scale @ skew @ shift shape_final = get_output_dimensions(combined, orig_shape) # determine shape after scale/rot on deskewed, this is larger than final due to # fill pixels shape_scalerot = get_output_dimensions(rot @ shift_scaled @ scale, shape_after_skew) # calc rotshift logger.debug(f"shape_scalerot {shape_scalerot}") logger.debug(f"shape_final {shape_final}") _tmp = unshift_centre(shape_final) diff = (shape_scalerot[0] - shape_final[0]) / 2 logger.debug(f"diff {diff}") # _tmp[0,3] -= diff unshift_final = _tmp rotshift = unshift_final @ rot @ shift_scaled logger.debug(f"rotate to coverslip: scale matrix: {scale}") logger.debug(f"rotate to coverslip: outshape1: {shape_after_scale}") logger.debug(f"rotate to coverslip: rotshift: {rotshift}") logger.debug(f"rotate to coverslip: outshape2: {shape_final}") rotate_func = partial( _twostep_affine, mat1=inv(scale), outshape1=shape_after_scale, mat2=inv(rotshift), outshape2=shape_final, order=interp_order, ) return rotate_func
logger.debug(f"rotate function: deskewfactor: {deskewfactor}") logger.debug(f"rotate function: dzdx_aspect: {dzdx_aspect}") # shift volume such that centre is at (0,0,0) for rotations shift = shift_centre(input_shape) logger.debug(f"rotate function: shift matrix {shift}") # Build deskew matrix skew = deskew_mat(deskewfactor) logger.debug(f"") # scale z to obtain isotropic pixels scale = scale_pixel_z(dzdx_aspect) # rotate rot = rot_around_y(-angle) # determine output shape for combined transform (except unshift) combined = rot @ scale @ skew @ shift output_shape = get_output_dimensions(combined, input_shape) # add unshift to bring 0,0,0 to centre of output volume from centre unshift_final = unshift_centre(output_shape) logger.debug(f"rotate function: unshift matrix: {unshift_final}") logger.debug(f"rotate function: output shape: {output_shape}") all_in_one = unshift_final @ rot @ scale @ skew @ shift logger.debug(f"rotate function: all in one: {all_in_one}") logger.debug(f"rotate function: all in one: {inv(all_in_one)}") rotate_func = partial(affine_transform, matrix=inv(all_in_one), output_shape=output_shape, order=interp_order) return rotate_func