def __init__( self, *, stack_axis: str, datasources: Iterable[DataSource], ): self.stack_axis = stack_axis self.datasources = sorted(datasources, key=lambda ds: ds.location[stack_axis]) tile_shapes = {ds.tile_shape for ds in self.datasources} if len(tile_shapes) != 1: raise ValueError( f"All datasources must have the same tile shape. Tile shapes: {tile_shapes}" ) tile_shape = tile_shapes.pop() if any(ds.shape[stack_axis] % tile_shape[stack_axis] != 0 for ds in self.datasources): raise ValueError( f"Stacking over axis that are not multiple of the tile size is not supported" ) self.stack_levels = [ ds.location[stack_axis] for ds in self.datasources ] interval = Interval5D.enclosing(ds.interval for ds in self.datasources) super().__init__(dtype=self.datasources[0].dtype, interval=interval, axiskeys=stack_axis + Point5D.LABELS.replace(stack_axis, ""), tile_shape=tile_shape)
def __setstate__(self, data: JsonValue): data_obj = ensureJsonObject(data) self.__init__( path=Path(ensureJsonString(data_obj.get("path"))), location=Interval5D.from_json_value(data_obj.get("interval")).start, filesystem=JsonableFilesystem.from_json_value(data_obj.get("filesystem")) )
def from_json_value(cls, data: JsonValue) -> "Annotation": data_dict = ensureJsonObject(data) raw_voxels = ensureJsonArray(data_dict.get("voxels")) voxels: Sequence[Point5D] = [ Point5D.from_json_value(raw_voxel) for raw_voxel in raw_voxels ] color = Color.from_json_data(data_dict.get("color")) raw_data = DataSource.from_json_value(data_dict.get("raw_data")) start = Point5D.min_coords(voxels) stop = Point5D.max_coords( voxels ) + 1 # +1 because slice.stop is exclusive, but max_point isinclusive scribbling_roi = Interval5D.create_from_start_stop(start=start, stop=stop) if scribbling_roi.shape.c != 1: raise ValueError( f"Annotations must not span multiple channels: {voxels}") scribblings = Array5D.allocate(scribbling_roi, dtype=np.dtype(bool), value=False) for voxel in voxels: scribblings.paint_point(point=voxel, value=True) return cls(scribblings._data, axiskeys=scribblings.axiskeys, color=color, raw_data=raw_data, location=start)
def interpolate_from_points(cls, color: Color, voxels: Sequence[Point5D], raw_data: DataSource): start = Point5D.min_coords(voxels) stop = Point5D.max_coords( voxels ) + 1 # +1 because slice.stop is exclusive, but max_point isinclusive scribbling_roi = Interval5D.create_from_start_stop(start=start, stop=stop) if scribbling_roi.shape.c != 1: raise ValueError( f"Annotations must not span multiple channels: {voxels}") scribblings = Array5D.allocate(scribbling_roi, dtype=np.dtype(bool), value=False) anchor = voxels[0] for voxel in voxels: for interp_voxel in anchor.interpolate_until(voxel): scribblings.paint_point(point=interp_voxel, value=True) anchor = voxel return cls(scribblings._data, axiskeys=scribblings.axiskeys, color=color, raw_data=raw_data, location=start)
def label_at(self, point: Point5D) -> int: point_roi = Interval5D.enclosing([point]) if not self.interval.contains(point_roi): raise ValueError(f"Point {point} is not inside the labels at {self.interval}") label = self.cut(point_roi).raw("x")[0] if label == 0: raise ValueError(f"Point {point} is not on top of an object") return label
def show(self): data = self.data_tile.retrieve().cut(copy=True) for axis in "xyz": increment = Point5D.zero(**{axis: 1}) for pos in (self.position + increment, self.position - increment): if data.interval.contains(Interval5D.enclosing([pos])): data.paint_point(pos, 0) data.paint_point(self.position, 255) data.show_images()
def _get_tile(self, tile: Interval5D) -> Array5D: if self.location != self.scale.voxel_offset: tile = tile.translated(-self.location).translated( self.scale.location) tile_path = self.path / self.scale.get_tile_path(tile) with self.filesystem.openbin(tile_path.as_posix()) as f: raw_tile_bytes = f.read() tile_5d = self.scale.encoding.decode( roi=tile, dtype=self.dtype, raw_chunk=raw_tile_bytes) #type: ignore return tile_5d
def merge(annotations: Sequence["Annotation"], color_map: Optional[Dict[Color, np.uint8]] = None) -> Array5D: out_roi = Interval5D.enclosing(annot.interval for annot in annotations) out = Array5D.allocate(interval=out_roi, value=0, dtype=np.dtype('uint8')) color_map = color_map or Color.create_color_map( annot.color for annot in annotations) for annot in annotations: out.set(annot.colored(color_map[annot.color]), mask_value=0) return out
def __init__( self, *, position: Point5D, # an anchor point within datasource klass: int, datasource: DataSource, # an object label is tied to the datasource components_extractor: ConnectedComponentsExtractor, # and also to the method used to extract the objects ): position_roi = DataRoi(datasource, **Interval5D.enclosing([position]).to_dict()) self.data_tile = position_roi.get_tiles( datasource.tile_shape, clamp=False).__next__().clamped(datasource.shape) self.position = position self.klass = klass self.datasource = datasource self.components_extractor = components_extractor # compute connected components in constructor to prevent creation of bad annotation self.label = components_extractor.compute( self.data_tile).label_at(position)
def test_skimage_datasource_tiles(png_image: Path): bs = DataRoi(SkimageDataSource(png_image, filesystem=OsFs("/"))) num_checked_tiles = 0 for tile in bs.split(Shape5D(x=2, y=2)): if tile == Interval5D.zero(x=(0, 2), y=(0, 2)): expected_raw = raw_0_2x0_2y elif tile == Interval5D.zero(x=(0, 2), y=(2, 4)): expected_raw = raw_0_2x2_4y elif tile == Interval5D.zero(x=(2, 4), y=(0, 2)): expected_raw = raw_2_4x0_2y elif tile == Interval5D.zero(x=(2, 4), y=(2, 4)): expected_raw = raw_2_4x2_4y elif tile == Interval5D.zero(x=(4, 5), y=(0, 2)): expected_raw = raw_4_5x0_2y elif tile == Interval5D.zero(x=(4, 5), y=(2, 4)): expected_raw = raw_4_5x2_4y else: raise Exception(f"Unexpected tile {tile}") assert (tile.retrieve().raw("yx") == expected_raw).all() num_checked_tiles += 1 assert num_checked_tiles == 6
def get_expected_roi(self, data_slice: Interval5D) -> Interval5D: c_start = data_slice.c[0] c_stop = c_start + self.num_classes return data_slice.updated(c=(c_start, c_stop))
def _get_tile(self, tile: Interval5D) -> Array5D: slices = tile.translated(-self.location).to_slices(self.axiskeys) raw: np.ndarray = self._dataset[slices] return Array5D(raw, axiskeys=self.axiskeys, location=tile.start)
def interval(self) -> Interval5D: return Interval5D(t=self.t, c=self.c, x=self.x, y=self.y, z=self.z)
def is_tile(self, tile: Interval5D) -> bool: return tile.is_tile(tile_shape=self.tile_shape, full_interval=self.interval, clamped=True)