Exemple #1
0
    def run_command(cls, args):
        _, name, baseurl = resolve_path_or_url(args.in_url)
        slicedimage = Reader.parse_doc(name, baseurl)

        Writer.write_to_path(slicedimage,
                             args.out_path,
                             pretty=args.pretty,
                             tile_opener=fake_file_opener,
                             tile_writer=identity_writer)
Exemple #2
0
    def write(self, filepath: str, tile_opener=None) -> None:
        """write the image tensor to disk in spaceTx format

        Parameters
        ----------
        filepath : str
            Path + prefix for the images and primary_images.json written by this function
        tile_opener : TODO ttung: doc me.

        """
        for tile in self._image_partition.tiles():
            h = tile.indices[Indices.ROUND]
            c = tile.indices[Indices.CH]
            zlayer = tile.indices.get(Indices.Z, 0)
            tile.numpy_array, axes = self.get_slice(indices={
                Indices.ROUND: h,
                Indices.CH: c,
                Indices.Z: zlayer
            })
            assert len(axes) == 0

        seen_x_coords, seen_y_coords, seen_z_coords = set(), set(), set()
        for tile in self._image_partition.tiles():
            seen_x_coords.add(tile.coordinates[Coordinates.X])
            seen_y_coords.add(tile.coordinates[Coordinates.Y])
            z_coords = tile.coordinates.get(Coordinates.Z, None)
            if z_coords is not None:
                seen_z_coords.add(z_coords)

        sorted_x_coords = sorted(seen_x_coords)
        sorted_y_coords = sorted(seen_y_coords)
        sorted_z_coords = sorted(seen_z_coords)
        x_coords_to_idx = {
            coords: idx
            for idx, coords in enumerate(sorted_x_coords)
        }
        y_coords_to_idx = {
            coords: idx
            for idx, coords in enumerate(sorted_y_coords)
        }
        z_coords_to_idx = {
            coords: idx
            for idx, coords in enumerate(sorted_z_coords)
        }

        if tile_opener is None:

            def tile_opener(tileset_path, tile, ext):
                tile_basename = os.path.splitext(tileset_path)[0]
                xcoord = tile.coordinates[Coordinates.X]
                ycoord = tile.coordinates[Coordinates.Y]
                zcoord = tile.coordinates.get(Coordinates.Z, None)
                xcoord = tuple(xcoord) if isinstance(xcoord, list) else xcoord
                ycoord = tuple(ycoord) if isinstance(ycoord, list) else ycoord
                xval = x_coords_to_idx[xcoord]
                yval = y_coords_to_idx[ycoord]
                if zcoord is not None:
                    zval = z_coords_to_idx[zcoord]
                    zstr = "-Z{}".format(zval)
                else:
                    zstr = ""
                return open(
                    "{}-X{}-Y{}{}-H{}-C{}.{}".format(
                        tile_basename,
                        xval,
                        yval,
                        zstr,
                        tile.indices[Indices.ROUND],
                        tile.indices[Indices.CH],
                        ext,
                    ), "wb")

        if not filepath.endswith('.json'):
            filepath += '.json'
        Writer.write_to_path(self._image_partition,
                             filepath,
                             pretty=True,
                             tile_opener=tile_opener)
