Nouveau contenu pour miniprojectutils.py (à copier-coller)
Code:
from dataclasses import dataclass
from typing import Tuple
import PIL.Image
import numpy as np
import os
# Type definitions
Image = np.ndarray
Kernel = list[list[float]]
Position = tuple[int, int]
Seam = list[int]
# Constant definitions
Infinity = float("inf")
@dataclass
class PixelData:
min_energy: int = Infinity
parent: Position = (-1, -1)
def __repr__(self) -> str:
return f"{self.min_energy} from ${self.parent}"
def split_name_ext(filename: str) -> tuple[str, str]:
"""Split a filename (or filepath) into its name or path without the extension, and its and extension separately"""
dot_position = filename.rfind(".")
if dot_position == -1:
return filename, ""
return filename[:dot_position], filename[dot_position:]
def load_image(filename: str) -> Image:
"""Load an image from a file and returns it as a numpy array"""
print(">> Reading image from", filename)
return np.array(PIL.Image.open(filename))
def save_image(img: Image, filename: str) -> None:
"""Save an image to a file"""
print("<< Writing image to", filename)
if not is_rgb(img):
# convert to 0-255 range
img = np.uint8(img)
PIL.Image.fromarray(img).save(filename)
def save_image_greyscale_details(img: Image, filename: str, seam: Seam | None = None, cell_data: list[list[PixelData]] | None = None) -> None:
"""Saves as an SVG file with zoomed pixels and indicated greyscale values"""
if is_rgb(img):
print("** Cannot write non-greyscale images this way")
return
if not filename.endswith(".svg"):
filename = filename + ".svg"
height, width = dimensions(img)
def center_for(coord: int) -> int:
return 10 * coord + 5
has_pixel_data = cell_data is not None
print("<< Writing SVG image details to", filename)
with open(filename, "w", encoding="utf-8") as file:
file.write(
f"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<svg width="{width * 100}" height="{height * 100}" viewBox="0 0 {width * 10} {height * 10}" xmlns="http://www.w3.org/2000/svg">\n"""
)
file.write("""<style type="text/css">\n""")
file.write(""" * { font-family: sans-serif; }\n""")
file.write(f""" .val {{ font-size: 2.5px; font-family: Helvetica, Arial, sans-serif; font-weight: bold; text-anchor: middle; dominant-baseline: middle; }}\n""")
file.write(""" .path { text-anchor: middle; dominant-baseline: middle; font-size: 4px; }\n""")
file.write(""" .pred { stroke: orange; stroke-width: 0.6px; }\n""")
file.write(""" .darkpx { fill: white; }\n""")
file.write(""" .brightpx { fill: black; }\n""")
file.write("""</style>\n""")
for row in range(height):
for col in range(width):
value = img[row, col]
file.write(
f"""<rect x="{col * 10}" y="{row * 10}" width="10" height="10" fill="rgb({value}, {value}, {value})" />\n"""
)
if seam:
file.write(f"""<path d="M {center_for(seam[0])} 0""")
for row, col in enumerate(seam):
file.write(f"L {center_for(col)} {center_for(row)}")
file.write(
f""" L {center_for(seam[-1])} {height * 10}" stroke="red" stroke-width="2" fill="none" />\n"""
)
for row in range(height):
for col in range(width):
value = img[row, col]
cls = "brightpx" if value > 128 else "darkpx"
if has_pixel_data and row < len(cell_data) and col < len(cell_data[row]) and (cell := cell_data[row][col]).min_energy != Infinity:
file.write(
f"""<text class="path {cls}" x="{center_for(col)}" y="{center_for(row) + 2}">{cell.min_energy}</text>\n"""
)
if row > 0:
pred_rel = cell.parent[1] - col
x, y = center_for(col), center_for(row) - 1
if pred_rel == 0:
file.write(
f"""<path class="pred" d="M {x} {y-4} l 0.5 1 l -1 0 z L {x}, {y}" />\n"""
)
elif pred_rel == 1:
file.write(
f"""<path class="pred" d="M {x + 5} {y-4.5} l -0.3 1.1 l -0.9 -.8 z L {x}, {y}" />\n"""
)
elif pred_rel == -1:
file.write(
f"""<path class="pred" d="M {x - 5} {y-4.5} l 0.3 1.1 l 0.9 -.8 z L {x}, {y}" />\n"""
)
file.write(
f"""<text class="val {cls}" x="{center_for(col) - 2.6}" y="{center_for(row) - 3}">{value}</text>\n"""
)
file.write("\n</svg>\n")
def dimensions(img: Image) -> Tuple[int, int]:
"""Return the dimensions of an image as a tuple (height, width)"""
return img.shape[0], img.shape[1]
def new_image_grey(height: int, width: int) -> Image:
"""Create a new greyscale image with the given dimensions"""
# int16 is used to hold all uint8 values and negative values,
# needed for the sobel filter
return np.zeros((height, width), dtype=np.int16)
def new_image_grey_with_data(data: list[list[int]]) -> Image:
"""Create a new greyscale image with the given pixel values"""
# could be uint8, but we use int16 to be consistent with new_image_grey
return np.array(data, dtype=np.int16)
def new_random_grey_image(height: int, width: int) -> Image:
"""Create a new greyscale image with random pixel values"""
return np.random.randint(0, 256, (height, width), dtype=np.uint16)
def new_image_rgb(height: int, width: int) -> Image:
"""Create a new RGB image with the given dimensions"""
return np.zeros((height, width, 3), dtype=np.uint8)
def is_rgb(img: Image) -> bool:
"""Return True if the image is RGB, False if it is greyscale"""
return len(img.shape) == 3
def copy_image(img: Image) -> Image:
"""Return a copy of the given image"""
return np.copy(img)
def highlight_seam(img: Image, seam: Seam) -> Image:
"""Return a copy of the given image with the given seam highlighted"""
print(" Highlighting seam...")
result = copy_image(img)
highlight_value = (255, 0, 0) if is_rgb(img) else 255
for row, col in enumerate(seam):
result[row, col] = highlight_value
return result
def remove_seam(img: Image, seam: Seam) -> Image:
"""Return a copy of the given image with the given seam removed"""
print(" Removing seam...")
height, width = dimensions(img)
if is_rgb(img):
result = new_image_rgb(height, width - 1)
else:
result = new_image_grey(height, width - 1)
for row in range(height):
for col in range(width - 1):
if col < seam[row]:
result[row, col] = img[row, col]
else:
result[row, col] = img[row, col + 1]
return result
from typing import Tuple
import PIL.Image
import numpy as np
import os
# Type definitions
Image = np.ndarray
Kernel = list[list[float]]
Position = tuple[int, int]
Seam = list[int]
# Constant definitions
Infinity = float("inf")
@dataclass
class PixelData:
min_energy: int = Infinity
parent: Position = (-1, -1)
def __repr__(self) -> str:
return f"{self.min_energy} from ${self.parent}"
def split_name_ext(filename: str) -> tuple[str, str]:
"""Split a filename (or filepath) into its name or path without the extension, and its and extension separately"""
dot_position = filename.rfind(".")
if dot_position == -1:
return filename, ""
return filename[:dot_position], filename[dot_position:]
def load_image(filename: str) -> Image:
"""Load an image from a file and returns it as a numpy array"""
print(">> Reading image from", filename)
return np.array(PIL.Image.open(filename))
def save_image(img: Image, filename: str) -> None:
"""Save an image to a file"""
print("<< Writing image to", filename)
if not is_rgb(img):
# convert to 0-255 range
img = np.uint8(img)
PIL.Image.fromarray(img).save(filename)
def save_image_greyscale_details(img: Image, filename: str, seam: Seam | None = None, cell_data: list[list[PixelData]] | None = None) -> None:
"""Saves as an SVG file with zoomed pixels and indicated greyscale values"""
if is_rgb(img):
print("** Cannot write non-greyscale images this way")
return
if not filename.endswith(".svg"):
filename = filename + ".svg"
height, width = dimensions(img)
def center_for(coord: int) -> int:
return 10 * coord + 5
has_pixel_data = cell_data is not None
print("<< Writing SVG image details to", filename)
with open(filename, "w", encoding="utf-8") as file:
file.write(
f"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<svg width="{width * 100}" height="{height * 100}" viewBox="0 0 {width * 10} {height * 10}" xmlns="http://www.w3.org/2000/svg">\n"""
)
file.write("""<style type="text/css">\n""")
file.write(""" * { font-family: sans-serif; }\n""")
file.write(f""" .val {{ font-size: 2.5px; font-family: Helvetica, Arial, sans-serif; font-weight: bold; text-anchor: middle; dominant-baseline: middle; }}\n""")
file.write(""" .path { text-anchor: middle; dominant-baseline: middle; font-size: 4px; }\n""")
file.write(""" .pred { stroke: orange; stroke-width: 0.6px; }\n""")
file.write(""" .darkpx { fill: white; }\n""")
file.write(""" .brightpx { fill: black; }\n""")
file.write("""</style>\n""")
for row in range(height):
for col in range(width):
value = img[row, col]
file.write(
f"""<rect x="{col * 10}" y="{row * 10}" width="10" height="10" fill="rgb({value}, {value}, {value})" />\n"""
)
if seam:
file.write(f"""<path d="M {center_for(seam[0])} 0""")
for row, col in enumerate(seam):
file.write(f"L {center_for(col)} {center_for(row)}")
file.write(
f""" L {center_for(seam[-1])} {height * 10}" stroke="red" stroke-width="2" fill="none" />\n"""
)
for row in range(height):
for col in range(width):
value = img[row, col]
cls = "brightpx" if value > 128 else "darkpx"
if has_pixel_data and row < len(cell_data) and col < len(cell_data[row]) and (cell := cell_data[row][col]).min_energy != Infinity:
file.write(
f"""<text class="path {cls}" x="{center_for(col)}" y="{center_for(row) + 2}">{cell.min_energy}</text>\n"""
)
if row > 0:
pred_rel = cell.parent[1] - col
x, y = center_for(col), center_for(row) - 1
if pred_rel == 0:
file.write(
f"""<path class="pred" d="M {x} {y-4} l 0.5 1 l -1 0 z L {x}, {y}" />\n"""
)
elif pred_rel == 1:
file.write(
f"""<path class="pred" d="M {x + 5} {y-4.5} l -0.3 1.1 l -0.9 -.8 z L {x}, {y}" />\n"""
)
elif pred_rel == -1:
file.write(
f"""<path class="pred" d="M {x - 5} {y-4.5} l 0.3 1.1 l 0.9 -.8 z L {x}, {y}" />\n"""
)
file.write(
f"""<text class="val {cls}" x="{center_for(col) - 2.6}" y="{center_for(row) - 3}">{value}</text>\n"""
)
file.write("\n</svg>\n")
def dimensions(img: Image) -> Tuple[int, int]:
"""Return the dimensions of an image as a tuple (height, width)"""
return img.shape[0], img.shape[1]
def new_image_grey(height: int, width: int) -> Image:
"""Create a new greyscale image with the given dimensions"""
# int16 is used to hold all uint8 values and negative values,
# needed for the sobel filter
return np.zeros((height, width), dtype=np.int16)
def new_image_grey_with_data(data: list[list[int]]) -> Image:
"""Create a new greyscale image with the given pixel values"""
# could be uint8, but we use int16 to be consistent with new_image_grey
return np.array(data, dtype=np.int16)
def new_random_grey_image(height: int, width: int) -> Image:
"""Create a new greyscale image with random pixel values"""
return np.random.randint(0, 256, (height, width), dtype=np.uint16)
def new_image_rgb(height: int, width: int) -> Image:
"""Create a new RGB image with the given dimensions"""
return np.zeros((height, width, 3), dtype=np.uint8)
def is_rgb(img: Image) -> bool:
"""Return True if the image is RGB, False if it is greyscale"""
return len(img.shape) == 3
def copy_image(img: Image) -> Image:
"""Return a copy of the given image"""
return np.copy(img)
def highlight_seam(img: Image, seam: Seam) -> Image:
"""Return a copy of the given image with the given seam highlighted"""
print(" Highlighting seam...")
result = copy_image(img)
highlight_value = (255, 0, 0) if is_rgb(img) else 255
for row, col in enumerate(seam):
result[row, col] = highlight_value
return result
def remove_seam(img: Image, seam: Seam) -> Image:
"""Return a copy of the given image with the given seam removed"""
print(" Removing seam...")
height, width = dimensions(img)
if is_rgb(img):
result = new_image_rgb(height, width - 1)
else:
result = new_image_grey(height, width - 1)
for row in range(height):
for col in range(width - 1):
if col < seam[row]:
result[row, col] = img[row, col]
else:
result[row, col] = img[row, col + 1]
return result
Last modified: Friday, 25 November 2022, 11:44