|
import sys |
|
from pathlib import Path |
|
from skimage.io import imread, imsave |
|
from skimage.transform import resize |
|
from skimage.color import rgba2rgb |
|
from argparse import ArgumentParser |
|
import numpy as np |
|
|
|
IMG_EXTENSIONS = set( |
|
[".jpg", ".JPG", ".jpeg", ".JPEG", ".png", ".PNG", ".ppm", ".PPM", ".bmp", ".BMP"] |
|
) |
|
|
|
|
|
def is_image_file(filename): |
|
"""Check that a file's name points to a known image format |
|
""" |
|
if isinstance(filename, Path): |
|
return filename.suffix in IMG_EXTENSIONS |
|
|
|
return Path(filename).suffix in IMG_EXTENSIONS |
|
|
|
|
|
def find_images(path, recursive=False): |
|
""" |
|
Get a list of all images contained in a directory: |
|
|
|
- path.glob("*") if not recursive |
|
- path.glob("**/*") if recursive |
|
""" |
|
p = Path(path) |
|
assert p.exists() |
|
assert p.is_dir() |
|
pattern = "*" |
|
if recursive: |
|
pattern += "*/*" |
|
|
|
return [i for i in p.glob(pattern) if i.is_file() and is_image_file(i)] |
|
|
|
|
|
def uint8(array): |
|
return array.astype(np.uint8) |
|
|
|
|
|
def crop_and_resize(image_path, label_path): |
|
""" |
|
Resizes an image so that it keeps the aspect ratio and the smallest dimensions |
|
is 640, then crops this resized image in its center so that the output is 640x640 |
|
without aspect ratio distortion |
|
|
|
Args: |
|
image_path (Path or str): Path to an image |
|
label_path (Path or str): Path to the image's associated label |
|
|
|
Returns: |
|
tuple((np.ndarray, np.ndarray)): (new image, new label) |
|
""" |
|
dolab = label_path is not None |
|
|
|
img = imread(image_path) |
|
if dolab: |
|
lab = imread(label_path) |
|
|
|
if img.shape[-1] == 4: |
|
img = uint8(rgba2rgb(img) * 255) |
|
|
|
if dolab and img.shape != lab.shape: |
|
print("\nWARNING: shape mismatch. Entering breakpoint to investigate:") |
|
breakpoint() |
|
|
|
|
|
h, w = img.shape[:2] |
|
if h < w: |
|
size = (640, int(640 * w / h)) |
|
else: |
|
size = (int(640 * h / w), 640) |
|
|
|
r_img = resize(img, size, preserve_range=True, anti_aliasing=True) |
|
r_img = uint8(r_img) |
|
|
|
if dolab: |
|
|
|
r_lab = resize(lab, size, preserve_range=True, anti_aliasing=False, order=0) |
|
r_lab = uint8(r_lab) |
|
|
|
|
|
H, W = r_img.shape[:2] |
|
|
|
top = (H - 640) // 2 |
|
left = (W - 640) // 2 |
|
|
|
rc_img = r_img[top : top + 640, left : left + 640, :] |
|
if dolab: |
|
rc_lab = r_lab[top : top + 640, left : left + 640, :] |
|
else: |
|
rc_lab = None |
|
|
|
return rc_img, rc_lab |
|
|
|
|
|
def label(img, label, alpha=0.4): |
|
return uint8(alpha * label + (1 - alpha) * img) |
|
|
|
|
|
if __name__ == "__main__": |
|
parser = ArgumentParser() |
|
parser.add_argument( |
|
"-i", "--input_dir", type=str, help="Directory to recursively read images from" |
|
) |
|
parser.add_argument( |
|
"-o", |
|
"--output_dir", |
|
type=str, |
|
help="Where to writ the result of the script," |
|
+ " keeping the input dir's structure", |
|
) |
|
parser.add_argument( |
|
"--no_labels", |
|
action="store_true", |
|
help="Only process images, don't look for labels", |
|
) |
|
parser.add_argument( |
|
"--store_labeled", |
|
action="store_true", |
|
help="Store a superposition of the label and the image in out/labeled/", |
|
) |
|
args = parser.parse_args() |
|
|
|
dolab = not args.no_labels |
|
dolabeled = args.store_labeled |
|
|
|
input_base = Path(args.input_dir).expanduser().resolve() |
|
output_base = Path(args.output_dir).expanduser().resolve() |
|
|
|
input_images = input_base / "imgs" |
|
output_images = output_base / "imgs" |
|
|
|
if dolab: |
|
input_labels = input_base / "labels" |
|
output_labels = output_base / "labels" |
|
if dolabeled: |
|
output_labeled = output_base / "labeled" |
|
|
|
print("Input images:", str(input_images)) |
|
print("Output images:", str(output_images)) |
|
if dolab: |
|
print("Input labels:", str(input_labels)) |
|
print("Output labels:", str(output_labels)) |
|
if dolabeled: |
|
print("Output labeled:", str(output_labeled)) |
|
else: |
|
print("NO LABEL PROCESSING (args.no_labels is specified)") |
|
print() |
|
|
|
assert input_images.exists() |
|
if dolab: |
|
assert input_labels.exists() |
|
|
|
if output_base.exists(): |
|
if ( |
|
"n" |
|
in input( |
|
"WARNING: output dir already exists." |
|
+ " Overwrite its content? (y/n, default: y)" |
|
).lower() |
|
): |
|
sys.exit() |
|
|
|
output_images.mkdir(parents=True, exist_ok=True) |
|
if dolab: |
|
output_labels.mkdir(parents=True, exist_ok=True) |
|
if dolabeled: |
|
output_labeled.mkdir(parents=True, exist_ok=True) |
|
|
|
images_paths = list( |
|
map(Path, sorted((map(str, find_images(input_images, recursive=True))))) |
|
) |
|
if dolab: |
|
labels_paths = list( |
|
map(Path, sorted((map(str, find_images(input_labels, recursive=True))))) |
|
) |
|
else: |
|
labels_paths = [None] * len(images_paths) |
|
|
|
for i, (image_path, label_path) in enumerate(zip(images_paths, labels_paths)): |
|
print( |
|
f"Processing {i + 1 :3} / {len(images_paths)} : {image_path.name}", |
|
end="\r", |
|
flush=True, |
|
) |
|
processed_image, processed_label = crop_and_resize(image_path, label_path) |
|
imsave(output_images / f"{image_path.stem}.png", processed_image) |
|
if dolab: |
|
imsave(output_labels / f"{label_path.stem}.png", processed_label) |
|
if dolabeled: |
|
labeled = label(processed_image, processed_label) |
|
imsave(output_labeled / f"{image_path.stem}.png", labeled) |
|
|
|
print("\nDone.") |
|
|