Exemple #3
0
def write_experiment_json(
        path,
        fov_count,
        hyb_dimensions,
        aux_name_to_dimensions,
        hyb_image_fetcher=None,
        aux_image_fetcher=None,
        postprocess_func=None,
        default_shape=(1536, 1024),
):
    """
    Build and returns a top-level experiment description with the following characteristics:

    Parameters
    ----------
    path : str
        Directory to write the files to.
    fov_count : int
        Number of fields of view in this experiment.
    hyb_dimensions : Mapping[str, int]
        Dictionary mapping dimension name to dimension size for the hybridization image.
    aux_name_to_dimensions : Mapping[str, Mapping[str, int]]
        Dictionary mapping the auxiliary image type to dictionaries, which map from dimension name to dimension size.
    hyb_image_fetcher : Optional[ImageFetcher]
        ImageFetcher for hybridization images.  Set this if you want specific image data to be set for the hybridization
        images.  If not provided, the image data is set to random noise via :class:`RandomNoiseImageFetcher`.
    aux_image_fetcher : Optional[Mapping[str, ImageFetcher]]
        ImageFetchers for auxiliary images.  Set this if you want specific image data to be set for one or more aux
        image types.  If not provided for any given aux image, the image data is set to random noise via
        :class:`RandomNoiseImageFetcher`.
    postprocess_func : Optional[Callable[[dict], dict]]
        If provided, this is called with the experiment document for any postprocessing.  An example of this would be to
        add something to one of the top-level extras field.  The callable should return what is to be written as the
        experiment document.
    default_shape : Tuple[int, int] (default = (1536, 1024))
        Default shape for the tiles in this experiment.
    """
    if hyb_image_fetcher is None:
        hyb_image_fetcher = RandomNoiseImageFetcher()
    if aux_image_fetcher is None:
        aux_image_fetcher = {}
    if postprocess_func is None:
        postprocess_func = lambda doc: doc

    experiment_doc = {
        'version': "0.0.0",
        'auxiliary_images': {},
        'extras': {},
    }
    hybridization_image = build_image(
        fov_count,
        hyb_dimensions[Indices.HYB], hyb_dimensions[Indices.CH], hyb_dimensions[Indices.Z],
        hyb_image_fetcher,
        default_shape=default_shape,
    )
    Writer.write_to_path(
        hybridization_image,
        os.path.join(path, "hybridization.json"),
        pretty=True,
        partition_path_generator=fov_path_generator,
        tile_opener=tile_opener,
        tile_writer=tile_writer,
    )
    experiment_doc['hybridization_images'] = "hybridization.json"

    for aux_name, aux_dimensions in aux_name_to_dimensions.items():
        if aux_dimensions is None:
            continue
        auxiliary_image = build_image(
            fov_count,
            aux_dimensions[Indices.HYB], aux_dimensions[Indices.CH], aux_dimensions[Indices.Z],
            aux_image_fetcher.get(aux_name, RandomNoiseImageFetcher()),
            default_shape=default_shape,
        )
        Writer.write_to_path(
            auxiliary_image,
            os.path.join(path, "{}.json".format(aux_name)),
            pretty=True,
            partition_path_generator=fov_path_generator,
            tile_opener=tile_opener,
            tile_writer=tile_writer,
        )
        experiment_doc['auxiliary_images'][aux_name] = "{}.json".format(aux_name)

    experiment_doc = postprocess_func(experiment_doc)
    with open(os.path.join(path, "experiment.json"), "w") as fh:
        json.dump(experiment_doc, fh, indent=4)
Exemple #4
0
    def export(self,
               filepath: str,
               tile_opener=None,
               tile_format: ImageFormat = ImageFormat.NUMPY) -> None:
        """write the image tensor to disk in spaceTx format

        Parameters
        ----------
        filepath : str
            Path + prefix for the images and primary_images.json written by this function
        tile_opener : TODO ttung: doc me.
        tile_format : ImageFormat
            Format in which each 2D plane should be written.

        """
        # Add log data to extras
        self._tile_data.extras[STARFISH_EXTRAS_KEY] = logging.LogEncoder(
        ).encode({LOG: self.log})
        tileset = TileSet(
            dimensions={
                Axes.ROUND,
                Axes.CH,
                Axes.ZPLANE,
                Axes.Y,
                Axes.X,
            },
            shape={
                Axes.ROUND: self.num_rounds,
                Axes.CH: self.num_chs,
                Axes.ZPLANE: self.num_zplanes,
            },
            default_tile_shape={
                Axes.Y: self.tile_shape[0],
                Axes.X: self.tile_shape[1]
            },
            extras=self._tile_data.extras,
        )
        for axis_val_map in self._iter_axes({Axes.ROUND, Axes.CH,
                                             Axes.ZPLANE}):
            tilekey = TileKey(round=axis_val_map[Axes.ROUND],
                              ch=axis_val_map[Axes.CH],
                              zplane=axis_val_map[Axes.ZPLANE])
            round_, ch, zplane = tilekey.round, tilekey.ch, tilekey.z
            extras: dict = self._tile_data[tilekey]

            selector = {
                Axes.ROUND: round_,
                Axes.CH: ch,
                Axes.ZPLANE: zplane,
            }

            coordinates: MutableMapping[Coordinates, Union[Tuple[Number,
                                                                 Number],
                                                           Number]] = dict()
            x_coordinates = (float(self.xarray[Coordinates.X.value][0]),
                             float(self.xarray[Coordinates.X.value][-1]))
            y_coordinates = (float(self.xarray[Coordinates.Y.value][0]),
                             float(self.xarray[Coordinates.Y.value][-1]))
            coordinates[Coordinates.X] = x_coordinates
            coordinates[Coordinates.Y] = y_coordinates
            if Coordinates.Z in self.xarray.coords:
                # set the z coord to the calculated value from the associated z plane
                z_coordinates = float(self.xarray[Coordinates.Z.value][zplane])
                coordinates[Coordinates.Z] = z_coordinates

            tile = Tile(
                coordinates=coordinates,
                indices=selector,
                extras=extras,
            )
            tile.numpy_array, _ = self.get_slice(selector={
                Axes.ROUND: round_,
                Axes.CH: ch,
                Axes.ZPLANE: zplane
            })
            tileset.add_tile(tile)

        if tile_opener is None:

            def tile_opener(tileset_path: Path, tile, ext):
                base = tileset_path.parent / tileset_path.stem
                if Axes.ZPLANE in tile.indices:
                    zval = tile.indices[Axes.ZPLANE]
                    zstr = "-Z{}".format(zval)
                else:
                    zstr = ""
                return open(
                    "{}-H{}-C{}{}.{}".format(
                        str(base),
                        tile.indices[Axes.ROUND],
                        tile.indices[Axes.CH],
                        zstr,
                        ext,
                    ), "wb")

        if not filepath.endswith('.json'):
            filepath += '.json'
        Writer.write_to_path(tileset,
                             filepath,
                             pretty=True,
                             tile_opener=tile_opener,
                             tile_format=tile_format)
