class Decoder(PipelineComponent): @classmethod def _get_algorithm_base_class(cls) -> Type[AlgorithmBase]: return _base.DecoderAlgorithmBase @classmethod def _cli_run(cls, ctx, instance): table = ctx.obj["intensities"] codes = ctx.obj["codebook"] output = ctx.obj["output"] intensities = instance.run(table, codes) intensities.save(output) @staticmethod @click.group("decode") @click.option("-i", "--input", required=True, type=click.Path(exists=True)) @click.option("-o", "--output", required=True) @click.option("--codebook", required=True, type=click.Path(exists=True)) @click.pass_context def _cli(ctx, input, output, codebook): """assign genes to spots""" ctx.obj = dict( component=Decoder, input=input, output=output, intensities=IntensityTable.load(input), codebook=Codebook.from_json(codebook), )
class TargetAssignment(PipelineComponent): @classmethod def _get_algorithm_base_class(cls) -> Type[AlgorithmBase]: return TargetAssignmentAlgorithm @classmethod def _cli_run(cls, ctx, instance): output = ctx.obj["output"] intensity_table = ctx.obj["intensity_table"] label_image = ctx.obj["label_image"] assigned = instance.run(label_image, intensity_table) print(f"Writing intensities, including cell ids to {output}") assigned.save(os.path.join(output)) @staticmethod @click.group("target_assignment") @click.option("--label-image", required=True, type=click.Path(exists=True)) @click.option("--intensities", required=True, type=click.Path(exists=True)) @click.option("-o", "--output", required=True) @click.pass_context def _cli(ctx, label_image, intensities, output): """assign targets to cells""" print('Assigning targets to cells...') ctx.obj = dict(component=TargetAssignment, output=output, intensity_table=IntensityTable.load(intensities), label_image=imread(label_image))
class Segmentation(PipelineComponent): @classmethod def _get_algorithm_base_class(cls) -> Type[AlgorithmBase]: return _base.SegmentationAlgorithmBase @classmethod def _cli_run(cls, ctx, instance): output = ctx.obj["output"] pri_stack = ctx.obj["primary_images"] nuc_stack = ctx.obj["nuclei"] label_image = instance.run(pri_stack, nuc_stack) print(f"Writing label image to {output}") imsave(output, label_image) @staticmethod @click.group("segment") @click.option("--primary-images", required=True, type=click.Path(exists=True)) @click.option("--nuclei", required=True, type=click.Path(exists=True)) @click.option("-o", "--output", required=True) @click.pass_context def _cli(ctx, primary_images, nuclei, output): """define polygons for cell boundaries and assign spots""" print('Segmenting ...') ctx.obj = dict( component=Segmentation, output=output, primary_images=ImageStack.from_path_or_url(primary_images), nuclei=ImageStack.from_path_or_url(nuclei), )
class Registration(PipelineComponent): @classmethod def _get_algorithm_base_class(cls) -> Type[AlgorithmBase]: return _base.RegistrationAlgorithmBase @classmethod def _cli_run(cls, ctx, instance): output = ctx.obj["output"] stack = ctx.obj["stack"] instance.run(stack) stack.export(output) @staticmethod @click.group("registration") @click.option("-i", "--input", type=click.Path(exists=True)) @click.option("-o", "--output", required=True) @click.pass_context def _cli(ctx, input, output): """translation correction of image stacks""" print("Registering...") ctx.obj = dict( component=Registration, input=input, output=output, stack=ImageStack.from_path_or_url(input), )
class ApplyTransform(PipelineComponent): @classmethod def pipeline_component_type_name(cls) -> str: return COMPONENT_NAME @classmethod def _cli_run(cls, ctx, instance): output = ctx.obj["output"] stack = ctx.obj["stack"] transformation_list = ctx.obj["transformation_list"] transformed = instance.run(stack, transformation_list) transformed.export(output) @staticmethod @click.group(COMPONENT_NAME) @click.option("-i", "--input", type=ImageStackParamType) @click.option("-o", "--output", required=True) @click.option( "--transformation-list", required=True, type=click.Path(exists=True), help="The list of transformations to apply to the ImageStack.") @click.pass_context def _cli(ctx, input, output, transformation_list): print("Applying Transform to images...") ctx.obj = dict( component=ApplyTransform, output=output, stack=input, transformation_list=TransformsList.from_json(transformation_list))
class PixelSpotDecoder(PipelineComponent): @classmethod def _get_algorithm_base_class(cls) -> Type[AlgorithmBase]: return _base.PixelDecoderAlgorithmBase @classmethod def _cli_run(cls, ctx, instance): output = ctx.obj["output"] image_stack = ctx.obj["image_stack"] # TODO ambrosejcarr serialize and save ConnectedComponentDecodingResult somehow intensities, ccdr = instance.run(image_stack) intensities.save(output) @staticmethod @click.group("detect_pixels") @click.option("-i", "--input", required=True, type=click.Path(exists=True)) @click.option("-o", "--output", required=True) @click.option( '--codebook', default=None, required=True, help=( 'A spaceTx spec-compliant json file that describes a three dimensional tensor ' 'whose values are the expected intensity of a spot for each code in each imaging ' 'round and each color channel.' ) ) @click.pass_context def _cli(ctx, input, output, codebook): """pixel-wise spot detection and decoding""" print('Detecting Spots ...') ctx.obj = dict( component=PixelSpotDecoder, image_stack=ImageStack.from_path_or_url(input), output=output, codebook=Codebook.from_json(codebook), )
class Filter(PipelineComponent): @classmethod def _get_algorithm_base_class(cls) -> Type[AlgorithmBase]: return _base.FilterAlgorithmBase @classmethod def _cli_run(cls, ctx, instance): output = ctx.obj["output"] stack = ctx.obj["stack"] filtered = instance.run(stack) filtered.export(output) @staticmethod @click.group("filter") @click.option("-i", "--input", type=click.Path(exists=True)) @click.option("-o", "--output", required=True) @click.pass_context def _cli(ctx, input, output): """smooth, sharpen, denoise, etc""" print("Filtering images...") ctx.obj = dict( component=Filter, input=input, output=output, stack=ImageStack.from_path_or_url(input), )
class Decoder(PipelineComponent): @classmethod def pipeline_component_type_name(cls) -> str: return COMPONENT_NAME @classmethod def _cli_run(cls, ctx, instance): table = ctx.obj["intensities"] codes = ctx.obj["codebook"] output = ctx.obj["output"] intensities = instance.run(table, codes) intensities.save(output) @staticmethod @click.group(COMPONENT_NAME) @click.option("-i", "--input", required=True, type=click.Path(exists=True)) @click.option("-o", "--output", required=True) @click.option("--codebook", required=True, type=CodebookParamType) @click.pass_context def _cli(ctx, input, output, codebook): """assign genes to spots""" ctx.obj = dict( component=Decoder, input=input, output=output, intensities=IntensityTable.load(input), codebook=codebook, )
class Filter(PipelineComponent): @classmethod def pipeline_component_type_name(cls) -> str: return COMPONENT_NAME @classmethod def _cli_run(cls, ctx, instance): output = ctx.obj["output"] stack = ctx.obj["stack"] filtered = instance.run(stack) filtered.export(output) @staticmethod @click.group(COMPONENT_NAME) @click.option("-i", "--input", type=click.Path(exists=True)) @click.option("-o", "--output", required=True) @click.pass_context def _cli(ctx, input, output): """smooth, sharpen, denoise, etc""" print("Filtering images...") ctx.obj = dict( component=Filter, input=input, output=output, stack=ImageStack.from_path_or_url(input), )
class SpotFinder(PipelineComponent): @classmethod def _get_algorithm_base_class(cls) -> Type[AlgorithmBase]: return _base.SpotFinderAlgorithmBase @classmethod def _cli_run(cls, ctx, instance): output = ctx.obj["output"] blobs_stack = ctx.obj["blobs_stack"] image_stack = ctx.obj["image_stack"] ref_image = ctx.obj["reference_image_from_max_projection"] if blobs_stack is not None: blobs_stack = ImageStack.from_path_or_url(blobs_stack) # type: ignore mp = blobs_stack.max_proj(Axes.ROUND, Axes.CH) mp_numpy = mp._squeezed_numpy(Axes.ROUND, Axes.CH) intensities = instance.run( image_stack, blobs_image=mp_numpy, reference_image_from_max_projection=ref_image, ) else: intensities = instance.run(image_stack) # When run() returns a tuple, we only save the intensities for now # TODO ambrosejcarr find a way to save arbitrary detector results if isinstance(intensities, tuple): intensities = intensities[0] intensities.save(output) @staticmethod @click.group("detect_spots") @click.option("-i", "--input", required=True, type=click.Path(exists=True)) @click.option("-o", "--output", required=True) @click.option( '--blobs-stack', default=None, required=False, help=( 'ImageStack that contains the blobs. Will be max-projected across imaging round ' 'and channel to produce the blobs_image' ) ) @click.option( '--reference-image-from-max-projection', default=False, is_flag=True, help=( 'Construct a reference image by max projecting imaging rounds and channels. Spots ' 'are found in this image and then measured across all images in the input stack.' ) ) @click.pass_context def _cli(ctx, input, output, blobs_stack, reference_image_from_max_projection): """detect spots""" print('Detecting Spots ...') ctx.obj = dict( component=SpotFinder, image_stack=ImageStack.from_path_or_url(input), output=output, blobs_stack=blobs_stack, reference_image_from_max_projection=reference_image_from_max_projection, )
class SpotFinder(PipelineComponent): @classmethod def _get_algorithm_base_class(cls) -> Type[AlgorithmBase]: return _base.SpotFinderAlgorithmBase @classmethod def _cli_run(cls, ctx, instance): output = ctx.obj["output"] blobs_stack = ctx.obj["blobs_stack"] image_stack = ctx.obj["image_stack"] ref_image = ctx.obj["reference_image_from_max_projection"] if blobs_stack is not None: blobs_stack = ImageStack.from_path_or_url( blobs_stack) # type: ignore mp = blobs_stack.max_proj(Axes.ROUND, Axes.CH) mp_numpy = mp._squeezed_numpy(Axes.ROUND, Axes.CH) # TODO: this won't work for PixelSpotDectector intensities = instance.run( image_stack, blobs_image=mp_numpy, reference_image_from_max_projection=ref_image, ) else: intensities = instance.run(image_stack) # When PixelSpotDetector is used run() returns a tuple if isinstance(intensities, tuple): intensities = intensities[0] intensities.save(output) @staticmethod @click.group("detect_spots") @click.option("-i", "--input", required=True, type=click.Path(exists=True)) @click.option("-o", "--output", required=True) @click.option( '--blobs-stack', default=None, required=False, help= ('ImageStack that contains the blobs. Will be max-projected across imaging round ' 'and channel to produce the blobs_image')) @click.option( '--reference-image-from-max-projection', default=False, is_flag=True, help= ('Construct a reference image by max projecting imaging rounds and channels. Spots ' 'are found in this image and then measured across all images in the input stack.' )) @click.option( '--codebook', default=None, required=False, help= ('A spaceTx spec-compliant json file that describes a three dimensional tensor ' 'whose values are the expected intensity of a spot for each code in each imaging ' 'round and each color channel.')) @click.pass_context def _cli(ctx, input, output, blobs_stack, reference_image_from_max_projection, codebook): """assign spots to regions""" print('Detecting Spots ...') ctx.obj = dict( component=SpotFinder, image_stack=ImageStack.from_path_or_url(input), output=output, blobs_stack=blobs_stack, reference_image_from_max_projection= reference_image_from_max_projection, codebook=None, ) if codebook is not None: ctx.obj["codebook"] = Codebook.from_json(codebook)
def dimensions_option(name, required): return click.option( "--{}-dimensions".format(name), type=StarfishIndex(), required=required, help= "Dimensions for the {} images. Should be a json dict, with {}, {}, " "and {} as the possible keys. The value should be the shape along that " "dimension. If a key is not present, the value is assumed to be 0.". format(name, Axes.ROUND.value, Axes.CH.value, Axes.ZPLANE.value)) decorators = [ click.command(), click.argument("output_dir", type=click.Path(exists=True, file_okay=False, writable=True)), click.option("--fov-count", type=int, required=True, help="Number of FOVs in this experiment."), dimensions_option("primary-image", True), ] for image_name in AUX_IMAGE_NAMES: decorators.append(dimensions_option(image_name, False)) def build(output_dir, fov_count, primary_image_dimensions, **kwargs): """generate synthetic experiments""" write_experiment_json( output_dir, fov_count,
class FourierShiftRegistration(RegistrationAlgorithmBase): """ Implements fourier shift registration. TODO: (dganguli) FILL IN DETAILS HERE PLS. Performs a simple translation registration. """ def __init__(self, upsampling: int, reference_stack: Union[str, ImageStack], **kwargs) -> None: """Implements fourier shift registrations, which performs a simple translation registration Parameters ---------- upsampling : int images are registered to within 1 / upsample_factor of a pixel reference_stack : ImageStack the ImageStack against which this object will register images See Also -------- https://en.wikipedia.org/wiki/Phase_correlation """ self.upsampling = upsampling # TODO ambrosejcarr: remove the ability to load from string in the constructor, move to CLI if isinstance(reference_stack, ImageStack): self.reference_stack = reference_stack else: self.reference_stack = ImageStack.from_path_or_url(reference_stack) def run(self, image: ImageStack, in_place: bool = False) -> Optional[ImageStack]: """Register an ImageStack against a reference image. Parameters ---------- image : ImageStack The stack to be registered in_place : bool If false, return a new registered stack. Else, register in-place (default False) Returns ------- """ if not in_place: image = deepcopy(image) # TODO: (ambrosejcarr) is this the appropriate way of dealing with Z in registration? mp = image.max_proj(Axes.CH, Axes.ZPLANE) mp_numpy = mp._squeezed_numpy(Axes.CH, Axes.ZPLANE) reference_image_mp = self.reference_stack.max_proj( Axes.ROUND, Axes.CH, Axes.ZPLANE) reference_image_numpy = reference_image_mp._squeezed_numpy( Axes.ROUND, Axes.CH, Axes.ZPLANE) for r in image.axis_labels(Axes.ROUND): # compute shift between maximum projection (across channels) and dots, for each round # TODO: make the max projection array ignorant of axes ordering. shift, error = compute_shift(mp_numpy[r, :, :], reference_image_numpy, self.upsampling) print(f"For round: {r}, Shift: {shift}, Error: {error}") for c in image.axis_labels(Axes.CH): for z in image.axis_labels(Axes.ZPLANE): # apply shift to all zplanes, channels, and imaging rounds selector = {Axes.ROUND: r, Axes.CH: c, Axes.ZPLANE: z} data, axes = image.get_slice(selector=selector) assert len(axes) == 0 result = shift_im(data, shift) result = preserve_float_range(result) image.set_slice(selector=selector, data=result) if not in_place: return image return None @staticmethod @click.command("FourierShiftRegistration") @click.option("--upsampling", default=1, type=int, help="Amount of up-sampling") @click.option("--reference-stack", required=True, type=click.Path(exists=True), help="The image stack to align the input image stack to.") @click.pass_context def _cli(ctx, upsampling, reference_stack): ctx.obj["component"]._cli_run( ctx, FourierShiftRegistration(upsampling, reference_stack))