- add option to skip autoscaling brightness when saving as mp4

- let coords_pandas also deal with polars dataframes
This commit is contained in:
w.pomp
2025-09-25 15:20:00 +02:00
parent 1fe3b3c824
commit 1b5febc35b
3 changed files with 48 additions and 12 deletions

View File

@@ -418,7 +418,7 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
) )
return new 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} | { return {key: value for key, value in self.__dict__.items() if key not in self.do_not_pickle} | {
"cache_size": self.cache.maxlen "cache_size": self.cache.maxlen
} }
@@ -1100,6 +1100,7 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
t: str | int | Sequence[int] = None, # noqa t: str | int | Sequence[int] = None, # noqa
colors: tuple[str] = None, colors: tuple[str] = None,
brightnesses: tuple[float] = None, brightnesses: tuple[float] = None,
no_scale_movie_brightnesses: bool = False,
scale: int = None, scale: int = None,
bar: bool = True, bar: bool = True,
speed: float = None, speed: float = None,
@@ -1133,9 +1134,20 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
frame = np.dstack([255 * frame * i for i in color]) frame = np.dstack([255 * frame * i for i in color])
return np.clip(np.round(frame), 0, 255).astype("uint8") 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",) 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 scale = scale or 1
shape_x = 2 * ((self.shape["x"] * scale + 1) // 2) shape_x = 2 * ((self.shape["x"] * scale + 1) // 2)
shape_y = 2 * ((self.shape["y"] * 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("-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("-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("-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) parser.add_argument("-S", "--movie-scale", help="upscale movie xy size, int", type=float)
args = parser.parse_args() args = parser.parse_args()
@@ -1559,6 +1578,7 @@ def main() -> None:
args.time, args.time,
args.movie_colors, args.movie_colors,
args.movie_brightnesses, args.movie_brightnesses,
args.no_scale_movie_brightnesses,
args.movie_scale, args.movie_scale,
bar=len(args.file) == 1, bar=len(args.file) == 1,
speed=args.movie_speed, speed=args.movie_speed,

View File

@@ -16,9 +16,14 @@ except ImportError:
sitk = None sitk = None
try: try:
from pandas import DataFrame, Series, concat import pandas as pd
except ImportError: except ImportError:
DataFrame, Series, concat = None, None, None pd = None
try:
import polars as pl
except ImportError:
pl = None
if hasattr(yaml, "full_load"): if hasattr(yaml, "full_load"):
@@ -126,9 +131,13 @@ class Transforms(dict):
return inverse return inverse
def coords_pandas(self, array, channel_names, columns=None): def coords_pandas(self, array, channel_names, columns=None):
if isinstance(array, DataFrame): if pd is None:
return concat([self.coords_pandas(row, channel_names, columns) for _, row in array.iterrows()], axis=1).T raise ImportError("pandas is not available")
elif isinstance(array, Series): 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 = [] key = []
if "C" in array: if "C" in array:
key.append(channel_names[int(array["C"])]) key.append(channel_names[int(array["C"])])
@@ -459,14 +468,21 @@ class Transform:
""" """
if self.is_unity(): if self.is_unity():
return array.copy() 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"] columns = columns or ["x", "y"]
array = array.copy() array = array.copy()
if isinstance(array, DataFrame): if isinstance(array, pd.DataFrame):
array[columns] = self.coords(np.atleast_2d(array[columns].to_numpy())) 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] array[columns] = self.coords(np.atleast_2d(array[columns].to_numpy()))[0]
return array 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 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)]) return np.array([self.inverse.transform.TransformPoint(i.tolist()) for i in np.asarray(array)])

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "ndbioimage" name = "ndbioimage"
version = "2025.8.0" version = "2025.9.0"
description = "Bio image reading, metadata and some affine registration." description = "Bio image reading, metadata and some affine registration."
authors = [ authors = [
{ name = "W. Pomp", email = "w.pomp@nki.nl" } { name = "W. Pomp", email = "w.pomp@nki.nl" }