You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
92 lines
3.0 KiB
92 lines
3.0 KiB
""" 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) |