Exemple #5
0
def write_experiment_json(
        path: str,
        fov_count: int,
        tile_format: ImageFormat,
        *,
        primary_image_dimensions: Mapping[Union[str, Axes], int],
        aux_name_to_dimensions: Mapping[str, Mapping[Union[str, Axes], int]],
        primary_tile_fetcher: Optional[TileFetcher]=None,
        aux_tile_fetcher: Optional[Mapping[str, TileFetcher]]=None,
        postprocess_func: Optional[Callable[[dict], dict]]=None,
        default_shape: Optional[Mapping[Axes, int]]=None,
        dimension_order: Sequence[Axes]=(Axes.ZPLANE, Axes.ROUND, Axes.CH),
) -> None:
    """
    Build and returns a top-level experiment description with the following characteristics:

    Parameters
    ----------
    path : str
        Directory to write the files to.
    fov_count : int
        Number of fields of view in this experiment.
    primary_image_dimensions : Mapping[Union[str, Axes], int]
        Dictionary mapping dimension name to dimension size for the primary image.
    aux_name_to_dimensions : Mapping[str, Mapping[Union[str, Axes], int]]
        Dictionary mapping the auxiliary image type to dictionaries, which map from dimension name
        to dimension size.
    primary_tile_fetcher : Optional[TileFetcher]
        TileFetcher for primary images.  Set this if you want specific image data to be set for the
        primary images.  If not provided, the image data is set to random noise via
        :class:`RandomNoiseTileFetcher`.
    aux_tile_fetcher : Optional[Mapping[str, TileFetcher]]
        TileFetchers for auxiliary images.  Set this if you want specific image data to be set for
        one or more aux image types.  If not provided for any given aux image, the image data is
        set to random noise via :class:`RandomNoiseTileFetcher`.
    postprocess_func : Optional[Callable[[dict], dict]]
        If provided, this is called with the experiment document for any postprocessing.
        An example of this would be to add something to one of the top-level extras field.
        The callable should return what is to be written as the experiment document.
    default_shape : Optional[Tuple[int, int]] (default = None)
        Default shape for the tiles in this experiment.
    dimension_order : Sequence[Axes]
        Ordering for which dimensions vary, in order of the slowest changing dimension to the
        fastest.  For instance, if the order is (ROUND, Z, CH) and each dimension has size 2, then
        the sequence is:
          (ROUND=0, CH=0, Z=0)
          (ROUND=0, CH=1, Z=0)
          (ROUND=0, CH=0, Z=1)
          (ROUND=0, CH=1, Z=1)
          (ROUND=1, CH=0, Z=0)
          (ROUND=1, CH=1, Z=0)
          (ROUND=1, CH=0, Z=1)
          (ROUND=1, CH=1, Z=1)
        (default = (Axes.Z, Axes.ROUND, Axes.CH))
    """
    if primary_tile_fetcher is None:
        primary_tile_fetcher = tile_fetcher_factory(RandomNoiseTile)
    if aux_tile_fetcher is None:
        aux_tile_fetcher = {}
    if postprocess_func is None:
        postprocess_func = lambda doc: doc

    experiment_doc: Dict[str, Any] = {
        'version': str(CURRENT_VERSION),
        'images': {},
        'extras': {},
    }
    primary_image = build_image(
        range(fov_count),
        range(primary_image_dimensions[Axes.ROUND]),
        range(primary_image_dimensions[Axes.CH]),
        range(primary_image_dimensions[Axes.ZPLANE]),
        primary_tile_fetcher,
        axes_order=dimension_order,
        default_shape=default_shape,
    )
    Writer.write_to_path(
        primary_image,
        os.path.join(path, "primary_images.json"),
        pretty=True,
        partition_path_generator=_fov_path_generator,
        tile_opener=_tile_opener,
        tile_format=tile_format,
    )
    experiment_doc['images']['primary'] = "primary_images.json"

    for aux_name, aux_dimensions in aux_name_to_dimensions.items():
        if aux_dimensions is None:
            continue
        auxiliary_image = build_image(
            range(fov_count),
            range(aux_dimensions[Axes.ROUND]),
            range(aux_dimensions[Axes.CH]),
            range(aux_dimensions[Axes.ZPLANE]),
            aux_tile_fetcher.get(aux_name, tile_fetcher_factory(RandomNoiseTile)),
            axes_order=dimension_order,
            default_shape=default_shape,
        )
        Writer.write_to_path(
            auxiliary_image,
            os.path.join(path, "{}.json".format(aux_name)),
            pretty=True,
            partition_path_generator=_fov_path_generator,
            tile_opener=_tile_opener,
            tile_format=tile_format,
        )
        experiment_doc['images'][aux_name] = "{}.json".format(aux_name)

    experiment_doc["codebook"] = "codebook.json"
    codebook_array = [
        {
            "codeword": [
                {"r": 0, "c": 0, "v": 1},
            ],
            "target": "PLEASE_REPLACE_ME"
        },
    ]
    codebook = Codebook.from_code_array(codebook_array)
    codebook_json_filename = "codebook.json"
    codebook.to_json(os.path.join(path, codebook_json_filename))

    experiment_doc = postprocess_func(experiment_doc)

    with open(os.path.join(path, "experiment.json"), "w") as fh:
        json.dump(experiment_doc, fh, indent=4)
