From a7908e83873731c541bd6874668396e574390bc0 Mon Sep 17 00:00:00 2001 From: Holger Frey Date: Thu, 13 Oct 2022 13:19:09 +0200 Subject: [PATCH] added sensospot_rename command for batch renaming images --- README.md | 1 + pyproject.toml | 1 + work_helpers/sensospot_rename.py | 92 ++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 work_helpers/sensospot_rename.py diff --git a/README.md b/README.md index af14ff0..40e7182 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ work-helpers Some helper scripts for the day to day work with Ubuntu in WSL2 + - **sensospot_rename**: rename sensospot images in a batch - **sg_mbp_new_version**: creates a new version folder, new excel changes files and modifies the overall changelog - **sg_mbp_release**: collect and annotate all files used for a safeguard workbook release - **sg_list_frms**: collect gitea issues and related frm filenames diff --git a/pyproject.toml b/pyproject.toml index f4ff9d3..73e99c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,3 +27,4 @@ sg_mbp_release = "work_helpers.sg_mbp_release:sg_mbp_release" sg_mbp_new_version = "work_helpers.sg_mbp_release:sg_mbp_new_version" sg_list_frms = "work_helpers.sg_frm_list:cli" random_password = "work_helpers.password:get_random_password" +sensospot_rename = "work_helpers.sensospot_rename:sensospot_rename" diff --git a/work_helpers/sensospot_rename.py b/work_helpers/sensospot_rename.py new file mode 100644 index 0000000..d88dc85 --- /dev/null +++ b/work_helpers/sensospot_rename.py @@ -0,0 +1,92 @@ +""" rename sensospot images """ + +import click +import pathlib +import shutil + + +RENAME_MAP_NAME = "rename_map.txt" +IMAGE_SUFFIXES = {".tif", ".jpg"} + + +def get_map_file_path(directory:str) -> pathlib.Path: + return pathlib.Path(directory) / RENAME_MAP_NAME + + +def list_images(directory:str) -> list[pathlib.Path]: + parent = pathlib.Path(directory) + non_hidden = (i for i in parent.iterdir() if not i.name.startswith(".")) + return [i for i in non_hidden if i.suffix.lower() in IMAGE_SUFFIXES] + + +def get_image_parts(image_path:pathlib.Path) -> tuple[str, str, str]: + return image_path.stem.rsplit("_", maxsplit=2) + + +def get_unique_parts(images:list[pathlib.Path]) -> list[list[str], list[str], list[str]]: + parts = [get_image_parts(p) for p in images] + return [sorted(set(items)) for items in zip(*parts)] + + +def write_rename_map(map_file:pathlib.Path) -> None: + images = list_images(directory) + parts = get_unique_parts(images) + headers = ["stems", "wells", "exposures"] + + lines = [] + for header, items in zip(headers, parts): + lines.append(f"# {header}") + lines.extend([f"{i}\t" for i in items]) + lines.append("") + lines.append("") + + map_file.write_text("\n".join(lines)) + + +def _parse_rename_map(content:str) -> tuple[str, str]: + lines = [line.strip() for line in content.splitlines()] + for i, line in enumerate(lines, start=1): + if not line or line.startswith("#"): + continue + if not "\t" in line: + raise ValueError(f"No tab in line {i}: '{line}'") + yield line.split("\t", maxsplit=1) + + +def read_rename_map(map_file:pathlib.Path) -> dict[str, str]: + content = map_file.read_text() + return {k: v for k, v in _parse_rename_map(content)} + + +def prepare_rename(images:list[pathlib.Path], rename_map:dict[str, str], sep="_") -> list[tuple[pathlib.Path, pathlib.Path]]: + for path in images: + renamed_parts = [rename_map[p] for p in get_image_parts(path)] + yield path, path.with_stem(sep.join(renamed_parts)) + + +@click.command() +@click.argument("directory", type=click.Path(exists=True, file_okay=False, dir_okay=True)) +def sensospot_rename(directory): + images = list_images(directory) + if not images: + raise click.UsageError(f"No images found in '{directory}'") + + map_file = get_map_file_path(directory) + if not map_file.is_file(): + write_rename_map(map_file) + click.echo(f"Prepared rename map at '{map_file!s}'") + click.echo(f"Rerun the command after editing the file.") + else: + click.echo(f"Reading rename map at '{map_file!s}'") + try: + rename_map = read_rename_map(map_file) + except ValueError as e: + raise click.UsageError(str(e)) + try: + prepared = list(prepare_rename(images, rename_map)) + except Exception: + raise click.UsageError("Could not rename images. Please check the image directory and rename map file.") + + for src, dst in prepared: + click.echo(f"renaming: {src} -> {dst}") + shutil.move(src, dst) \ No newline at end of file