From 9ad88b562d7b6e500557a52eb3b459aca3b14dc4 Mon Sep 17 00:00:00 2001 From: AlgoFoe Date: Tue, 21 Jul 2026 20:06:30 +0530 Subject: [PATCH 1/4] docs: improve numpy-style docstrings in internal modules --- brainrender/_colors.py | 82 +++++++++++++++++++++++------ brainrender/_io.py | 115 ++++++++++++++++++++++++++++++++--------- brainrender/_utils.py | 56 ++++++++++++++++---- brainrender/_video.py | 38 +++++++++++--- 4 files changed, 234 insertions(+), 57 deletions(-) diff --git a/brainrender/_colors.py b/brainrender/_colors.py index 27b29a8a..765b3786 100644 --- a/brainrender/_colors.py +++ b/brainrender/_colors.py @@ -1,21 +1,43 @@ +"""Color mapping and palette utilities for brainrender.""" + import random import matplotlib as mpl import numpy as np +import numpy.typing as npt from vedo.colors import colors as vcolors from vedo.colors import get_color as getColor -def map_color(value, name="jet", vmin=None, vmax=None): - """Map a real value in range [vmin, vmax] to a (r,g,b) color scale. +def map_color( + value: float, + name: str = "jet", + vmin: float | None = None, + vmax: float | None = None, +) -> tuple[float, float, float]: + """ + Map a scalar value in ``[vmin, vmax]`` to an RGB colour. + + Parameters + ---------- + value + Scalar value to transform into a colour. + name + Colormap name or matplotlib colormap. Default ``"jet"``. + vmin + Lower bound of the value range. + vmax + Upper bound of the value range. - :param value: scalar value to transform into a color - :type value: float, list - :param name: color map name (Default value = "jet") - :type name: str, matplotlib.colors.LinearSegmentedColorMap - :param vmin: (Default value = None) - :param vmax: (Default value = None) - :returns: return: (r,g,b) color, or a list of (r,g,b) colors. + Returns + ------- + tuple of float + ``(r, g, b)`` colour. + + Raises + ------ + ValueError + If ``vmax`` is smaller than ``vmin``. """ if vmax < vmin: raise ValueError("vmax should be larger than vmin") @@ -31,13 +53,29 @@ def map_color(value, name="jet", vmin=None, vmax=None): return mp(value)[0:3] -def make_palette(N, *colors): - """Generate N colors starting from `color1` to `color2` - by linear interpolation HSV in or RGB spaces. - Adapted from vedo make_palette function +def make_palette(N: int, *colors: str) -> list[npt.NDArray]: + """ + Generate N colours interpolated across the given input colours. + + Adapted from vedo's ``make_palette`` function. Interpolation is + performed in RGB space. + + Parameters + ---------- + N + Number of output colours. + *colors + Input colours. Any number between 1 and N is accepted. + + Returns + ------- + list of numpy.ndarray + List of ``(r, g, b)`` colour arrays. - :param int: N: number of output colors. - :param colors: input colors, any number of colors with 0 < ncolors <= N is okay. + Raises + ------ + ValueError + If no colours are passed or more colours than N are passed. """ N = int(N) @@ -72,9 +110,19 @@ def make_palette(N, *colors): return output -def get_random_colors(n_colors=1): +def get_random_colors(n_colors: int = 1) -> str | list[str]: """ - :param n_colors: (Default value = 1) + Return one or more random colour names from vedo's colour palette. + + Parameters + ---------- + n_colors + Number of colours to return. Default 1. + + Returns + ------- + str or list of str + A single colour name if ``n_colors == 1``, otherwise a list. """ col_names = list(vcolors.keys()) if n_colors == 1: diff --git a/brainrender/_io.py b/brainrender/_io.py index 05babe05..f888b289 100644 --- a/brainrender/_io.py +++ b/brainrender/_io.py @@ -1,15 +1,30 @@ +"""File I/O and network utilities for brainrender.""" + +from collections.abc import Callable from pathlib import Path +from typing import Any import requests -from vedo import load +from vedo import load, Mesh, Volume -def connected_to_internet(url="http://www.google.com/", timeout=5): +def connected_to_internet( + url: str = "http://www.google.com/", + timeout: int = 5, +) -> bool: """ - Check that there is an internet connection - - :param url: url to use for testing (Default value = 'http://www.google.com/') - :param timeout: timeout to wait for [in seconds] (Default value = 5) + Check that there is an internet connection. + + Parameters + ---------- + url + URL to use for testing. Default ``"http://www.google.com/"``. + timeout + Timeout in seconds. Default 5. + + Returns + ------- + bool """ try: @@ -20,27 +35,54 @@ def connected_to_internet(url="http://www.google.com/", timeout=5): return False -def fail_on_no_connection(func): +def fail_on_no_connection(func: Callable) -> Callable: """ - Decorator that throws an error if no internet connection is available + Decorator that raises an error if no internet connection is available. + + Parameters + ---------- + func + Function to wrap. + + Returns + ------- + collections.abc.Callable + + Raises + ------ + ConnectionError + If no internet connection is found. """ if not connected_to_internet(): # pragma: no cover raise ConnectionError( "No internet connection found." ) # pragma: no cover - def inner(*args, **kwargs): + def inner(*args: Any, **kwargs: Any) -> Any: return func(*args, **kwargs) return inner -def request(url): +def request(url: str) -> requests.Response: """ - Sends a request to a url - - :param url: - + Send a GET request to a URL. + + Parameters + ---------- + url + URL to request. + + Returns + ------- + requests.Response + + Raises + ------ + ConnectionError + If no internet connection is found. + ValueError + If the request fails. """ if not connected_to_internet(): # pragma: no cover raise ConnectionError( @@ -57,13 +99,27 @@ def request(url): raise ValueError(exception_string) -def check_file_exists(func): # pragma: no cover +def check_file_exists(func: Callable) -> Callable: # pragma: no cover """ - Decorator that throws an error if a function;s first argument + Decorator that raises an error if a function's first argument is not a path to an existing file. + + Parameters + ---------- + func + Function to wrap. + + Returns + ------- + collections.abc.Callable + + Raises + ------ + FileNotFoundError + If the file does not exist. """ - def inner(*args, **kwargs): + def inner(*args: Any, **kwargs: Any) -> Any: if not Path(args[0]).exists(): raise FileNotFoundError( f"File {args[0]} not found" @@ -74,13 +130,26 @@ def inner(*args, **kwargs): @check_file_exists -def load_mesh_from_file(filepath, color=None, alpha=None): +def load_mesh_from_file( + filepath: str | Path, + color: str | None = None, + alpha: float | None = None, +) -> Mesh | Volume: """ - Load a a mesh or volume from files like .obj, .stl, ... - - :param filepath: path to file - :param **kwargs: - + Load a mesh or volume from a file (e.g. .obj, .stl). + + Parameters + ---------- + filepath + Path to the mesh file. + color + Colour to apply to the mesh. + alpha + Transparency to apply to the mesh. + + Returns + ------- + vedo.Mesh or vedo.Volume """ actor = load(str(filepath)) actor.c(color).alpha(alpha) diff --git a/brainrender/_utils.py b/brainrender/_utils.py index c20f1c31..061239d9 100644 --- a/brainrender/_utils.py +++ b/brainrender/_utils.py @@ -1,26 +1,53 @@ +"""General utility helpers for file system traversal and list manipulation.""" + from pathlib import Path +from typing import Any -def listdir(fld): +def listdir(fld: str | Path) -> list[str]: """ List the files into a folder with the complete file path instead of the relative file path like os.listdir. - :param fld: string, folder path + Parameters + ---------- + fld + Path to the folder. + Returns + ------- + list of str """ return [str(f) for f in Path(fld).glob("**/*") if f.is_file()] -def get_subdirs(folderpath): +def get_subdirs(folderpath: str | Path) -> list[str]: """ - Returns the subfolders in a given folder + Return all subdirectories in a given folder. + + Parameters + ---------- + folderpath + Path to the folder. + + Returns + ------- + list of str """ return [str(f) for f in Path(folderpath).glob("**/*") if f.is_dir()] -def listify(obj): +def listify(obj: Any) -> list: """ - Makes sure that the obj is a list + Ensure the object is a list. + + Parameters + ---------- + obj + Object to listify. + + Returns + ------- + list """ if isinstance(obj, list): return obj @@ -30,11 +57,20 @@ def listify(obj): return [obj] -def return_list_smart(lst): +def return_list_smart(lst: list) -> Any: """ - If the list has length > 1 returns the list - if it has length == 1 it returns the element - if it has length == 0 it returns None + Return a list, single element, or None depending on list length. + + Parameters + ---------- + lst + Input list. + + Returns + ------- + list, Any, or None + The list if length > 1, the single item if length == 1, + or None if empty. """ if len(lst) > 1: return lst diff --git a/brainrender/_video.py b/brainrender/_video.py index 97f8f548..17b90523 100644 --- a/brainrender/_video.py +++ b/brainrender/_video.py @@ -1,5 +1,8 @@ +"""Thin wrapper around vedo.Video for rendering brainrender scenes to video.""" + import os +from typing import Any from loguru import logger from myterial import amber_light from rich import print @@ -7,19 +10,40 @@ class Video(VtkVideo): - # Redefine vedo.Video close method - def __init__(self, *args, fmt="mp4", size="1620x1050", **kwargs): + def __init__( + self, + *args: Any, + fmt: str = "mp4", + size: str = "1620x1050", + **kwargs: Any, + ) -> None: """ - Video class, takes care of storing screenshots (frames) - as images in a temporary folder and then merging these into a - single video file when the video is closed. + Store screenshots as frames and merge them into a video on close. + + Parameters + ---------- + *args + Positional arguments forwarded to ``vedo.Video``. + fmt + Video format. Default ``"mp4"``. + size + Frame size in pixels. Default ``"1620x1050"``. + **kwargs + Keyword arguments forwarded to ``vedo.Video``. """ super().__init__(*args, **kwargs) self.format = fmt self.size = size - def close(self): - """Render the video and write to file.""" + def close(self) -> tuple[int, str]: + """ + Render the video and write to file. + + Returns + ------- + tuple + The FFmpeg exit code and the executed command string. + """ print(f"[{amber_light}]Saving video") logger.debug(f"[{amber_light}]Saving video") From 8757998733297f4b66af47e216d40b82c020f90f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:47:11 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- brainrender/_io.py | 2 +- brainrender/_video.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/brainrender/_io.py b/brainrender/_io.py index 9e04af68..41477a65 100644 --- a/brainrender/_io.py +++ b/brainrender/_io.py @@ -5,7 +5,7 @@ from typing import Any import requests -from vedo import load, Mesh, Volume +from vedo import Mesh, Volume, load def connected_to_internet( diff --git a/brainrender/_video.py b/brainrender/_video.py index 17b90523..8b0c64a3 100644 --- a/brainrender/_video.py +++ b/brainrender/_video.py @@ -1,8 +1,8 @@ """Thin wrapper around vedo.Video for rendering brainrender scenes to video.""" import os - from typing import Any + from loguru import logger from myterial import amber_light from rich import print From 9ae79782bb2e6a3fb1353c954a951b3eeb78b5ac Mon Sep 17 00:00:00 2001 From: AlgoFoe Date: Sat, 25 Jul 2026 16:17:19 +0530 Subject: [PATCH 3/4] Apply suggested changes --- brainrender/_io.py | 10 +++++++--- brainrender/_utils.py | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/brainrender/_io.py b/brainrender/_io.py index 9e04af68..e240b2a4 100644 --- a/brainrender/_io.py +++ b/brainrender/_io.py @@ -2,11 +2,14 @@ from collections.abc import Callable from pathlib import Path -from typing import Any +from typing import Any, ParamSpec, TypeVar import requests from vedo import load, Mesh, Volume +P = ParamSpec("P") +R = TypeVar("R") + def connected_to_internet( url: str = "http://www.google.com/", @@ -25,6 +28,7 @@ def connected_to_internet( Returns ------- bool + ``True`` if an internet connection is available, otherwise ``False``. """ try: @@ -35,7 +39,7 @@ def connected_to_internet( return False -def fail_on_no_connection(func: Callable) -> Callable: +def fail_on_no_connection(func: Callable[P, R]) -> Callable[P, R]: """ Decorator that raises an error if no internet connection is available. @@ -99,7 +103,7 @@ def request(url: str) -> requests.Response: raise ValueError(exception_string) -def check_file_exists(func: Callable) -> Callable: # pragma: no cover +def check_file_exists(func: Callable[P, R]) -> Callable[P, R]: # pragma: no cover """ Decorator that raises an error if a function's first argument is not a path to an existing file. diff --git a/brainrender/_utils.py b/brainrender/_utils.py index 061239d9..529e5429 100644 --- a/brainrender/_utils.py +++ b/brainrender/_utils.py @@ -1,8 +1,9 @@ """General utility helpers for file system traversal and list manipulation.""" from pathlib import Path -from typing import Any +from typing import TypeVar +T = TypeVar("T") def listdir(fld: str | Path) -> list[str]: """ @@ -36,7 +37,7 @@ def get_subdirs(folderpath: str | Path) -> list[str]: return [str(f) for f in Path(folderpath).glob("**/*") if f.is_dir()] -def listify(obj: Any) -> list: +def listify(obj: T | list[T] | tuple[T, ...]) -> list[T]: """ Ensure the object is a list. @@ -57,7 +58,7 @@ def listify(obj: Any) -> list: return [obj] -def return_list_smart(lst: list) -> Any: +def return_list_smart(lst: list[T]) -> list[T] | T | None: """ Return a list, single element, or None depending on list length. From f2ef486e0dff0b8e16d55a0544c304dd7b528b7b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 10:48:41 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- brainrender/_io.py | 4 +++- brainrender/_utils.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/brainrender/_io.py b/brainrender/_io.py index f109015d..46dfa367 100644 --- a/brainrender/_io.py +++ b/brainrender/_io.py @@ -103,7 +103,9 @@ def request(url: str) -> requests.Response: raise ValueError(exception_string) -def check_file_exists(func: Callable[P, R]) -> Callable[P, R]: # pragma: no cover +def check_file_exists( + func: Callable[P, R], +) -> Callable[P, R]: # pragma: no cover """ Decorator that raises an error if a function's first argument is not a path to an existing file. diff --git a/brainrender/_utils.py b/brainrender/_utils.py index 529e5429..947e15df 100644 --- a/brainrender/_utils.py +++ b/brainrender/_utils.py @@ -5,6 +5,7 @@ T = TypeVar("T") + def listdir(fld: str | Path) -> list[str]: """ List the files into a folder with the complete file path instead of the relative file path like os.listdir.