Exemple #6
0
def write_irregular_experiment_json(
        path: str,
        tile_format: ImageFormat,
        *,
        image_tile_identifiers: Mapping[str, Iterable[TileIdentifier]],
        tile_fetchers: Mapping[str, TileFetcher],
        postprocess_func: Optional[Callable[[dict], dict]]=None,
        default_shape: Optional[Mapping[Axes, int]]=None,
        fov_path_generator: Callable[[Path, str], Path] = None,
        tile_opener: Optional[Callable[[Path, Tile, str], BinaryIO]] = None,
) -> None:
    """
    Build and returns a top-level experiment description with the following characteristics:

    Parameters
    ----------
    path : str
        Directory to write the files to.
    tile_format : ImageFormat
        File format to write the tiles as.
    image_tile_identifiers : Mapping[str, Iterable[TileIdentifier]]
        Dictionary mapping the image type to an iterable of TileIdentifiers.
    tile_fetchers : Mapping[str, TileFetcher]
        Dictionary mapping the image type to a TileFetcher.
    postprocess_func : Optional[Callable[[dict], dict]]
        If provided, this is called with the experiment document for any postprocessing.
        An example of this would be to add something to one of the top-level extras field.
        The callable should return what is to be written as the experiment document.
    default_shape : Optional[Tuple[int, int]] (default = None)
        Default shape for the tiles in this experiment.
    fov_path_generator : Optional[Callable[[Path, str], Path]]
        Generates the path for a FOV's json file.  If one is not provided, the default generates
        the FOV's json file at the same level as the top-level json file for an image.    If this is
        not provided, a reasonable default will be provided.
    tile_opener : Optional[Callable[[Path, Tile, str], BinaryIO]]
        Callable that gets invoked with the following arguments: 1. the directory of the experiment
        that is being constructed, 2. the tile that is being written, and 3. the file extension
        that the tile should be written with.  The callable is expected to return an open file
        handle.  If this is not provided, a reasonable default will be provided.
    """
    if postprocess_func is None:
        postprocess_func = lambda doc: doc
    if fov_path_generator is None:
        fov_path_generator = _fov_path_generator
    if tile_opener is None:
        tile_opener = _tile_opener

    experiment_doc: Dict[str, Any] = {
        'version': str(CURRENT_VERSION),
        'images': {},
        'extras': {},
    }
    for image_type, tile_identifiers in image_tile_identifiers.items():
        tile_fetcher = tile_fetchers[image_type]

        image = build_irregular_image(tile_identifiers, tile_fetcher, default_shape)

        Writer.write_to_path(
            image,
            os.path.join(path, f"{image_type}.json"),
            pretty=True,
            partition_path_generator=fov_path_generator,
            tile_opener=tile_opener,
            tile_format=tile_format,
        )
        experiment_doc['images'][image_type] = f"{image_type}.json"

    experiment_doc["codebook"] = "codebook.json"
    codebook_array = [
        {
            "codeword": [
                {"r": 0, "c": 0, "v": 1},
            ],
            "target": "PLEASE_REPLACE_ME"
        },
    ]
    codebook = Codebook.from_code_array(codebook_array)
    codebook_json_filename = "codebook.json"
    codebook.to_json(os.path.join(path, codebook_json_filename))

    experiment_doc = postprocess_func(experiment_doc)

    with open(os.path.join(path, "experiment.json"), "w") as fh:
        json.dump(experiment_doc, fh, indent=4)
