batoolset 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. batoolset/GUI/__init__.py +0 -0
  2. batoolset/GUI/list_gui.py +513 -0
  3. batoolset/GUI/multi_image_display.py +989 -0
  4. batoolset/GUI/open.py +325 -0
  5. batoolset/GUI/overlay_hints.py +45 -0
  6. batoolset/GUI/paint2.py +1345 -0
  7. batoolset/GUI/scrollablepaint.py +1152 -0
  8. batoolset/IJ_ROIs/__init__.py +0 -0
  9. batoolset/IJ_ROIs/tools.py +40 -0
  10. batoolset/PATH/__init__.py +0 -0
  11. batoolset/PATH/tools.py +79 -0
  12. batoolset/SQLite_tools/__init__.py +0 -0
  13. batoolset/SQLite_tools/tools.py +840 -0
  14. batoolset/__init__.py +0 -0
  15. batoolset/binary/__init__.py +0 -0
  16. batoolset/binary/tools.py +123 -0
  17. batoolset/binarytools/__init__.py +0 -0
  18. batoolset/binarytools/cell_center_detector.py +290 -0
  19. batoolset/clipboard_tools/__init__.py +0 -0
  20. batoolset/clipboard_tools/tools.py +7 -0
  21. batoolset/colors/__init__.py +0 -0
  22. batoolset/colors/colorgen.py +193 -0
  23. batoolset/colors/tools.py +22 -0
  24. batoolset/command_line/__init__.py +0 -0
  25. batoolset/command_line/tools.py +18 -0
  26. batoolset/dialogs/__init__.py +0 -0
  27. batoolset/dialogs/opensave.py +228 -0
  28. batoolset/dicts/__init__.py +0 -0
  29. batoolset/dicts/tools.py +299 -0
  30. batoolset/dims/__init__.py +0 -0
  31. batoolset/dims/tools.py +83 -0
  32. batoolset/draw/__init__.py +0 -0
  33. batoolset/draw/shapes/Position.py +183 -0
  34. batoolset/draw/shapes/__init__.py +0 -0
  35. batoolset/draw/shapes/automated_alignment_drawing_refactored.py +346 -0
  36. batoolset/draw/shapes/circle2d.py +99 -0
  37. batoolset/draw/shapes/compute_optimal_size_all_merged.py +317 -0
  38. batoolset/draw/shapes/ellipse2d.py +336 -0
  39. batoolset/draw/shapes/freehand2d.py +235 -0
  40. batoolset/draw/shapes/group.py +1830 -0
  41. batoolset/draw/shapes/image2d.py +2469 -0
  42. batoolset/draw/shapes/line2d.py +575 -0
  43. batoolset/draw/shapes/point2d.py +257 -0
  44. batoolset/draw/shapes/polygon2d.py +357 -0
  45. batoolset/draw/shapes/polyline2d.py +235 -0
  46. batoolset/draw/shapes/rect2d.py +388 -0
  47. batoolset/draw/shapes/rectangle2d.py +601 -0
  48. batoolset/draw/shapes/scalebar.py +304 -0
  49. batoolset/draw/shapes/serializablefont.py +126 -0
  50. batoolset/draw/shapes/serializableguiparams.py +10 -0
  51. batoolset/draw/shapes/square2d.py +103 -0
  52. batoolset/draw/shapes/textorientation.py +45 -0
  53. batoolset/draw/shapes/tools.py +467 -0
  54. batoolset/draw/shapes/transform2D.py +350 -0
  55. batoolset/draw/shapes/txt2d.py +550 -0
  56. batoolset/draw/widgets/__init__.py +0 -0
  57. batoolset/draw/widgets/col.py +343 -0
  58. batoolset/draw/widgets/image_group.py +248 -0
  59. batoolset/draw/widgets/image_organizer.py +96 -0
  60. batoolset/draw/widgets/paint.py +184 -0
  61. batoolset/draw/widgets/vectorial.py +257 -0
  62. batoolset/figure/__init__.py +0 -0
  63. batoolset/figure/alignment.py +814 -0
  64. batoolset/figure/column.py +1117 -0
  65. batoolset/figure/fig_tools.py +285 -0
  66. batoolset/figure/minimal_debugger_scale_etc.py +139 -0
  67. batoolset/figure/panel.py +697 -0
  68. batoolset/figure/row.py +852 -0
  69. batoolset/figure/user_selection.py +287 -0
  70. batoolset/files/__init__.py +0 -0
  71. batoolset/files/tools.py +1321 -0
  72. batoolset/images/__init__.py +0 -0
  73. batoolset/images/tools.py +81 -0
  74. batoolset/img.py +5216 -0
  75. batoolset/linux/__init__.py +0 -0
  76. batoolset/linux/tools.py +72 -0
  77. batoolset/lists/__init__.py +0 -0
  78. batoolset/lists/tools.py +436 -0
  79. batoolset/luts/__init__.py +0 -0
  80. batoolset/luts/lut_minimal_test.py +1207 -0
  81. batoolset/matplolib/__init__.py +0 -0
  82. batoolset/matplolib/tools.py +44 -0
  83. batoolset/np_tools/__init__.py +0 -0
  84. batoolset/np_tools/tools.py +252 -0
  85. batoolset/np_tools/trash.py +15 -0
  86. batoolset/pandas_tools/__init__.py +0 -0
  87. batoolset/pandas_tools/tools.py +880 -0
  88. batoolset/partials/__init__.py +0 -0
  89. batoolset/partials/tools.py +83 -0
  90. batoolset/plot_tools/__init__.py +0 -0
  91. batoolset/plot_tools/tools.py +70 -0
  92. batoolset/pyqt/__init__.py +0 -0
  93. batoolset/pyqt/tools.py +691 -0
  94. batoolset/re_tools/__init__.py +0 -0
  95. batoolset/re_tools/tools.py +35 -0
  96. batoolset/serialization/__init__.py +0 -0
  97. batoolset/serialization/tools.py +1192 -0
  98. batoolset/sets/__init__.py +0 -0
  99. batoolset/sets/tools.py +109 -0
  100. batoolset/settings/__init__.py +0 -0
  101. batoolset/settings/global_settings.py +134 -0
  102. batoolset/strings/__init__.py +0 -0
  103. batoolset/strings/tools.py +225 -0
  104. batoolset/ta/__init__.py +0 -0
  105. batoolset/ta/database/__init__.py +0 -0
  106. batoolset/ta/database/advanced_sql_plotter.py +1170 -0
  107. batoolset/ta/database/my_qt_table.py +79 -0
  108. batoolset/ta/database/preview_and_edit_sqlite_db.py +405 -0
  109. batoolset/ta/database/sql.py +2141 -0
  110. batoolset/ta/database/sql_column_name_dialog.py +122 -0
  111. batoolset/ta/measurements/TAmeasures.py +2226 -0
  112. batoolset/ta/measurements/__init__.py +0 -0
  113. batoolset/ta/measurements/measurements3D/__init__.py +0 -0
  114. batoolset/ta/measurements/measurements3D/get_point_on_surface_if_centroid_is_bad.py +175 -0
  115. batoolset/ta/measurements/measurements3D/height_map_to_projection.py +82 -0
  116. batoolset/ta/measurements/measurements3D/measures3D.py +208 -0
  117. batoolset/ta/measurements/measurements3D/triangulate.py +331 -0
  118. batoolset/ta/measurements/nematic.py +562 -0
  119. batoolset/ta/measurements/nematic_measurements.py +107 -0
  120. batoolset/ta/segmentation/__init__.py +0 -0
  121. batoolset/ta/segmentation/neo_wshed.py +314 -0
  122. batoolset/ta/selections/__init__.py +0 -0
  123. batoolset/ta/selections/selection.py +329 -0
  124. batoolset/ta/tracking/__init__.py +0 -0
  125. batoolset/ta/tracking/last_tracking_based_on_matching_or_on_translation_from_mermaid_warp.py +653 -0
  126. batoolset/ta/tracking/local_to_track_correspondance.py +138 -0
  127. batoolset/ta/tracking/rapid_detection_of_vertices.py +517 -0
  128. batoolset/ta/tracking/registration.py +108 -0
  129. batoolset/ta/tracking/swap_detector_and_fixer.py +296 -0
  130. batoolset/ta/tracking/track_correction.py +238 -0
  131. batoolset/ta/tracking/tracking_error_detector_and_fixer.py +3099 -0
  132. batoolset/ta/tracking/tracking_yet_another_approach_pyramidal_registration_n_neo_swapping_correction.py +725 -0
  133. batoolset/ta/utils/TA_tools.py +43 -0
  134. batoolset/ta/utils/__init__.py +0 -0
  135. batoolset/ta/utils/rps_tools.py +1783 -0
  136. batoolset/tests/__init__.py +0 -0
  137. batoolset/tests/rps_tools.py +51 -0
  138. batoolset/tests/tools.py +10 -0
  139. batoolset/tools/__init__.py +0 -0
  140. batoolset/tools/early_stopper_class.py +9 -0
  141. batoolset/tools/guided_data_recoverer.py +110 -0
  142. batoolset/tools/logger.py +100 -0
  143. batoolset/tools/mytimer.py +31 -0
  144. batoolset/tools/prompt_color.py +160 -0
  145. batoolset/tools/qthandler.py +118 -0
  146. batoolset/uitools/__init__.py +0 -0
  147. batoolset/uitools/blinker.py +56 -0
  148. batoolset/unsafe/__init__.py +0 -0
  149. batoolset/unsafe/tools.py +115 -0
  150. batoolset/utils/__init__.py +0 -0
  151. batoolset/utils/commontools.py +158 -0
  152. batoolset/utils/loadlist.py +728 -0
  153. batoolset/version.py +10 -0
  154. batoolset/xml/__init__.py +0 -0
  155. batoolset/xml/tools.py +65 -0
  156. batoolset/zip_tools/__init__.py +0 -0
  157. batoolset/zip_tools/tools.py +76 -0
  158. batoolset-0.1.0.dist-info/LICENSE +24 -0
  159. batoolset-0.1.0.dist-info/METADATA +45 -0
  160. batoolset-0.1.0.dist-info/RECORD +162 -0
  161. batoolset-0.1.0.dist-info/WHEEL +5 -0
  162. batoolset-0.1.0.dist-info/top_level.txt +1 -0
