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
Пример #2
0
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
Пример #3
0
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
Пример #4
0
    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