Source code for linumpy.cli.args
"""Common argument-parsing helpers for linumpy CLI scripts."""
import argparse
import multiprocessing
import os
import shutil
from pathlib import Path
[docs]
def get_available_cpus() -> int:
"""
Get the number of available CPUs, respecting environment variables.
Checks in order:
1. LINUMPY_MAX_CPUS - maximum CPUs to use (explicit limit)
2. LINUMPY_RESERVED_CPUS - CPUs to reserve for overhead (default: 0)
Returns
-------
int: Number of available CPUs
"""
total_cpus = multiprocessing.cpu_count()
# Check for explicit max CPUs limit
max_cpus = os.environ.get("LINUMPY_MAX_CPUS")
if max_cpus is not None:
try:
max_cpus = int(max_cpus)
return max(1, min(max_cpus, total_cpus))
except ValueError:
pass
# Check for reserved CPUs
reserved = os.environ.get("LINUMPY_RESERVED_CPUS")
if reserved is not None:
try:
reserved = int(reserved)
return max(1, total_cpus - reserved)
except ValueError:
pass
# Default: use all but 1 CPU
return max(1, total_cpus - 1)
[docs]
DEFAULT_N_CPUS = get_available_cpus()
[docs]
def parse_processes_arg(n_processes: int | None) -> int:
"""
Parse the n_processes argument, respecting system limits.
Args:
n_processes: Number of processes requested. If None or <= 0,
uses the default (get_available_cpus()).
Returns
-------
int: Number of processes to use
"""
available = get_available_cpus()
if n_processes is None or n_processes <= 0 or n_processes > available:
return available
return n_processes
[docs]
def add_processes_arg(parser: argparse.ArgumentParser | argparse._ActionsContainer) -> argparse.Action:
"""Add the ``--n_processes`` argument to *parser*."""
a = parser.add_argument(
"--n_processes", type=int, default=1, help="Number of processes to use. -1 to use all cores [%(default)s]."
)
return a
[docs]
def add_overwrite_arg(parser: argparse.ArgumentParser) -> None:
"""Add the ``-f`` / ``--overwrite`` flag to *parser*."""
parser.add_argument("-f", dest="overwrite", action="store_true", help="Force overwriting of the output files.")
[docs]
def assert_output_exists(output: Path, parser: argparse.ArgumentParser, args: argparse.Namespace) -> None:
"""Error out if *output* already exists and overwrite flag is not set."""
output_path = Path(output)
if output_path.exists():
if not args.overwrite:
parser.error(f"Output {output} exists. Use -f to overwrite.")
elif output_path.is_dir(): # remove the directory if it exists
shutil.rmtree(output)
[docs]
def add_verbose_arg(parser: argparse.ArgumentParser) -> None:
"""Add the ``-v`` / ``--verbose`` argument to *parser*."""
parser.add_argument(
"-v",
default="WARNING",
const="INFO",
nargs="?",
choices=["DEBUG", "INFO", "WARNING"],
dest="verbose",
help="Produces verbose output depending on "
"the provided level. \nDefault level is warning, "
"default when using -v is info.",
)
[docs]
def detect_shift_units(resolution: tuple) -> tuple[float, float]:
"""Detect whether OME-Zarr resolution is in mm or µm, return (res_x_um, res_y_um).
OME-Zarr resolution can be in mm (OME-NGFF standard) or µm depending on the writer.
Detects by magnitude: values < 1.0 are assumed mm, >= 1.0 are assumed µm.
Parameters
----------
resolution : sequence
Resolution tuple from read_omezarr (res_z, res_y, res_x).
Returns
-------
res_x_um, res_y_um : float
XY resolution in microns per pixel.
"""
res_x_raw = resolution[-1]
res_y_raw = resolution[-2] if len(resolution) >= 2 else res_x_raw
if res_x_raw < 1.0:
res_x_um = res_x_raw * 1000.0
res_y_um = res_y_raw * 1000.0
else:
res_x_um = float(res_x_raw)
res_y_um = float(res_y_raw)
return res_x_um, res_y_um