def worm_frame_mask(width_tck, image_shape, num_spline_points=None, antialias=False, zoom=1): """Use a centerline and width spline to draw a worm mask image in the worm frame of reference. Parameters: width_tck: width splines defining worm outline image_shape: shape of the output mask num_spline_points: number of points to evaluate the worm outline along (more points = smoother mask). By default, ~1 point/pixel will be used, which is more than enough. antialias: if False, return a mask with only values 0 and 255. If True, edges will be smoothed for better appearance. This is slightly slower, and unnecessary when just using the mask to select pixels of interest. zoom: zoom-value to use (for matching output of to_worm_frame with zooming.) Returns: mask image with dtype=numpy.uint8 in range [0, 255]. To obtain a True/False-valued mask from a uint8 mask (regardless of antialiasing): bool_mask = uint8_mask > 255 """ worm_length = image_shape[0] if num_spline_points is None: num_spline_points = worm_length widths = interpolate.spline_interpolate(width_tck, num_points=num_spline_points) widths *= zoom x_vals = numpy.linspace(0, worm_length, num_spline_points) centerline_y = image_shape[1] / 2 top = numpy.transpose([x_vals, centerline_y - widths]) bottom = numpy.transpose([x_vals, centerline_y + widths])[::-1] path = celiagg.Path() path.lines(numpy.concatenate([top, bottom])) return draw.draw_mask(image_shape, path, antialias)
def worm_image_coords_in_lab_frame(lab_image_shape, worm_image_shape, center_tck, width_tck, standard_width=None, zoom=1, reflect_centerline=False): """Produce a map in the lab frame noting the coordinates of each worm pixel in the frame of reference of an image as generated by to_worm_frame(). The output coordinates are relative to a worm frame-of-reference image, as produced by to_worm_frame(). All parameters must be the same as those passed to to_worm_frame() for the coordinate transform to be correct. In particular, if a standard_width and/or a zoom factor were used to produce the image, those values must be used here as well. Areas outside of the worm will be nan. Parameters: lab_image_shape: shape of output image in lab frame worm_image_shape: shape of worm image in which the coordinates are defined center_tck: centerline spline of the worm in the lab frame. width_tck: spline width profile of the worm. standard_width: a width spline specifying the "standardized" width profile for the output image. zoom: zoom factor. reflect_centerline: reflect worm coordinates over the centerline Returns: (x_coords, y_coords), each of shape lab_image_shape """ triangle_strip = spline_geometry.triangle_strip(center_tck, width_tck) wtck = width_tck if standard_width is None else standard_width widths = interpolate.spline_interpolate(wtck, num_points=len(triangle_strip)//2) right = worm_image_shape[1]/2 - widths*zoom # worm_image_shape[1]/2 is the position of the centerline left = worm_image_shape[1]/2 + widths*zoom # worm_image_shape[1]/2 is the position of the centerline return _worm_coords(lab_image_shape, triangle_strip, x_max=worm_image_shape[0], right=right, left=left, reflect_centerline=reflect_centerline)
def abs_worm_coords_in_lab_frame(lab_image_shape, center_tck, width_tck, reflect_centerline=False): """Produce a map of the pixel-wise worm coordinates in the lab frame. Output x-coords run from 0 to the length of the worm, and y-coords from -width (right side) to width, which varies along the worm. Areas outside of the worm will be nan. Parameters: lab_image_shape: shape of output image in lab frame center_tck: centerline spline of the worm in the lab frame. width_tck: spline width profile of the worm. reflect_centerline: reflect worm coordinates over the centerline Returns: (x_coords, y_coords), each of shape lab_image_shape """ triangle_strip = spline_geometry.triangle_strip(center_tck, width_tck) x_max = spline_geometry.arc_length(center_tck) widths = interpolate.spline_interpolate(width_tck, num_points=len(triangle_strip) // 2) return _worm_coords(lab_image_shape, triangle_strip, x_max, right=-widths, left=widths, reflect_centerline=reflect_centerline)
def to_worm_frame(images, center_tck, width_tck=None, width_margin=20, sample_distance=None, standard_length=None, standard_width=None, zoom=1, reflect_centerline=False, order=3, dtype=None, **kwargs): """Transform images from the lab reference frame to the worm reference frame. The width of the output image is defined by the center_tck, which defines the length of the worm and thus the width of the image. The height of the image can be specified either directly by the sample_distance parameter, or can be computed from a width_tck that defines the location of the sides of the worm (a fixed width_margin is added so that the image extends a bit past the worm). The size and shape of the output worm can be standardized to a "unit worm" by use of the "standard_length" and "standard_width" parameters; see below. Parameters: images: single numpy array, or list/tuple/3d array of multiple images to be transformed. center_tck: centerline spline defining the pose of the worm in the lab frame. width_tck: width spline defining the distance from centerline to worm edges. Optional; uses are as follows: (1) if sample_distance is not specified, a width_tck must be specified in order to calculate the output image height; (2) if standard_width is specified, a width_tck must also be specified to define the transform from this worm's width profile to the standardized width profile. width_margin: if sample_distance is not specified, width_margin is used to define the distance (in image pixels) that the output image will extend past the edge of the worm (at its widest). If a zoom is specified, note that the margin pixels will be zoomed too. sample_distance: number of pixels to sample in each direction perpendicular to the centerline. The height of the output image is int(round(2 * sample_distance * zoom)). standard_length: if not specified, the width of the output image is int(round(arc_length)*zoom), where arc_length is the path integral along center_tck (i.e. the length from beginning to end). If standard_length is specified, then the length of the output image is int(round(standard_length*zoom)). The full length of the worm will be compressed or expanded as necessary to bring it to the specified standard_length. standard_width: a width spline specifying the "standardized" width profile for the output image. If specified, the actual width profile must also be provided as width_tck. In this case, the output image will be compressed/expanded perpendicular to the centerline as needed to make the actual widths conform to the standard width profile. zoom: zoom factor, can be any real number > 0. reflect_centerline: reflect worm over its centerline. order: image interpolation order (0 = nearest neighbor, 1 = linear, 3 = cubic). Cubic is best, but slowest. dtype: if None, use dtype of input images for output. Otherwise, use the specified dtype. kwargs: additional keyword arguments to pass to ndimage.map_coordinates. Returns: single image or list of images (depending on whether the input is a single image or list/tuple/3d array). """ assert width_tck is not None or sample_distance is not None if standard_width is not None: assert width_tck is not None if standard_length is None: length = spline_geometry.arc_length(center_tck) else: length = standard_length x_samples = int(round(length * zoom)) if sample_distance is None: wtck = standard_width if standard_width is not None else width_tck sample_distance = interpolate.spline_interpolate(wtck, num_points=x_samples).max() + width_margin y_samples = int(round(2 * sample_distance * zoom)) # basic plan: # 1) get the centerline and the perpendiculars to it. # 2) define positions along each perpendicular at which to sample the input images. # (This is the "offset_distances" variable). x = numpy.arange(x_samples, dtype=float) + 0.5 # want to sample at pixel centers, not edges y = numpy.ones_like(x) * (y_samples / 2) worm_frame_centerline = numpy.transpose([x, y]) centerline, perpendiculars, spline_y = _lab_centerline_and_perps(worm_frame_centerline, (x_samples, y_samples), center_tck, width_tck, standard_width, zoom) # if the far edges of the top and bottom pixels are sample_distance from the centerline, # figure out the position of the *centers* of the top and bottom of the pixels. # i.e. correct for fencepost error sample_max = sample_distance * (y_samples - 1) / y_samples offsets = numpy.linspace(-sample_max, sample_max, y_samples) # distances along each perpendicular across the width of the sample swath offset_distances = numpy.multiply.outer(perpendiculars.T, offsets) # shape = (2, x_samples, y_samples) centerline = centerline.T[:, :, numpy.newaxis] # from shape = (x_samples, 2) to shape = (2, x_samples, 1) if reflect_centerline: offset_distances *= -1 sample_coordinates = centerline + offset_distances # shape = (2, x_samples, y_samples) unpack_list = False if isinstance(images, numpy.ndarray): if images.ndim == 3: images = list(images) else: unpack_list = True images = [images] # subtract half-pixel offset because map_coordinates treats (0,0) as the middle # of the top-left pixel, not the far corner of that pixel. worm_frame = [ndimage.map_coordinates(image, sample_coordinates - _HALF_PX_OFFSET.reshape(2, 1, 1), order=order, output=dtype, **kwargs) for image in images] if unpack_list: worm_frame = worm_frame[0] return worm_frame