def test_crop_metadata(self, small_cont_1c: ImageContainer, dy: int): crop = small_cont_1c.crop_corner(dy, 0, 50, mask_circle=True) assert small_cont_1c.data.attrs[Key.img.coords] is _NULL_COORDS assert crop.data.attrs[Key.img.coords] == CropCoords(0, 0, 50, 50 + dy) assert crop.data.attrs[Key.img.padding] == CropPadding(x_pre=0, y_pre=abs(dy), x_post=0, y_post=0) assert crop.data.attrs[Key.img.mask_circle]
def transform_metadata(data: xr.Dataset) -> xr.Dataset: data.attrs[Key.img.coords] = CropCoords.from_tuple( data.attrs.get(Key.img.coords, _NULL_COORDS.to_tuple())) data.attrs[Key.img.padding] = CropPadding.from_tuple( data.attrs.get(Key.img.padding, _NULL_PADDING.to_tuple())) if Key.img.mask_circle not in data.attrs: data.attrs[Key.img.mask_circle] = False if Key.img.scale not in data.attrs: data.attrs[Key.img.scale] = 1 return data
def convert_to_full_image_coordinates(x: np.ndarray, y: np.ndarray) -> np.ndarray: if not len(y): return np.array([[]], dtype=np.float64) if self.data.attrs.get("mask_circle", False): if self.data.dims["y"] != self.data.dims["x"]: raise ValueError( f"Crop is not a square: `{self.data.dims}`.") c = self.data.dims["x"] // 2 # center mask = (x - c)**2 + (y - c)**2 <= c**2 y = y[mask] x = x[mask] if not len(y): return np.array( [[]], dtype=np.float64) # because of masking, should not happen coord = self.data.attrs.get( Key.img.coords, CropCoords(x0=0, y0=0, x1=self.data.dims["x"], y1=self.data.dims["y"] )) # fall back to default (i.e no crop) coordinates padding = self.data.attrs.get( Key.img.padding, _NULL_PADDING) # fallback to no padding y_slc, x_slc = coord.to_image_coordinates(padding).slice # relative coordinates y = (y - np.min(y)) / (np.max(y) - np.min(y)) x = (x - np.min(x)) / (np.max(x) - np.min(x)) # coordinates in the uncropped image y = coord.slice[0].start + (y_slc.stop - y_slc.start) * y x = coord.slice[1].start + (x_slc.stop - x_slc.start) * x return np.c_[x, y] # type: ignore[no-any-return]
def crop_corner( self, y: FoI_t, x: FoI_t, size: Optional[Union[FoI_t, Tuple[FoI_t, FoI_t]]] = None, scale: float = 1.0, cval: Union[int, float] = 0, mask_circle: bool = False, preserve_dtypes: bool = True, ) -> "ImageContainer": """ Extract a crop from the upper-left corner. Parameters ---------- %(yx)s %(size)s scale Rescale the crop using :func:`skimage.transform.rescale`. cval Fill value to use if ``mask_circle = True`` or if crop goes out of the image boundary. mask_circle Whether to mask out values that are not within a circle defined by this crop. Only available if ``size`` defines a square. preserve_dtypes Whether to preserver the data types of underlying :class:`xarray.DataArray`, even if ``cval`` is of different type. Returns ------- The cropped image of size ``size * scale``. Raises ------ ValueError If the crop would completely lie outside of the image or if ``mask_circle = True`` and ``size`` does not define a square. Notes ----- If ``preserve_dtypes = True`` but ``cval`` cannot be safely cast, ``cval`` will be set to 0. """ self._assert_not_empty() y, x = self._convert_to_pixel_space((y, x)) size = self._get_size(size) size = self._convert_to_pixel_space(size) ys, xs = size _assert_positive(ys, name="height") _assert_positive(xs, name="width") _assert_positive(scale, name="scale") orig = CropCoords(x0=x, y0=y, x1=x + xs, y1=y + ys) ymin, xmin = self.shape coords = CropCoords(x0=min(max(x, 0), xmin), y0=min(max(y, 0), ymin), x1=min(x + xs, xmin), y1=min(y + ys, ymin)) if not coords.dy: raise ValueError("Height of the crop is empty.") if not coords.dx: raise ValueError("Width of the crop is empty.") crop = self.data.isel(x=slice(coords.x0, coords.x1), y=slice(coords.y0, coords.y1)).copy(deep=False) crop.attrs[Key.img.coords] = coords if orig != coords: padding = orig - coords # because padding does not change dtype by itself for key, arr in crop.items(): if preserve_dtypes: if not np.can_cast(cval, arr.dtype, casting="safe"): cval = 0 else: crop[key] = crop[key].astype(np.dtype(type(cval)), copy=False) crop = crop.pad( y=(padding.y_pre, padding.y_post), x=(padding.x_pre, padding.x_post), mode="constant", constant_values=cval, ) crop.attrs["padding"] = padding else: crop.attrs["padding"] = _NULL_PADDING return self._from_dataset( self._post_process(data=crop, scale=scale, cval=cval, mask_circle=mask_circle, preserve_dtypes=preserve_dtypes))