diff --git a/ndbioimage/__init__.py b/ndbioimage/__init__.py index 47cdf77..87adefb 100755 --- a/ndbioimage/__init__.py +++ b/ndbioimage/__init__.py @@ -418,7 +418,7 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC): ) return new - def __getstate__(self) -> dict[str:Any]: + def __getstate__(self) -> dict[str, Any]: return {key: value for key, value in self.__dict__.items() if key not in self.do_not_pickle} | { "cache_size": self.cache.maxlen } @@ -1100,6 +1100,7 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC): t: str | int | Sequence[int] = None, # noqa colors: tuple[str] = None, brightnesses: tuple[float] = None, + no_scale_movie_brightnesses: bool = False, scale: int = None, bar: bool = True, speed: float = None, @@ -1133,9 +1134,20 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC): frame = np.dstack([255 * frame * i for i in color]) return np.clip(np.round(frame), 0, 255).astype("uint8") - ab = list(zip(*[get_ab(i) for i in self.transpose("cztyx")])) # type: ignore + if no_scale_movie_brightnesses and self.dtype.kind != "f": + info = np.iinfo(self.dtype) + ab = list(zip(*[(info.min, info.max) for _ in range(self.shape["c"])])) + elif no_scale_movie_brightnesses: + ab = list(zip(*[(0, 1) for _ in range(self.shape["c"])])) + else: + ab = list(zip(*[get_ab(i) for i in self.transpose("cztyx")])) # type: ignore colors = colors or ("r", "g", "b")[: self.shape["c"]] + max(0, self.shape["c"] - 3) * ("w",) - brightnesses = brightnesses or (1,) * self.shape["c"] + if brightnesses is None: + brightnesses = (1,) * self.shape["c"] + elif len(brightnesses) == 1: + brightnesses = brightnesses * self.shape["c"] + elif len(brightnesses) != self.shape["c"]: + raise ValueError("brightnesses must have same length as shape[c]") scale = scale or 1 shape_x = 2 * ((self.shape["x"] * scale + 1) // 2) shape_y = 2 * ((self.shape["y"] * scale + 1) // 2) @@ -1538,6 +1550,13 @@ def main() -> None: parser.add_argument("-C", "--movie-colors", help="colors for channels in movie", type=str, nargs="*") parser.add_argument("-V", "--movie_speed", help="speed of move, default = 25 / 7", type=float, default=None) parser.add_argument("-B", "--movie-brightnesses", help="scale brightness of each channel", type=float, nargs="*") + parser.add_argument( + "-N", + "--no-scale-movie-brightnesses", + help="do not scale brightness of each channel," + " if image file has an integer data type it's range will be scaled to [0, 1]", + action="store_true", + ) parser.add_argument("-S", "--movie-scale", help="upscale movie xy size, int", type=float) args = parser.parse_args() @@ -1559,6 +1578,7 @@ def main() -> None: args.time, args.movie_colors, args.movie_brightnesses, + args.no_scale_movie_brightnesses, args.movie_scale, bar=len(args.file) == 1, speed=args.movie_speed, diff --git a/ndbioimage/transforms.py b/ndbioimage/transforms.py index 3f53ecb..19a4a24 100644 --- a/ndbioimage/transforms.py +++ b/ndbioimage/transforms.py @@ -16,9 +16,14 @@ except ImportError: sitk = None try: - from pandas import DataFrame, Series, concat + import pandas as pd except ImportError: - DataFrame, Series, concat = None, None, None + pd = None + +try: + import polars as pl +except ImportError: + pl = None if hasattr(yaml, "full_load"): @@ -126,9 +131,13 @@ class Transforms(dict): return inverse def coords_pandas(self, array, channel_names, columns=None): - if isinstance(array, DataFrame): - return concat([self.coords_pandas(row, channel_names, columns) for _, row in array.iterrows()], axis=1).T - elif isinstance(array, Series): + if pd is None: + raise ImportError("pandas is not available") + if isinstance(array, pd.DataFrame): + return pd.concat( + [self.coords_pandas(row, channel_names, columns) for _, row in array.iterrows()], axis=1 + ).T + elif isinstance(array, pd.Series): key = [] if "C" in array: key.append(channel_names[int(array["C"])]) @@ -459,14 +468,21 @@ class Transform: """ if self.is_unity(): return array.copy() - elif DataFrame is not None and isinstance(array, (DataFrame, Series)): + elif pd is not None and isinstance(array, (pd.DataFrame, pd.Series)): columns = columns or ["x", "y"] array = array.copy() - if isinstance(array, DataFrame): + if isinstance(array, pd.DataFrame): array[columns] = self.coords(np.atleast_2d(array[columns].to_numpy())) - elif isinstance(array, Series): + elif isinstance(array, pd.Series): array[columns] = self.coords(np.atleast_2d(array[columns].to_numpy()))[0] return array + elif pl is not None and isinstance(array, (pl.DataFrame, pl.LazyFrame)): + columns = columns or ["x", "y"] + if isinstance(array, pl.DataFrame): + xy = self.coords(np.atleast_2d(array.select(columns).to_numpy())) + elif isinstance(array, pl.LazyFrame): + xy = self.coords(np.atleast_2d(array.select(columns).collect().to_numpy())) + return array.with_columns(**{c: i for c, i in zip(columns, xy.T)}) else: # somehow we need to use the inverse here to get the same effect as when using self.frame return np.array([self.inverse.transform.TransformPoint(i.tolist()) for i in np.asarray(array)]) diff --git a/pyproject.toml b/pyproject.toml index c3b70e3..910e219 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ndbioimage" -version = "2025.8.0" +version = "2025.9.0" description = "Bio image reading, metadata and some affine registration." authors = [ { name = "W. Pomp", email = "w.pomp@nki.nl" }