Coverage for /opt/conda/envs/apienv/lib/python3.10/site-packages/daiquiri/core/components/imageviewer/transform.py: 36%
70 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-14 02:13 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-14 02:13 +0000
1from dataclasses import dataclass
2import os
3import time
4from typing import List, Dict
6import numpy
7from PIL import Image
10def create_scale_matrix(scale_x: float, scale_y: float) -> numpy.ndarray:
11 return numpy.array([[scale_x, 0, 0], [0, scale_y, 0], [0, 0, 1]])
14def create_translation_matrix(offset_x: float, offset_y: float) -> numpy.ndarray:
15 return numpy.array([[1, 0, offset_x], [0, 1, offset_y], [0, 0, 1]])
18def calculate_transform_matrix(
19 refs: numpy.ndarray, reals: numpy.ndarray
20) -> numpy.ndarray:
21 A, res, rank, s = numpy.linalg.lstsq(refs, reals, rcond=-1)
22 return A.T
25@dataclass
26class Extremes:
27 width: int
28 height: int
29 min_x: int
30 max_x: int
31 min_y: int
32 max_y: int
35def get_extremes(matrix: numpy.ndarray, width: int, height: int) -> Extremes:
36 extremes_tl = numpy.dot(matrix, numpy.array([0, 0, 1]))
37 extremes_tr = numpy.dot(matrix, numpy.array([width, 0, 1]))
38 extremes_bl = numpy.dot(matrix, numpy.array([0, height, 1]))
39 extremes_br = numpy.dot(matrix, numpy.array([width, height, 1]))
41 min_x = min(extremes_tl[0], extremes_tr[0], extremes_bl[0], extremes_br[0])
42 max_x = max(extremes_tl[0], extremes_tr[0], extremes_bl[0], extremes_br[0])
44 min_y = min(extremes_tl[1], extremes_tr[1], extremes_bl[1], extremes_br[1])
45 max_y = max(extremes_tl[1], extremes_tr[1], extremes_bl[1], extremes_br[1])
47 width = max_x - min_x
48 height = max_y - min_y
49 print("width", width, "height", height, "ratio", width / height)
51 return Extremes(width, height, min_x, max_x, min_y, max_y)
54@dataclass
55class ExportedReference:
56 image_path: str
57 scale_factor: float
58 center_x: int
59 center_y: int
62def export_reference_to_sampleimage(
63 original_path: str,
64 snapshot_path: str,
65 reference_points: List[List[int]],
66 vlm_points: List[List[int]],
67 max_size=2000,
68 crop: Dict[str, List[int]] = None,
69) -> ExportedReference:
70 with Image.open(original_path) as original:
71 print("snapshot points", reference_points)
72 refs = [numpy.array([point[0], point[1], 1]) for point in reference_points]
73 vlms = [numpy.array([point[0], point[1], 1]) for point in vlm_points]
75 snapshot_dir = os.path.dirname(snapshot_path)
76 snapshot_file = os.path.basename(snapshot_path)
77 # To allow transparency
78 file_ext = os.path.splitext(snapshot_file)[1][1:]
79 sampleimage_file = f"{snapshot_dir}/transformed_{int(time.time())}_{snapshot_file.replace(file_ext, 'png')}"
81 # Calculate transform between reference and vlm points
82 A_T = calculate_transform_matrix(refs, vlms)
84 # Get extremities of new image
85 ext = get_extremes(A_T, original.size[0], original.size[1])
86 print("extremes", ext)
88 # Scale new image (default max 2000 pixels)
89 final_scale_factor = max_size / ext.width
90 scale_matrix2 = create_scale_matrix(final_scale_factor, final_scale_factor)
91 transform_matrix = numpy.dot(scale_matrix2, A_T)
93 # Get extremities of scaled image
94 ext2 = get_extremes(transform_matrix, original.size[0], original.size[1])
95 print("scaled extremes", ext2)
97 # Translate the new image into the viewport
98 translation_matrix = create_translation_matrix(-ext2.min_x, -ext2.min_y)
100 final_matrix = numpy.dot(translation_matrix, transform_matrix)
101 # PIL Transform.AFFINE needs the inverted tramsform matrix
102 final_matrix_inverted = numpy.linalg.inv(final_matrix)
104 original = original.convert("RGBA")
105 # Transform the original image
106 transformed_image = original.transform(
107 (int(round(ext2.width)), int(round(ext2.height))),
108 Image.AFFINE,
109 data=final_matrix_inverted.flatten()[:6],
110 resample=Image.NEAREST,
111 )
112 transformed_image.save(sampleimage_file)
114 # Calculate the new centre of the image (exclude final translate)
115 old_center = numpy.array(original.size) / 2
116 new_center = numpy.dot(
117 transform_matrix, numpy.array([old_center[0], old_center[1], 1])
118 )
120 # nm -> um
121 scale_factor_um = 1 / final_scale_factor * 1e-3
122 print("scale factor", scale_factor_um)
123 image_centre = new_center[0:2] / final_scale_factor
124 print("center", old_center, image_centre)
126 return ExportedReference(
127 image_path=sampleimage_file,
128 scale_factor=scale_factor_um,
129 center_x=image_centre[0],
130 center_y=image_centre[1],
131 )