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

1from dataclasses import dataclass 

2import os 

3import time 

4from typing import List, Dict 

5 

6import numpy 

7from PIL import Image 

8 

9 

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]]) 

12 

13 

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]]) 

16 

17 

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 

23 

24 

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 

33 

34 

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])) 

40 

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]) 

43 

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]) 

46 

47 width = max_x - min_x 

48 height = max_y - min_y 

49 print("width", width, "height", height, "ratio", width / height) 

50 

51 return Extremes(width, height, min_x, max_x, min_y, max_y) 

52 

53 

54@dataclass 

55class ExportedReference: 

56 image_path: str 

57 scale_factor: float 

58 center_x: int 

59 center_y: int 

60 

61 

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] 

74 

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')}" 

80 

81 # Calculate transform between reference and vlm points 

82 A_T = calculate_transform_matrix(refs, vlms) 

83 

84 # Get extremities of new image 

85 ext = get_extremes(A_T, original.size[0], original.size[1]) 

86 print("extremes", ext) 

87 

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) 

92 

93 # Get extremities of scaled image 

94 ext2 = get_extremes(transform_matrix, original.size[0], original.size[1]) 

95 print("scaled extremes", ext2) 

96 

97 # Translate the new image into the viewport 

98 translation_matrix = create_translation_matrix(-ext2.min_x, -ext2.min_y) 

99 

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) 

103 

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) 

113 

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 ) 

119 

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) 

125 

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 )