Exemple #7
0
    def export(self,
               filepath: str,
               tile_opener=None,
               tile_format: ImageFormat = ImageFormat.NUMPY) -> None:
        """write the image tensor to disk in spaceTx format

        Parameters
        ----------
        filepath : str
            Path + prefix for the images and primary_images.json written by this function
        tile_opener : TODO ttung: doc me.
        tile_format : ImageFormat
            Format in which each 2D plane should be written.

        """
        tileset = TileSet(
            dimensions={
                Indices.ROUND,
                Indices.CH,
                Indices.Z,
                Indices.Y,
                Indices.X,
            },
            shape={
                Indices.ROUND: self.num_rounds,
                Indices.CH: self.num_chs,
                Indices.Z: self.num_zlayers,
            },
            default_tile_shape=self._tile_shape,
            extras=self._tile_metadata.extras,
        )
        for round_ in range(self.num_rounds):
            for ch in range(self.num_chs):
                for zlayer in range(self.num_zlayers):
                    tilekey = TileKey(round=round_, ch=ch, z=zlayer)
                    extras: dict = self._tile_metadata[tilekey]

                    tile_indices = {
                        Indices.ROUND: round_,
                        Indices.CH: ch,
                        Indices.Z: zlayer,
                    }

                    coordinates: MutableMapping[Coordinates,
                                                Tuple[Number,
                                                      Number]] = dict()
                    x_coordinates = self.tile_coordinates(
                        tile_indices, Coordinates.X)
                    y_coordinates = self.tile_coordinates(
                        tile_indices, Coordinates.Y)
                    z_coordinates = self.tile_coordinates(
                        tile_indices, Coordinates.Z)

                    coordinates[Coordinates.X] = x_coordinates
                    coordinates[Coordinates.Y] = y_coordinates
                    if z_coordinates[0] != np.nan and z_coordinates[
                            1] != np.nan:
                        coordinates[Coordinates.Z] = z_coordinates

                    tile = Tile(
                        coordinates=coordinates,
                        indices=tile_indices,
                        extras=extras,
                    )
                    tile.numpy_array, _ = self.get_slice(indices={
                        Indices.ROUND: round_,
                        Indices.CH: ch,
                        Indices.Z: zlayer
                    })
                    tileset.add_tile(tile)

        if tile_opener is None:

            def tile_opener(tileset_path, tile, ext):
                tile_basename = os.path.splitext(tileset_path)[0]
                if Indices.Z in tile.indices:
                    zval = tile.indices[Indices.Z]
                    zstr = "-Z{}".format(zval)
                else:
                    zstr = ""
                return open(
                    "{}-H{}-C{}{}.{}".format(
                        tile_basename,
                        tile.indices[Indices.ROUND],
                        tile.indices[Indices.CH],
                        zstr,
                        ext,
                    ), "wb")

        if not filepath.endswith('.json'):
            filepath += '.json'
        Writer.write_to_path(tileset,
                             filepath,
                             pretty=True,
                             tile_opener=tile_opener,
                             tile_format=tile_format)