File without changes
File without changes
@@ -0,0 +1,175 @@
1
+ # https://gis.stackexchange.com/questions/76498/how-is-st-pointonsurface-calculated # really great and what I'm looking for
2
+ # https://geocompr.robinlovelace.net/geometric-operations.html#centroids
3
+
4
+ # this sounds like a smart and nice method --> see how fast this is to implement that
5
+
6
+ # can I do a fast version of it that relies on vertices to find this point
7
+ # scan line by line the shape maybe using bounding box and take the center of the longest continuous line as the centroid --> maybe that is a really good idea
8
+
9
+ # the following code is based on the idea described here https://gis.stackexchange.com/questions/76498/how-is-st-pointonsurface-calculated
10
+ from math import ceil
11
+
12
+ import numpy as np
13
+ from skimage.measure._regionprops import RegionProperties
14
+
15
+ from batoolset.img import Img
16
+ import matplotlib.pyplot as plt
17
+ from skimage.measure import regionprops
18
+ from skimage import measure
19
+ from skimage.draw import disk
20
+ from timeit import default_timer as timer
21
+
22
+
23
+ # that seems to work but do some cleaning and the only issue is that if cell is too close to border, such as a fake cell surrounding the image then it will be the first line --> and it would be easy
24
+
25
+ # TODO also always use that for tracking as this should be much better than things I was using before and should prevent issues
26
+ __DEBUG__ = False
27
+
28
+ def point_on_surface2(region, labels, prefer_centroid_if_inside_the_cell=True):
29
+ # new version of point_on_surface just coded to be more flexible, code is not the same and behaviour may differ --> compare thoroughly some day
30
+ if isinstance(region, RegionProperties):
31
+ region =region.coords
32
+ cell_id = labels[region[0][0], region[0][1]]
33
+ centroid = np.rint(np.average(region, axis=0)).astype(int)
34
+ if prefer_centroid_if_inside_the_cell and labels[centroid[0], centroid[1]] == cell_id:
35
+ return centroid
36
+ else:
37
+ # TODO FIX THAT SOME DAY lazy way of doing just return the first pixel of the shape maybe some centroid like would be better but ok for wings and for what I wanna do --> MAYBE TAKE CODE IN ORIGINAL FUNCTION AND HACK IT TO WORK WITH NDARRAYS
38
+ return region[0]
39
+
40
+
41
+
42
+ # TODO clean code
43
+ # this point will by definition always be inside the cell even in concave shapes
44
+
45
+ # the other possibility is the ultimate erosion but this will be extremely slow and resource consuming; I guess
46
+ # inspired by https://gis.stackexchange.com/questions/76498/how-is-st-pointonsurface-calculated but then I deviated from it
47
+ def point_on_surface(region, labels, prefer_centroid_if_inside_the_cell=True):
48
+ # TODO --> maybe ceil the centroid to get a better centroid point (pb can be outside of the image although it is very unlikely (in fact it is not possible --> can ceil)
49
+
50
+ cell_id = labels[region.coords[0][0], region.coords[0][1]]
51
+
52
+ if prefer_centroid_if_inside_the_cell:
53
+ centroid = region.centroid
54
+
55
+ # TODO replace by rounding maybe some day but ok for now
56
+
57
+ # if labels[int(ceil(centroid[0])), int(ceil(centroid[1]))]==cell_id:
58
+ # return (int(ceil(centroid[0])), int(ceil(centroid[1])))
59
+ if labels[int(round(centroid[0])), int(round(centroid[1]))]==cell_id:
60
+ return (int(round(centroid[0])), int(round(centroid[1])))
61
+
62
+ if __DEBUG__:
63
+ print(cell_id)
64
+
65
+ max_line_length = 0
66
+ # line_length_counter = 0
67
+ point_on_surface = (region.coords[0][0], region.coords[0][1])
68
+
69
+ # how do I get the
70
+ # get the centroid of the region
71
+ # --> ended ok
72
+ cell_encountered = False
73
+ first_encounter_x = None
74
+ line_length_counter = 0
75
+ # there is a bug
76
+
77
+ for iii in range(region.bbox[0], region.bbox[2]):
78
+ # start_coord_x = region.bbox[1]
79
+ # end_coord_x = region.bbox[3]
80
+ # if line_by_line:
81
+ cell_encountered = False
82
+ first_encounter_x = None
83
+ line_length_counter=0 # if I don't do set it to 0 here, I'll get some sort of center of mass effect which is maybe desired or not but if I do that this is very different from the idea above maybe ask for mode 1 or 2
84
+ for jjj in range(region.bbox[1], region.bbox[3]):
85
+
86
+ if labels[iii, jjj] == cell_id:
87
+ # print(cell_encountered, iii, jjj, line_length_counter)
88
+ if not cell_encountered:
89
+ if __DEBUG__:
90
+ print('first encounter', iii, jjj)
91
+ first_encounter_x = jjj
92
+ cell_encountered = True
93
+ line_length_counter += 1
94
+ # print('1',line_length_counter, first_encounter_x, jjj) # --> inded
95
+ else:
96
+ if __DEBUG__:
97
+ if line_length_counter>0:
98
+ print('last encounter', iii, jjj, line_length_counter)
99
+ if line_length_counter >= max_line_length and first_encounter_x is not None:
100
+ max_line_length = line_length_counter
101
+ point_on_surface = (
102
+ iii, int(first_encounter_x + (jjj - first_encounter_x) / 2)) # get the centroid of the line
103
+ if __DEBUG__:
104
+ print('2',region.bbox, max_line_length, point_on_surface, first_encounter_x, cell_encountered, iii, jjj)
105
+
106
+ first_encounter_x = None # reset first encounter
107
+ cell_encountered = False # reset first encounter
108
+ line_length_counter = 0
109
+ # else:
110
+ # line_length_counter = 0
111
+ # cell_encountered = False # reset first encounter
112
+ # first_encounter_x = None # reset first encounter
113
+
114
+ # if line_by_line:
115
+ # need reset also at the end of every loop if it has not found something
116
+ # nb replacing this by >= changes a bit the stuff --> that does make sense especially for cells at the border!!!
117
+ if line_length_counter > max_line_length and first_encounter_x is not None: # en fait c'est mieux comme ça
118
+ max_line_length = line_length_counter
119
+ point_on_surface = (
120
+ iii, int(first_encounter_x + (jjj - first_encounter_x) / 2)) # get the centroid of the line
121
+ if __DEBUG__:
122
+ print('2', region.bbox, max_line_length, point_on_surface, first_encounter_x, cell_encountered, iii, jjj)
123
+
124
+
125
+ # if line_length_counter > max_line_length and first_encounter_x is not None: # en fait c'est mieux comme ça
126
+ # max_line_length = line_length_counter
127
+ # point_on_surface = (
128
+ # iii, int(first_encounter_x + (jjj - first_encounter_x) / 2)) # get the centroid of the line
129
+ #
130
+ # if __DEBUG__:
131
+ # print('2', region.bbox, max_line_length, point_on_surface, first_encounter_x, cell_encountered, iii, jjj)
132
+ if __DEBUG__:
133
+ print(region.bbox)
134
+ return point_on_surface
135
+
136
+ if __name__ == '__main__':
137
+
138
+ start = timer()
139
+
140
+ # get a good test image here just to test the stuff
141
+ cells = \
142
+ Img('/E/Sample_images/sample_images_PA/trash_test_mem/full_wing_multi/early_stages/FocStich_RGB005/handCorrection.png')[
143
+ ..., 0]
144
+ labels = measure.label(cells, connectivity=1, background=255) # FOUR_CONNECTED
145
+ # plt.imshow(labels)
146
+ # plt.show()
147
+
148
+
149
+ # c'est bon ça a l'air de bien marche en fait
150
+ for region in regionprops(labels):
151
+ if region.area > 60000:
152
+ # get its centroid
153
+ pt_on_surface = point_on_surface(region, labels)
154
+ print(pt_on_surface)
155
+
156
+ # compare computed position to the calculated position
157
+ centroid = region.centroid
158
+ x, y = disk(centroid, 30, shape=labels.shape)
159
+ # prefer centroid if inside
160
+ labels[x, y] = 30000
161
+
162
+ # labels[pt_on_surface[0], pt_on_surface[1]] = 6000
163
+ # for xx, yy in zip(x, y):
164
+ x, y = disk(pt_on_surface, 30, shape=labels.shape)
165
+ labels[x, y] = 25000
166
+
167
+ # ax.add_patch(circ)
168
+
169
+ # --> 5 secs --> not that bad I guess because the image is huge...
170
+
171
+ duration = timer() - start
172
+ print(duration)
173
+
174
+ plt.imshow(labels)
175
+ plt.show()
@@ -0,0 +1,82 @@
1
+ # the idea is to read the height map image and create a max proj out of it --> TODO
2
+ from batoolset.img import Img
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+
6
+ def surface_projection_from_height_map(original_image, height_map , integer_mode = False, channel=None):
7
+ # to make it simple first just cast it
8
+ height_map_int = height_map.astype(np.uint8)
9
+
10
+ if channel is not None:
11
+ if len(original_image.shape)>2:
12
+ original_image = original_image[..., channel]
13
+
14
+ surface_projection = np.zeros_like(height_map)
15
+
16
+
17
+ # [os.path.join(path, f) for f in images_to_analyze if
18
+ # os.path.isfile(os.path.join(path, f))[]
19
+ # surface_projection = original_image[height_map,:,:]
20
+
21
+
22
+
23
+
24
+ min = height_map_int.min()
25
+ max = height_map_int.max()
26
+
27
+
28
+ for val in range(min, max):
29
+ surface_projection[height_map_int==val] = original_image[val, height_map_int==val]
30
+
31
+
32
+
33
+
34
+
35
+ # plt.imshow(difference_to_int)
36
+ # plt.show()
37
+ #
38
+ # plt.imshow(one_minus_difference)
39
+ # plt.show()
40
+
41
+ # plt.imshow(surface_projection)
42
+ # plt.show()
43
+
44
+ # Img(surface_projection,dimensions='hw').save('/E/Sample_images/sample_images_denoise_manue/trashme_test_robot/predict/surface_proj_int.tif')
45
+
46
+ if integer_mode:
47
+ return surface_projection
48
+
49
+ weights_for_layer_1 = height_map - height_map_int
50
+ weights_for_layer_0 = 1 - weights_for_layer_1
51
+ # NB try indexed_probs = np.take_along_axis(probs, np.expand_dims(indices, axis=-1), axis=-1)[..., 0]
52
+
53
+ for val in range(min, max-1):
54
+ layer_0 = original_image[val, height_map_int==val] * weights_for_layer_0[height_map_int == val]
55
+ layer_1 = original_image[val+1, height_map_int == val] * weights_for_layer_1[height_map_int == val]
56
+ # do weighted average of the layers --> TODO
57
+ # surface_projection[height_map_int == val] = (layer_0+layer_1)/2
58
+ # for each pixel I need to do the difference to the layer
59
+
60
+
61
+ # surface_projection[height_map_int == val] = (layer_0+layer_1)
62
+ surface_projection[height_map_int == val] = layer_0+layer_1
63
+
64
+ # pas mal mais voir comment gerer le fait que ce soit du float ???
65
+
66
+ # for floats get the rest and compute an average --> that is most likely not easy TODO --> that should be fairly easy todo in fact
67
+
68
+
69
+ # plt.imshow(surface_projection)
70
+ # plt.show()
71
+
72
+
73
+ # Img(surface_projection,dimensions='hw').save('/E/Sample_images/sample_images_denoise_manue/trashme_test_robot/predict/surface_proj_float.tif')
74
+ return surface_projection
75
+
76
+
77
+ if __name__ == '__main__':
78
+ height_map = Img('/E/Sample_images/sample_images_denoise_manue/trashme_test_robot/predict/Image7.tif')
79
+ original_image = Img('/E/Sample_images/sample_images_denoise_manue/trashme_test_robot/Image7.tif')
80
+ surface_proj = surface_projection_from_height_map(original_image, height_map)
81
+ plt.imshow(surface_proj)
82
+ plt.show()
@@ -0,0 +1,208 @@
1
+ import numpy as np
2
+ from batoolset.tools.logger import TA_logger # logging
3
+ from batoolset.ta.utils.rps_tools import pavlidis, compute_distance_between_consecutive_points
4
+
5
+ logger = TA_logger()
6
+
7
+ # this would actually work for 2D and 3D area --> heightmap is optional but it makes no sense to run this with 2D coords and no heightmaps
8
+ # if a heightmap is provided the z coords of every point will be replaced by height map height
9
+ # nb if several triangles are provided, it is assumed they are all traingulation of the same object, hence their area is summed and the sum is returned
10
+ def compute_3D_surfacearea(triangles, heightmap=None, stop_on_2D=False, disable_2D_warning=False):
11
+ """
12
+
13
+ :param triangles: cell/object triangulation as input (as list or ndarray)
14
+ :param heightmap: a heightmap for the cell
15
+ :param stop_on_2D: break if 2D data is provided
16
+ :return: the surface area of the triangulated object
17
+ """
18
+ if not isinstance(triangles, np.ndarray):
19
+ triangles = np.asarray(triangles)
20
+
21
+ if triangles.shape[-1] == 2 and heightmap is None:
22
+ if not disable_2D_warning:
23
+ logger.error(
24
+ 'Error, triangle coords are purely 2D and no heightmap is provided, hence it is not possible to compute 3D area. To continue, the software will assume all heights are the same (= 0) (i.e. the image is really a pure 2D image)') # assuming 2D maybe --> just stack
25
+
26
+ if stop_on_2D:
27
+ return
28
+
29
+ flat_height = np.zeros((*triangles.shape[0:-1], (1)))
30
+ triangles = np.append(triangles, flat_height, -1)
31
+
32
+ # MEGA TODO NB SHOULD I MAKE Z THE FIRST DIMENSION ACTUALLY ????, probably does not change anything
33
+ if heightmap is not None:
34
+ if triangles.shape[-1] == 3:
35
+ b = triangles[:, :, :-1] # get rid of z before adding it again
36
+ else:
37
+ b=triangles
38
+ search_coords = (b[..., 0].ravel(), b[..., 1].ravel()) # 'np.where' like coords
39
+ heights = heightmap[search_coords]
40
+
41
+ # print('heights', heights) # en fait c'est bon car des triangles sont out -> modifie l'aire
42
+
43
+ heights = np.reshape(heights, (*b.shape[0:-1], (1)))
44
+ triangles = np.append(b, heights, -1) # add the height column to the coords
45
+
46
+ # compute a, b and c bond lengths for 3D triangles
47
+ a = ((triangles[:, 0, 0] - triangles[:, 1, 0]) ** 2 + (
48
+ triangles[:, 0, 1] - triangles[:, 1, 1]) ** 2 +
49
+ (triangles[:, 0, 2] - triangles[:, 1, 2]) ** 2) ** 0.5
50
+ b = ((triangles[:, 1, 0] - triangles[:, 2, 0]) ** 2 + (
51
+ triangles[:, 1, 1] - triangles[:, 2, 1]) ** 2 +
52
+ (triangles[:, 1, 2] - triangles[:, 2, 2]) ** 2) ** 0.5
53
+ c = ((triangles[:, 2, 0] - triangles[:, 0, 0]) ** 2 + (
54
+ triangles[:, 2, 1] - triangles[:, 0, 1]) ** 2 +
55
+ (triangles[:, 2, 2] - triangles[:, 0, 2]) ** 2) ** 0.5
56
+
57
+
58
+
59
+ # compute the triangle area using Heron's formula
60
+ # https://en.wikipedia.org/wiki/Triangle
61
+ s = (a + b + c) / 2.0 # semi perimeter (half of the triangle's perimeter)
62
+ areas = (s * (s - a) * (s - b) * (s - c)) ** 0.5
63
+
64
+ return areas.sum()
65
+
66
+
67
+
68
+ # def compute3D_curved_distance(point1, point2, heightmap=None, three_points_mode=False):
69
+ # if heightmap is None:
70
+ # # cannot compute 3D dist
71
+ # print('no valid height map --> cannot compute curved distance')
72
+ # return distance_between_points(point1, point2)
73
+ #
74
+ # pt1_y = int(point1[0])
75
+ # pt1_x = int(point1[1])
76
+ # # pt1_z = point1[2]
77
+ # pt2_y = int(point2[0])
78
+ # pt2_x = int(point2[1])
79
+ # # pt2_z = point1[2]
80
+ # rr, cc = draw.line(pt1_y, pt1_x,
81
+ # pt2_y, pt2_x)
82
+ # # for every point I need get the height of it and compute the real 3D distance --> not hard in fact I think
83
+ #
84
+ # dist3D = 0
85
+ #
86
+ # # print(rr, cc,'-->')
87
+ #
88
+ # if three_points_mode:
89
+ # # dirty hack to speed up things but ok to find the stuff then really recompute the real distance --> should be ok
90
+ # rr = [rr[0], rr[int(len(rr) / 2.)], rr[len(rr) - 1]]
91
+ # cc = [cc[0], cc[int(len(cc) / 2.)], cc[len(cc) - 1]]
92
+ #
93
+ # # print(rr,cc)
94
+ # # compute distance of current point with prev one --> TODO
95
+ # for iii in range(1, len(rr), 1):
96
+ # y1 = rr[iii - 1]
97
+ # x1 = cc[iii - 1]
98
+ # z1 = heightmap[y1, x1]
99
+ # y2 = rr[iii]
100
+ # x2 = cc[iii]
101
+ # z2 = heightmap[y2, x2]
102
+ # # print(y1, x1, 'height', z1)
103
+ # # print(y2, x2, 'height', z2)
104
+ #
105
+ # # print(y1, x1, z1, y2, x2, z2)
106
+ # # print('--> ', distance3D(y1, x1, z1, y2, x2, z2))
107
+ # # NB POINTS NEED BE SORTED --> CHECK THAT THIS IS THE CASE --> EASY MAX DIST BETWEEN CONSECUTIVE 2D POINTS CAN ONLY BE SQRT 2
108
+ #
109
+ # dist3D += distance3D(y1, x1, z1, y2, x2, z2)
110
+ # # print(dist3D)
111
+ # return dist3D
112
+
113
+ # TODO --> do that --> need sorted stuff
114
+ # if points are not sorted then need pavlidis sort them
115
+ # do pretty much the same as in the other
116
+ def perimeter_3D(points, heightmap,points_are_sorted=False,is_closed_contour=False):
117
+ if not points_are_sorted:
118
+ sorted_points =pavlidis(points, closed_contour=is_closed_contour)
119
+ else:
120
+ sorted_points = points
121
+
122
+ if not isinstance(sorted_points, np.ndarray):
123
+ sorted_points = np.asarray(sorted_points)
124
+
125
+ # NOW NEED GET HEIGHT FOR ALL POINTS --> SHOULD BE DOABLE
126
+
127
+ search_coords = (sorted_points[..., 0].ravel(), sorted_points[..., 1].ravel()) # 'np.where' like coords
128
+ # print('search_coords',search_coords)
129
+ heights = heightmap[search_coords]
130
+
131
+
132
+ # print(sorted_points.shape)
133
+ # print(heights.shape)
134
+ # print(heights)
135
+ heights=heights[..., np.newaxis]
136
+ # sorted_points = np.append(sorted_points, heights, -1) # add the heights dimension to the sorted points
137
+ sorted_points = np.append(heights,sorted_points, -1) # add the heights dimension to the sorted points
138
+ # sorted_points = np.hstack((heights, sorted_points)) # add the heights dimension to the sorted points
139
+
140
+ # print('sorted_points',sorted_points)
141
+ consecutive_distance = compute_distance_between_consecutive_points(sorted_points)
142
+
143
+ # print(consecutive_distance)
144
+
145
+
146
+
147
+ perimeter3D = np.sum(consecutive_distance)
148
+
149
+
150
+ # then compare to the pixel by pixel method of it and should be the same result in fact
151
+ # then for every point sum the distance from one point to the next...
152
+
153
+
154
+
155
+ # also find a way to order pixels and get the perimeter right
156
+ # si > sqrt 2 alors need a connection --> put this as a section and try connect to first or last of every list --> maybe not so hard to implement and in the end all the points should be connected but try on a real example !!!
157
+ # TODO
158
+ # do it both for 2D and 3D
159
+ # the ordering of the pixels is key --> really need do that
160
+ # then I'll have a fairly good clone of TA that would be great and fully open source!!!
161
+ # raise NotImplementedError
162
+
163
+
164
+ return perimeter3D
165
+
166
+
167
+ if __name__ == '__main__':
168
+
169
+ if False:
170
+ perimeter_3D()
171
+ import sys
172
+ sys.exit(0)
173
+
174
+ # it all seems to work but now just compute the area in a smarter way
175
+
176
+ # triangles3D= [[[2.37749672, -0.99445809,0], [1.20416474, -0.9998551,1], [2.87830002, -0.99819343,2]],[[2.37749672, -0.99445809,0], [1.20416474, -0.9998551,0], [2.87830002, -0.99819343,0]]]
177
+ # the second triangle is a line --> so area is 0, but 1 is the same but 3D --> in that sense it has an area
178
+ triangles3D = [[[2, 1, 0], [1, 1, 1], [3, 1, 2]], [[2, 1, 0], [1, 1, 0], [3, 1, 0]],
179
+ [[2, 1, 0], [1, 1, 0], [3, 2, 0]], [[2, 1, 0], [1, 1, 0], [2, 2, 0]],
180
+ [[0, 0, 0], [3, 0, 0], [3, 3, 0]]] # why 0 for the second triangle --> do I have a bug ???
181
+
182
+ triangles2D = [[[2, 1], [1, 1], [3, 1]], [[2, 1], [1, 1], [3, 1]],
183
+ [[2, 1], [1, 1], [3, 2]], [[2, 1], [1, 1], [2, 2]],
184
+ [[0, 0], [3, 0], [3, 3]]] # why 0 for the second triangle -
185
+
186
+ heightmap_flat = np.asarray([[0, 0, 0, 0, 0],
187
+ [0, 0, 0, 0, 0],
188
+ [0, 0, 0, 0, 0],
189
+ [0, 0, 0, 0, 0]])
190
+
191
+ # print(heightmap_flat)
192
+
193
+ heightmap_graded = np.asarray([[1, 2, 3, 4, 5],
194
+ [6, 7, 8, 9, 10],
195
+ [11, 12, 14, 15, 16],
196
+ [17, 18, 19, 20, 21]])
197
+
198
+ # print(heightmap_graded)
199
+
200
+ # triangles2D= [[[2.37749672, -0.99445809], [1.20416474, -0.9998551], [2.87830002, -0.99819343]],[[2.37749672, -0.99445809], [1.20416474, -0.9998551], [2.87830002, -0.99819343]]]
201
+ # if heightmap is provided --> get the coords from heightmap otherwise assume what is passed is already 3D
202
+ # check and compare 2D and 3D
203
+ # check also no dupes in the triangles!!!
204
+ print(compute_3D_surfacearea(triangles3D, heightmap=None)) # heightmap_graded # None # heightmap_flat)z
205
+ print(compute_3D_surfacearea(triangles2D, heightmap=None))
206
+ print(compute_3D_surfacearea(triangles2D, heightmap=None,stop_on_2D=True))
207
+
208
+ # if height map is provided then take it