Spaces:
Runtime error
Runtime error
#!/usr/bin/env python3 | |
""" | |
Copyright (c) 2020, Carleton University Biomedical Informatics Collaboratory | |
This source code is licensed under the MIT license found in the | |
LICENSE file in the root directory of this source tree. | |
""" | |
from typing import Optional, List | |
import os | |
from PIL import Image, ImageDraw, ImageFont | |
import numpy as np | |
import cv2 | |
from .label import Label | |
from .line import Line | |
from .grid import Grid | |
class Report(object): | |
def __init__(self, filename: Optional[str] = None, image: Optional[Image.Image] = None): | |
assert not (filename and image) and bool(filename) != bool(image) | |
if filename: | |
self.filename = filename | |
self.pil_image = Image.open(filename) | |
else: | |
self.filename = None | |
self.pil_image = image | |
def rescale(self, factor: float) -> "Report": | |
"""Creates a new Report that has been resized. | |
Parameters | |
---------- | |
factor : float | |
The resize factor. | |
Returns | |
------- | |
Report | |
A new Report that has be rescaled. | |
""" | |
return Report(image=self.pil_image.resize(int(self.pil_image.width * factor), int(self.pil_image.height * factor))) | |
def rotate(self, angle: float) -> "Report": | |
"""Creates a new Report that has been rotated. | |
Parameters | |
---------- | |
angle : float | |
The rotation (in degrees) to apply (CCW). | |
Returns | |
------- | |
Report | |
A new Report that has be rotated. | |
""" | |
return Report(image=self.pil_image.rotate(angle, center=(0,0), fillcolor="rgb(255,255,255)")) | |
def crop(self, x1: int, y1: int, x2: int, y2: int) -> "Report": | |
"""Creates a new cropped Report. | |
Parameters | |
---------- | |
x1: int | |
The x pixel coordinate of the top-left corner. | |
y1: int | |
The y pixel coordinate of the top-left corner. | |
x2: int | |
The x pixel coordinate of the bottom-right corner. | |
y2: int | |
The y pixel coordinate of the bottom-right corner. | |
Returns | |
------- | |
Report | |
A new cropped Report. | |
""" | |
return Report(image=self.pil_image.crop((x1, y1, x2, y2))) | |
def show( | |
self, | |
labels: Optional[List[Label]] = [], | |
lines: Optional[List[Line]] = [], | |
grids: Optional[List[Grid]] = [], | |
points: Optional[List[tuple]] = [], | |
title: Optional[str] = None, | |
filename: Optional[str] = None | |
): | |
"""Displays the audiogram on the screen, and saves it to a file | |
if `filename` parameter is provided. | |
Parameters | |
---------- | |
labels : List[Label] | |
A list of labels to show (default: []). | |
lines : List[Lines] | |
A list of lines to show (default: []). | |
grids : List[Grid] | |
A list of grids to show (default: []). | |
points : List[dict] | |
A list of points of the form { "x": int, "y": int } to show (default: []). | |
title: str | |
The title of the plot | |
filename: Optional[str] | |
The path to where the image should be save. Image is not saved | |
if no filename is provided (default: None). | |
""" | |
labeled_copy = self.pil_image.copy().convert("RGB") | |
drawing = ImageDraw.Draw(labeled_copy) | |
fontpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "assets", "fonts", "Arial.ttf") | |
font = None | |
try: | |
font = ImageFont.truetype(fontpath, 32) | |
except: | |
font = ImageFont.truetype("DejaVuSans.ttf", 32) | |
for label in labels: | |
label.draw(drawing) | |
for line in lines: | |
line.draw(drawing) | |
for grid in grids: | |
grid.draw(drawing) | |
for point in points: | |
r = 5 # radius | |
drawing.ellipse([(point[0] - r, point[1] - r), (point[0] + r, point[1] + r)], fill="rgb(0,0,255)") | |
if title: | |
drawing.text((self.pil_image.size[0]/2, 50), title, font=font, align="center", fill="rgb(53,155,232)") | |
if filename: | |
labeled_copy.save(filename) | |
labeled_copy.show() | |
def save( | |
self, | |
filename: str, | |
labels: Optional[List[Label]] = [], | |
lines: Optional[List[Line]] = [], | |
grids: Optional[List[Grid]] = [], | |
title: Optional[str] = None, | |
): | |
"""Saves the report to a file along with annotations for the | |
provided report elements. | |
Parameters | |
---------- | |
filename: str | |
The path to where the image should be save. | |
labels : List[Label] | |
A list of labels to show (default: []). | |
lines : List[Lines] | |
A list of lines to show (default: []). | |
grids : List[Grid] | |
A list of grids to show (default: []). | |
title: str | |
The title of the plot | |
""" | |
labeled_copy = self.pil_image.copy().convert("RGB") | |
drawing = ImageDraw.Draw(labeled_copy) | |
fontpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "assets", "Arial.ttf") | |
font = None | |
try: | |
font = ImageFont.truetype(fontpath, 32) | |
except: | |
font = ImageFont.truetype("DejaVuSans.ttf", 32) | |
for label in labels: | |
label.draw(drawing) | |
for line in lines: | |
line.draw(drawing) | |
for grid in grids: | |
grid.draw(drawing) | |
if title: | |
drawing.text((self.pil_image.size[0]/2, 50), title, font=font, align="center", fill="rgb(53,155,232)") | |
labeled_copy.save(filename) | |
def get_image(self, resize_factor: float = 1) -> Image: | |
"""Returns a copy of the PIL image representing the report. | |
Parameters | |
---------- | |
resize_factor : float | |
The resize factor of the image sought (default: 1). | |
Returns | |
------- | |
PIL.Image | |
A copy of the image at the resize factor provided. | |
""" | |
return self.pil_image.resize( | |
(int(self.pil_image.size[0] * resize_factor), | |
int(self.pil_image.size[1] * resize_factor)) | |
) | |
def detect_lines(self, threshold=250) -> List[Line]: | |
"""Detects lines in the report using the Hough Transform. | |
For details, see: https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghlines/py_houghlines.html | |
Parameters | |
---------- | |
threshold : int | |
The threshold above which a line is detected. See documentation for OpenCV's HoughLine function for details. | |
Returns | |
------- | |
List[Line] | |
A list of lines detected in the report. | |
""" | |
gray = np.array(self.get_image()) | |
edges = cv2.Canny(gray, 150, 300, apertureSize = 3) | |
lines = cv2.HoughLines(edges, 1, np.pi/180, threshold, None, 0, 0) | |
if lines is None: | |
return [] | |
lines_list = [] | |
for line in lines: | |
for l in (line[0],): | |
rho = l[0] | |
theta = l[1] | |
a = np.cos(theta) | |
b = np.sin(theta) | |
x0 = a*rho | |
y0 = b*rho | |
x1 = int(x0 + 1000*(-b)) | |
y1 = int(y0 + 1000*(a)) | |
x2 = int(x0 - 1000*(-b)) | |
y2 = int(y0 - 1000*(a)) | |
lines_list.append(Line(x1, y1, x2, y2)) | |
return lines_list | |