Exemple #8
0
    def export(self,
               filepath: str,
               tile_opener: Optional[Callable[[PurePath, Tile, str], BinaryIO]] = None,
               tile_format: ImageFormat=ImageFormat.NUMPY) -> None:
        """write the image tensor to disk in spaceTx format

        Parameters
        ----------
        filepath : str
            Path + prefix for the images and primary_images.json written by this function
        tile_opener : Optional[Callable[[PurePath, Tile, str], BinaryIO]]
            A callable responsible for opening the file that a tile's data is to be written to. The
            callable should accept three arguments -- the path of the tileset, the tile data, and
            the expected file extension. If this is not specified, a reasonable default is provided.
        tile_format : ImageFormat
            Format in which each 2D plane should be written.

        """
        # Add log data to extras
        tileset_extras = self._tile_data.extras if self._tile_data else {}
        tileset_extras[STARFISH_EXTRAS_KEY] = self.log.encode()
        tileset = TileSet(
            dimensions={
                Axes.ROUND,
                Axes.CH,
                Axes.ZPLANE,
                Axes.Y,
                Axes.X,
            },
            shape={
                Axes.ROUND: self.num_rounds,
                Axes.CH: self.num_chs,
                Axes.ZPLANE: self.num_zplanes,
            },
            default_tile_shape={Axes.Y: self.tile_shape[0], Axes.X: self.tile_shape[1]},
            extras=tileset_extras,
        )
        for selector in self._iter_axes({Axes.ROUND, Axes.CH, Axes.ZPLANE}):
            tilekey = TileKey(
                round=selector[Axes.ROUND],
                ch=selector[Axes.CH],
                zplane=selector[Axes.ZPLANE])
            extras: dict = self._tile_data[tilekey] if self._tile_data else {}

            coordinates: MutableMapping[Coordinates, Union[Tuple[Number, Number], Number]] = dict()
            x_coordinates = (float(self.xarray[Coordinates.X.value][0]),
                             float(self.xarray[Coordinates.X.value][-1]))
            y_coordinates = (float(self.xarray[Coordinates.Y.value][0]),
                             float(self.xarray[Coordinates.Y.value][-1]))
            coordinates[Coordinates.X] = x_coordinates
            coordinates[Coordinates.Y] = y_coordinates
            if Coordinates.Z in self.xarray.coords:
                # set the z coord to the calculated value from the associated z plane
                z_coordinates = float(self.xarray[Coordinates.Z.value][selector[Axes.ZPLANE]])
                coordinates[Coordinates.Z] = z_coordinates

            tile = Tile(
                coordinates=coordinates,
                indices=selector,
                extras=extras,
            )
            tile.numpy_array, _ = self.get_slice(selector)
            tileset.add_tile(tile)

        if tile_opener is None:
            def tile_opener(tileset_path: PurePath, tile: Tile, ext: str):
                base = tileset_path.parent / tileset_path.stem
                if Axes.ZPLANE in tile.indices:
                    zval = tile.indices[Axes.ZPLANE]
                    zstr = "-Z{}".format(zval)
                else:
                    zstr = ""
                return open(
                    "{}-H{}-C{}{}.{}".format(
                        str(base),
                        tile.indices[Axes.ROUND],
                        tile.indices[Axes.CH],
                        zstr,
                        ext,
                    ),
                    "wb")

        if not filepath.endswith('.json'):
            filepath += '.json'
        Writer.write_to_path(
            tileset,
            filepath,
            pretty=True,
            tile_opener=tile_opener,
            tile_format=tile_format)