vedo.assembly
Submodule for managing groups of vedo objects
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import numpy as np 4from weakref import ref as weak_ref_to 5 6import vedo.vtkclasses as vtk 7 8import vedo 9from vedo.transformations import LinearTransform 10from vedo.visual import CommonVisual, Actor3DHelper 11 12__docformat__ = "google" 13 14__doc__ = """ 15Submodule for managing groups of vedo objects 16 17 18""" 19 20__all__ = ["Group", "Assembly", "procrustes_alignment"] 21 22 23################################################# 24def procrustes_alignment(sources, rigid=False): 25 """ 26 Return an `Assembly` of aligned source meshes with the `Procrustes` algorithm. 27 The output `Assembly` is normalized in size. 28 29 The `Procrustes` algorithm takes N set of points and aligns them in a least-squares sense 30 to their mutual mean. The algorithm is iterated until convergence, 31 as the mean must be recomputed after each alignment. 32 33 The set of average points generated by the algorithm can be accessed with 34 `algoutput.info['mean']` as a numpy array. 35 36 Arguments: 37 rigid : bool 38 if `True` scaling is disabled. 39 40 Examples: 41 - [align4.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align4.py) 42 43  44 """ 45 46 group = vtk.new("MultiBlockDataGroupFilter") 47 for source in sources: 48 if sources[0].npoints != source.npoints: 49 vedo.logger.error("sources have different nr of points") 50 raise RuntimeError() 51 group.AddInputData(source.dataset) 52 procrustes = vtk.new("ProcrustesAlignmentFilter") 53 procrustes.StartFromCentroidOn() 54 procrustes.SetInputConnection(group.GetOutputPort()) 55 if rigid: 56 procrustes.GetLandmarkTransform().SetModeToRigidBody() 57 procrustes.Update() 58 59 acts = [] 60 for i, s in enumerate(sources): 61 poly = procrustes.GetOutput().GetBlock(i) 62 mesh = vedo.mesh.Mesh(poly) 63 mesh.actor.SetProperty(s.actor.GetProperty()) 64 mesh.properties = s.actor.GetProperty() 65 if hasattr(s, "name"): 66 mesh.name = s.name 67 acts.append(mesh) 68 assem = Assembly(acts) 69 assem.transform = procrustes.GetLandmarkTransform() 70 assem.info["mean"] = vedo.utils.vtk2numpy(procrustes.GetMeanPoints().GetData()) 71 return assem 72 73 74################################################# 75class Group(CommonVisual, vtk.vtkPropAssembly): 76 """Form groups of generic objects (not necessarily meshes).""" 77 78 def __init__(self, objects=()): 79 """Form groups of generic objects (not necessarily meshes).""" 80 81 super().__init__() 82 83 self.actor = self 84 85 self.name = "Group" 86 self.filename = "" 87 self.trail = None 88 self.trail_points = [] 89 self.trail_segment_size = 0 90 self.trail_offset = None 91 self.shadows = [] 92 self.info = {} 93 self.rendered_at = set() 94 self.scalarbar = None 95 96 for a in vedo.utils.flatten(objects): 97 if a: 98 self.AddPart(a.actor) 99 100 self.PickableOff() 101 102 103 def __iadd__(self, obj): 104 """ 105 Add an object to the group 106 """ 107 if not vedo.utils.is_sequence(obj): 108 obj = [obj] 109 for a in obj: 110 if a: 111 self.AddPart(a) 112 return self 113 114 def unpack(self): 115 """Unpack the group into its elements""" 116 elements = [] 117 self.InitPathTraversal() 118 parts = self.GetParts() 119 parts.InitTraversal() 120 for i in range(parts.GetNumberOfItems()): 121 ele = parts.GetItemAsObject(i) 122 elements.append(ele) 123 124 # gr.InitPathTraversal() 125 # for _ in range(gr.GetNumberOfPaths()): 126 # path = gr.GetNextPath() 127 # print([path]) 128 # path.InitTraversal() 129 # for i in range(path.GetNumberOfItems()): 130 # a = path.GetItemAsObject(i).GetViewProp() 131 # print([a]) 132 133 return elements 134 135 def clear(self): 136 """Remove all parts""" 137 for a in self.unpack(): 138 self.RemovePart(a) 139 return self 140 141 def on(self): 142 """Switch on visibility""" 143 self.VisibilityOn() 144 return self 145 146 def off(self): 147 """Switch off visibility""" 148 self.VisibilityOff() 149 return self 150 151 def pickable(self, value=None): 152 """Set/get the pickability property of an object.""" 153 if value is None: 154 return self.GetPickable() 155 self.SetPickable(value) 156 return self 157 158 def draggable(self, value=None): 159 """Set/get the draggability property of an object.""" 160 if value is None: 161 return self.GetDragable() 162 self.SetDragable(value) 163 return self 164 165 def pos(self, x=None, y=None): 166 """Set/Get object 2D position on the screen.""" 167 if x is None: # get functionality 168 return np.array(self.GetPosition()) 169 170 if y is None: # assume x is of the form (x,y) 171 x, y = x 172 self.SetPosition(x, y) 173 return self 174 175 def shift(self, ds): 176 """Add a shift to the current object position on the screen.""" 177 p = np.array(self.GetPosition()) 178 self.SetPosition(p + ds) 179 return self 180 181 def bounds(self): 182 """ 183 Get the object 2D bounds. 184 Returns a list in format [xmin,xmax, ymin,ymax]. 185 """ 186 return self.GetBounds() 187 188 189################################################# 190class Assembly(CommonVisual, Actor3DHelper, vtk.vtkAssembly): 191 """ 192 Group many objects and treat them as a single new object. 193 """ 194 195 def __init__(self, *meshs): 196 """ 197 Group many objects and treat them as a single new object, 198 keeping track of internal transformations. 199 200 Examples: 201 - [gyroscope1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/gyroscope1.py) 202 203  204 """ 205 super().__init__() 206 207 # Init by filename 208 if len(meshs) == 1 and isinstance(meshs[0], str): 209 filename = vedo.file_io.download(meshs[0], verbose=False) 210 data = np.load(filename, allow_pickle=True) 211 meshs = [vedo.file_io._from_numpy(dd) for dd in data] 212 213 if len(meshs) == 1: 214 meshs = meshs[0] 215 else: 216 meshs = vedo.utils.flatten(meshs) 217 218 self.actor = self 219 self.actor.retrieve_object = weak_ref_to(self) 220 221 self.name = "Assembly" 222 self.filename = "" 223 self.rendered_at = set() 224 self.scalarbar = None 225 self.info = {} 226 self.time = 0 227 228 self.transform = LinearTransform() 229 230 self.objects = [m for m in meshs if m] 231 self.actors = [m.actor for m in self.objects] 232 233 scalarbars = [] 234 for a in self.actors: 235 if isinstance(a, vtk.get_class("Prop3D")): # and a.GetNumberOfPoints(): 236 self.AddPart(a) 237 if hasattr(a, "scalarbar") and a.scalarbar is not None: 238 scalarbars.append(a.scalarbar) 239 240 if len(scalarbars) > 1: 241 self.scalarbar = Group(scalarbars) 242 elif len(scalarbars) == 1: 243 self.scalarbar = scalarbars[0] 244 245 self.pipeline = vedo.utils.OperationNode( 246 "Assembly", 247 parents=self.objects, 248 comment=f"#meshes {len(self.objects)}", 249 c="#f08080", 250 ) 251 ########################################## 252 253 def __str__(self): 254 """Print info about Assembly object.""" 255 module = self.__class__.__module__ 256 name = self.__class__.__name__ 257 out = vedo.printc( 258 f"{module}.{name} at ({hex(id(self))})".ljust(75), 259 bold=True, invert=True, return_string=True, 260 ) 261 out += "\x1b[0m" 262 263 if self.name: 264 out += "name".ljust(14) + ": " + self.name 265 if "legend" in self.info.keys() and self.info["legend"]: 266 out+= f", legend='{self.info['legend']}'" 267 out += "\n" 268 269 n = len(self.unpack()) 270 out += "n. of objects".ljust(14) + ": " + str(n) + " " 271 names = [a.name for a in self.unpack() if a.name] 272 if names: 273 out += str(names).replace("'","")[:56] 274 out += "\n" 275 276 pos = self.GetPosition() 277 out += "position".ljust(14) + ": " + str(pos) + "\n" 278 279 bnds = self.GetBounds() 280 bx1, bx2 = vedo.utils.precision(bnds[0], 3), vedo.utils.precision(bnds[1], 3) 281 by1, by2 = vedo.utils.precision(bnds[2], 3), vedo.utils.precision(bnds[3], 3) 282 bz1, bz2 = vedo.utils.precision(bnds[4], 3), vedo.utils.precision(bnds[5], 3) 283 out+= "bounds".ljust(14) + ":" 284 out+= " x=(" + bx1 + ", " + bx2 + ")," 285 out+= " y=(" + by1 + ", " + by2 + ")," 286 out+= " z=(" + bz1 + ", " + bz2 + ")\n" 287 return out.rstrip() + "\x1b[0m" 288 289 def _repr_html_(self): 290 """ 291 HTML representation of the Assembly object for Jupyter Notebooks. 292 293 Returns: 294 HTML text with the image and some properties. 295 """ 296 import io 297 import base64 298 from PIL import Image 299 300 library_name = "vedo.assembly.Assembly" 301 help_url = "https://vedo.embl.es/docs/vedo/assembly.html" 302 303 arr = self.thumbnail(zoom=1.1, elevation=-60) 304 305 im = Image.fromarray(arr) 306 buffered = io.BytesIO() 307 im.save(buffered, format="PNG", quality=100) 308 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 309 url = "data:image/png;base64," + encoded 310 image = f"<img src='{url}'></img>" 311 312 # statisitics 313 bounds = "<br/>".join( 314 [ 315 vedo.utils.precision(min_x, 4) + " ... " + vedo.utils.precision(max_x, 4) 316 for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2]) 317 ] 318 ) 319 320 help_text = "" 321 if self.name: 322 help_text += f"<b> {self.name}:   </b>" 323 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 324 if self.filename: 325 dots = "" 326 if len(self.filename) > 30: 327 dots = "..." 328 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 329 330 allt = [ 331 "<table>", 332 "<tr>", 333 "<td>", 334 image, 335 "</td>", 336 "<td style='text-align: center; vertical-align: center;'><br/>", 337 help_text, 338 "<table>", 339 "<tr><td><b> nr. of objects </b></td><td>" 340 + str(self.GetNumberOfPaths()) 341 + "</td></tr>", 342 "<tr><td><b> position </b></td><td>" + str(self.GetPosition()) + "</td></tr>", 343 "<tr><td><b> diagonal size </b></td><td>" 344 + vedo.utils.precision(self.diagonal_size(), 5) 345 + "</td></tr>", 346 "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>", 347 "</table>", 348 "</table>", 349 ] 350 return "\n".join(allt) 351 352 def __add__(self, obj): 353 """ 354 Add an object to the assembly 355 """ 356 if isinstance(obj, vtk.get_class("Prop3D")): 357 358 self.objects.append(obj) 359 self.actors.append(obj.actor) 360 self.AddPart(obj.actor) 361 362 if hasattr(obj, "scalarbar") and obj.scalarbar is not None: 363 if self.scalarbar is None: 364 self.scalarbar = obj.scalarbar 365 return self 366 367 def unpack_group(scalarbar): 368 if isinstance(scalarbar, Group): 369 return scalarbar.unpack() 370 else: 371 return scalarbar 372 373 if isinstance(self.scalarbar, Group): 374 self.scalarbar += unpack_group(obj.scalarbar) 375 else: 376 self.scalarbar = Group([unpack_group(self.scalarbar), unpack_group(obj.scalarbar)]) 377 self.pipeline = vedo.utils.OperationNode("add mesh", parents=[self, obj], c="#f08080") 378 return self 379 380 def __contains__(self, obj): 381 """Allows to use `in` to check if an object is in the `Assembly`.""" 382 return obj in self.objects 383 384 def __getitem__(self, i): 385 """Return i-th object.""" 386 if isinstance(i, int): 387 return self.objects[i] 388 elif isinstance(i, str): 389 for m in self.objects: 390 if i in m.name: 391 return m 392 return None 393 394 def __len__(self): 395 """Return nr. of objects in the assembly.""" 396 return len(self.objects) 397 398 # TODO #### 399 # def propagate_transform(self): 400 # """Propagate the transformation to all parts.""" 401 # # navigate the assembly and apply the transform to all parts 402 # # and reset position, orientation and scale of the assembly 403 # for i in range(self.GetNumberOfPaths()): 404 # path = self.GetPath(i) 405 # obj = path.GetLastNode().GetViewProp() 406 # obj.SetUserTransform(self.transform.T) 407 # obj.SetPosition(0, 0, 0) 408 # obj.SetOrientation(0, 0, 0) 409 # obj.SetScale(1, 1, 1) 410 # raise NotImplementedError() 411 412 def unpack(self, i=None): 413 """Unpack the list of objects from a `Assembly`. 414 415 If `i` is given, get `i-th` object from a `Assembly`. 416 Input can be a string, in this case returns the first object 417 whose name contains the given string. 418 419 Examples: 420 - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py) 421 """ 422 if i is None: 423 return self.objects 424 elif isinstance(i, int): 425 return self.objects[i] 426 elif isinstance(i, str): 427 for m in self.objects: 428 if i in m.name: 429 return m 430 431 def recursive_unpack(self): 432 """Flatten out an Assembly.""" 433 434 def _genflatten(lst): 435 if not lst: 436 return [] 437 ## 438 if isinstance(lst[0], Assembly): 439 lst = lst[0].unpack() 440 ## 441 for elem in lst: 442 if isinstance(elem, Assembly): 443 apos = elem.GetPosition() 444 asum = np.sum(apos) 445 for x in elem.unpack(): 446 if asum: 447 yield x.clone().shift(apos) 448 else: 449 yield x 450 else: 451 yield elem 452 453 return list(_genflatten([self])) 454 455 def pickable(self, value=True): 456 """Set/get the pickability property of an assembly and its elements""" 457 self.SetPickable(value) 458 # set property to each element 459 for elem in self.recursive_unpack(): 460 elem.pickable(value) 461 return self 462 463 def clone(self): 464 """Make a clone copy of the object. Same as `copy()`.""" 465 newlist = [] 466 for a in self.objects: 467 newlist.append(a.clone()) 468 return Assembly(newlist) 469 470 def copy(self): 471 """Return a copy of the object. Alias of `clone()`.""" 472 return self.clone() 473 474 def write(self, filename="assembly.npy"): 475 """ 476 Write the object to file in `numpy` format. 477 """ 478 objs = [] 479 for ob in self.unpack(): 480 d = vedo.file_io._to_numpy(ob) 481 objs.append(d) 482 np.save(filename, objs) 483 return self
76class Group(CommonVisual, vtk.vtkPropAssembly): 77 """Form groups of generic objects (not necessarily meshes).""" 78 79 def __init__(self, objects=()): 80 """Form groups of generic objects (not necessarily meshes).""" 81 82 super().__init__() 83 84 self.actor = self 85 86 self.name = "Group" 87 self.filename = "" 88 self.trail = None 89 self.trail_points = [] 90 self.trail_segment_size = 0 91 self.trail_offset = None 92 self.shadows = [] 93 self.info = {} 94 self.rendered_at = set() 95 self.scalarbar = None 96 97 for a in vedo.utils.flatten(objects): 98 if a: 99 self.AddPart(a.actor) 100 101 self.PickableOff() 102 103 104 def __iadd__(self, obj): 105 """ 106 Add an object to the group 107 """ 108 if not vedo.utils.is_sequence(obj): 109 obj = [obj] 110 for a in obj: 111 if a: 112 self.AddPart(a) 113 return self 114 115 def unpack(self): 116 """Unpack the group into its elements""" 117 elements = [] 118 self.InitPathTraversal() 119 parts = self.GetParts() 120 parts.InitTraversal() 121 for i in range(parts.GetNumberOfItems()): 122 ele = parts.GetItemAsObject(i) 123 elements.append(ele) 124 125 # gr.InitPathTraversal() 126 # for _ in range(gr.GetNumberOfPaths()): 127 # path = gr.GetNextPath() 128 # print([path]) 129 # path.InitTraversal() 130 # for i in range(path.GetNumberOfItems()): 131 # a = path.GetItemAsObject(i).GetViewProp() 132 # print([a]) 133 134 return elements 135 136 def clear(self): 137 """Remove all parts""" 138 for a in self.unpack(): 139 self.RemovePart(a) 140 return self 141 142 def on(self): 143 """Switch on visibility""" 144 self.VisibilityOn() 145 return self 146 147 def off(self): 148 """Switch off visibility""" 149 self.VisibilityOff() 150 return self 151 152 def pickable(self, value=None): 153 """Set/get the pickability property of an object.""" 154 if value is None: 155 return self.GetPickable() 156 self.SetPickable(value) 157 return self 158 159 def draggable(self, value=None): 160 """Set/get the draggability property of an object.""" 161 if value is None: 162 return self.GetDragable() 163 self.SetDragable(value) 164 return self 165 166 def pos(self, x=None, y=None): 167 """Set/Get object 2D position on the screen.""" 168 if x is None: # get functionality 169 return np.array(self.GetPosition()) 170 171 if y is None: # assume x is of the form (x,y) 172 x, y = x 173 self.SetPosition(x, y) 174 return self 175 176 def shift(self, ds): 177 """Add a shift to the current object position on the screen.""" 178 p = np.array(self.GetPosition()) 179 self.SetPosition(p + ds) 180 return self 181 182 def bounds(self): 183 """ 184 Get the object 2D bounds. 185 Returns a list in format [xmin,xmax, ymin,ymax]. 186 """ 187 return self.GetBounds()
Form groups of generic objects (not necessarily meshes).
79 def __init__(self, objects=()): 80 """Form groups of generic objects (not necessarily meshes).""" 81 82 super().__init__() 83 84 self.actor = self 85 86 self.name = "Group" 87 self.filename = "" 88 self.trail = None 89 self.trail_points = [] 90 self.trail_segment_size = 0 91 self.trail_offset = None 92 self.shadows = [] 93 self.info = {} 94 self.rendered_at = set() 95 self.scalarbar = None 96 97 for a in vedo.utils.flatten(objects): 98 if a: 99 self.AddPart(a.actor) 100 101 self.PickableOff()
Form groups of generic objects (not necessarily meshes).
115 def unpack(self): 116 """Unpack the group into its elements""" 117 elements = [] 118 self.InitPathTraversal() 119 parts = self.GetParts() 120 parts.InitTraversal() 121 for i in range(parts.GetNumberOfItems()): 122 ele = parts.GetItemAsObject(i) 123 elements.append(ele) 124 125 # gr.InitPathTraversal() 126 # for _ in range(gr.GetNumberOfPaths()): 127 # path = gr.GetNextPath() 128 # print([path]) 129 # path.InitTraversal() 130 # for i in range(path.GetNumberOfItems()): 131 # a = path.GetItemAsObject(i).GetViewProp() 132 # print([a]) 133 134 return elements
Unpack the group into its elements
136 def clear(self): 137 """Remove all parts""" 138 for a in self.unpack(): 139 self.RemovePart(a) 140 return self
Remove all parts
152 def pickable(self, value=None): 153 """Set/get the pickability property of an object.""" 154 if value is None: 155 return self.GetPickable() 156 self.SetPickable(value) 157 return self
Set/get the pickability property of an object.
159 def draggable(self, value=None): 160 """Set/get the draggability property of an object.""" 161 if value is None: 162 return self.GetDragable() 163 self.SetDragable(value) 164 return self
Set/get the draggability property of an object.
166 def pos(self, x=None, y=None): 167 """Set/Get object 2D position on the screen.""" 168 if x is None: # get functionality 169 return np.array(self.GetPosition()) 170 171 if y is None: # assume x is of the form (x,y) 172 x, y = x 173 self.SetPosition(x, y) 174 return self
Set/Get object 2D position on the screen.
176 def shift(self, ds): 177 """Add a shift to the current object position on the screen.""" 178 p = np.array(self.GetPosition()) 179 self.SetPosition(p + ds) 180 return self
Add a shift to the current object position on the screen.
182 def bounds(self): 183 """ 184 Get the object 2D bounds. 185 Returns a list in format [xmin,xmax, ymin,ymax]. 186 """ 187 return self.GetBounds()
Get the object 2D bounds. Returns a list in format [xmin,xmax, ymin,ymax].
Inherited Members
191class Assembly(CommonVisual, Actor3DHelper, vtk.vtkAssembly): 192 """ 193 Group many objects and treat them as a single new object. 194 """ 195 196 def __init__(self, *meshs): 197 """ 198 Group many objects and treat them as a single new object, 199 keeping track of internal transformations. 200 201 Examples: 202 - [gyroscope1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/gyroscope1.py) 203 204  205 """ 206 super().__init__() 207 208 # Init by filename 209 if len(meshs) == 1 and isinstance(meshs[0], str): 210 filename = vedo.file_io.download(meshs[0], verbose=False) 211 data = np.load(filename, allow_pickle=True) 212 meshs = [vedo.file_io._from_numpy(dd) for dd in data] 213 214 if len(meshs) == 1: 215 meshs = meshs[0] 216 else: 217 meshs = vedo.utils.flatten(meshs) 218 219 self.actor = self 220 self.actor.retrieve_object = weak_ref_to(self) 221 222 self.name = "Assembly" 223 self.filename = "" 224 self.rendered_at = set() 225 self.scalarbar = None 226 self.info = {} 227 self.time = 0 228 229 self.transform = LinearTransform() 230 231 self.objects = [m for m in meshs if m] 232 self.actors = [m.actor for m in self.objects] 233 234 scalarbars = [] 235 for a in self.actors: 236 if isinstance(a, vtk.get_class("Prop3D")): # and a.GetNumberOfPoints(): 237 self.AddPart(a) 238 if hasattr(a, "scalarbar") and a.scalarbar is not None: 239 scalarbars.append(a.scalarbar) 240 241 if len(scalarbars) > 1: 242 self.scalarbar = Group(scalarbars) 243 elif len(scalarbars) == 1: 244 self.scalarbar = scalarbars[0] 245 246 self.pipeline = vedo.utils.OperationNode( 247 "Assembly", 248 parents=self.objects, 249 comment=f"#meshes {len(self.objects)}", 250 c="#f08080", 251 ) 252 ########################################## 253 254 def __str__(self): 255 """Print info about Assembly object.""" 256 module = self.__class__.__module__ 257 name = self.__class__.__name__ 258 out = vedo.printc( 259 f"{module}.{name} at ({hex(id(self))})".ljust(75), 260 bold=True, invert=True, return_string=True, 261 ) 262 out += "\x1b[0m" 263 264 if self.name: 265 out += "name".ljust(14) + ": " + self.name 266 if "legend" in self.info.keys() and self.info["legend"]: 267 out+= f", legend='{self.info['legend']}'" 268 out += "\n" 269 270 n = len(self.unpack()) 271 out += "n. of objects".ljust(14) + ": " + str(n) + " " 272 names = [a.name for a in self.unpack() if a.name] 273 if names: 274 out += str(names).replace("'","")[:56] 275 out += "\n" 276 277 pos = self.GetPosition() 278 out += "position".ljust(14) + ": " + str(pos) + "\n" 279 280 bnds = self.GetBounds() 281 bx1, bx2 = vedo.utils.precision(bnds[0], 3), vedo.utils.precision(bnds[1], 3) 282 by1, by2 = vedo.utils.precision(bnds[2], 3), vedo.utils.precision(bnds[3], 3) 283 bz1, bz2 = vedo.utils.precision(bnds[4], 3), vedo.utils.precision(bnds[5], 3) 284 out+= "bounds".ljust(14) + ":" 285 out+= " x=(" + bx1 + ", " + bx2 + ")," 286 out+= " y=(" + by1 + ", " + by2 + ")," 287 out+= " z=(" + bz1 + ", " + bz2 + ")\n" 288 return out.rstrip() + "\x1b[0m" 289 290 def _repr_html_(self): 291 """ 292 HTML representation of the Assembly object for Jupyter Notebooks. 293 294 Returns: 295 HTML text with the image and some properties. 296 """ 297 import io 298 import base64 299 from PIL import Image 300 301 library_name = "vedo.assembly.Assembly" 302 help_url = "https://vedo.embl.es/docs/vedo/assembly.html" 303 304 arr = self.thumbnail(zoom=1.1, elevation=-60) 305 306 im = Image.fromarray(arr) 307 buffered = io.BytesIO() 308 im.save(buffered, format="PNG", quality=100) 309 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 310 url = "data:image/png;base64," + encoded 311 image = f"<img src='{url}'></img>" 312 313 # statisitics 314 bounds = "<br/>".join( 315 [ 316 vedo.utils.precision(min_x, 4) + " ... " + vedo.utils.precision(max_x, 4) 317 for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2]) 318 ] 319 ) 320 321 help_text = "" 322 if self.name: 323 help_text += f"<b> {self.name}:   </b>" 324 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 325 if self.filename: 326 dots = "" 327 if len(self.filename) > 30: 328 dots = "..." 329 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 330 331 allt = [ 332 "<table>", 333 "<tr>", 334 "<td>", 335 image, 336 "</td>", 337 "<td style='text-align: center; vertical-align: center;'><br/>", 338 help_text, 339 "<table>", 340 "<tr><td><b> nr. of objects </b></td><td>" 341 + str(self.GetNumberOfPaths()) 342 + "</td></tr>", 343 "<tr><td><b> position </b></td><td>" + str(self.GetPosition()) + "</td></tr>", 344 "<tr><td><b> diagonal size </b></td><td>" 345 + vedo.utils.precision(self.diagonal_size(), 5) 346 + "</td></tr>", 347 "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>", 348 "</table>", 349 "</table>", 350 ] 351 return "\n".join(allt) 352 353 def __add__(self, obj): 354 """ 355 Add an object to the assembly 356 """ 357 if isinstance(obj, vtk.get_class("Prop3D")): 358 359 self.objects.append(obj) 360 self.actors.append(obj.actor) 361 self.AddPart(obj.actor) 362 363 if hasattr(obj, "scalarbar") and obj.scalarbar is not None: 364 if self.scalarbar is None: 365 self.scalarbar = obj.scalarbar 366 return self 367 368 def unpack_group(scalarbar): 369 if isinstance(scalarbar, Group): 370 return scalarbar.unpack() 371 else: 372 return scalarbar 373 374 if isinstance(self.scalarbar, Group): 375 self.scalarbar += unpack_group(obj.scalarbar) 376 else: 377 self.scalarbar = Group([unpack_group(self.scalarbar), unpack_group(obj.scalarbar)]) 378 self.pipeline = vedo.utils.OperationNode("add mesh", parents=[self, obj], c="#f08080") 379 return self 380 381 def __contains__(self, obj): 382 """Allows to use `in` to check if an object is in the `Assembly`.""" 383 return obj in self.objects 384 385 def __getitem__(self, i): 386 """Return i-th object.""" 387 if isinstance(i, int): 388 return self.objects[i] 389 elif isinstance(i, str): 390 for m in self.objects: 391 if i in m.name: 392 return m 393 return None 394 395 def __len__(self): 396 """Return nr. of objects in the assembly.""" 397 return len(self.objects) 398 399 # TODO #### 400 # def propagate_transform(self): 401 # """Propagate the transformation to all parts.""" 402 # # navigate the assembly and apply the transform to all parts 403 # # and reset position, orientation and scale of the assembly 404 # for i in range(self.GetNumberOfPaths()): 405 # path = self.GetPath(i) 406 # obj = path.GetLastNode().GetViewProp() 407 # obj.SetUserTransform(self.transform.T) 408 # obj.SetPosition(0, 0, 0) 409 # obj.SetOrientation(0, 0, 0) 410 # obj.SetScale(1, 1, 1) 411 # raise NotImplementedError() 412 413 def unpack(self, i=None): 414 """Unpack the list of objects from a `Assembly`. 415 416 If `i` is given, get `i-th` object from a `Assembly`. 417 Input can be a string, in this case returns the first object 418 whose name contains the given string. 419 420 Examples: 421 - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py) 422 """ 423 if i is None: 424 return self.objects 425 elif isinstance(i, int): 426 return self.objects[i] 427 elif isinstance(i, str): 428 for m in self.objects: 429 if i in m.name: 430 return m 431 432 def recursive_unpack(self): 433 """Flatten out an Assembly.""" 434 435 def _genflatten(lst): 436 if not lst: 437 return [] 438 ## 439 if isinstance(lst[0], Assembly): 440 lst = lst[0].unpack() 441 ## 442 for elem in lst: 443 if isinstance(elem, Assembly): 444 apos = elem.GetPosition() 445 asum = np.sum(apos) 446 for x in elem.unpack(): 447 if asum: 448 yield x.clone().shift(apos) 449 else: 450 yield x 451 else: 452 yield elem 453 454 return list(_genflatten([self])) 455 456 def pickable(self, value=True): 457 """Set/get the pickability property of an assembly and its elements""" 458 self.SetPickable(value) 459 # set property to each element 460 for elem in self.recursive_unpack(): 461 elem.pickable(value) 462 return self 463 464 def clone(self): 465 """Make a clone copy of the object. Same as `copy()`.""" 466 newlist = [] 467 for a in self.objects: 468 newlist.append(a.clone()) 469 return Assembly(newlist) 470 471 def copy(self): 472 """Return a copy of the object. Alias of `clone()`.""" 473 return self.clone() 474 475 def write(self, filename="assembly.npy"): 476 """ 477 Write the object to file in `numpy` format. 478 """ 479 objs = [] 480 for ob in self.unpack(): 481 d = vedo.file_io._to_numpy(ob) 482 objs.append(d) 483 np.save(filename, objs) 484 return self
Group many objects and treat them as a single new object.
196 def __init__(self, *meshs): 197 """ 198 Group many objects and treat them as a single new object, 199 keeping track of internal transformations. 200 201 Examples: 202 - [gyroscope1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/gyroscope1.py) 203 204  205 """ 206 super().__init__() 207 208 # Init by filename 209 if len(meshs) == 1 and isinstance(meshs[0], str): 210 filename = vedo.file_io.download(meshs[0], verbose=False) 211 data = np.load(filename, allow_pickle=True) 212 meshs = [vedo.file_io._from_numpy(dd) for dd in data] 213 214 if len(meshs) == 1: 215 meshs = meshs[0] 216 else: 217 meshs = vedo.utils.flatten(meshs) 218 219 self.actor = self 220 self.actor.retrieve_object = weak_ref_to(self) 221 222 self.name = "Assembly" 223 self.filename = "" 224 self.rendered_at = set() 225 self.scalarbar = None 226 self.info = {} 227 self.time = 0 228 229 self.transform = LinearTransform() 230 231 self.objects = [m for m in meshs if m] 232 self.actors = [m.actor for m in self.objects] 233 234 scalarbars = [] 235 for a in self.actors: 236 if isinstance(a, vtk.get_class("Prop3D")): # and a.GetNumberOfPoints(): 237 self.AddPart(a) 238 if hasattr(a, "scalarbar") and a.scalarbar is not None: 239 scalarbars.append(a.scalarbar) 240 241 if len(scalarbars) > 1: 242 self.scalarbar = Group(scalarbars) 243 elif len(scalarbars) == 1: 244 self.scalarbar = scalarbars[0] 245 246 self.pipeline = vedo.utils.OperationNode( 247 "Assembly", 248 parents=self.objects, 249 comment=f"#meshes {len(self.objects)}", 250 c="#f08080", 251 ) 252 ##########################################
Group many objects and treat them as a single new object, keeping track of internal transformations.
Examples:
413 def unpack(self, i=None): 414 """Unpack the list of objects from a `Assembly`. 415 416 If `i` is given, get `i-th` object from a `Assembly`. 417 Input can be a string, in this case returns the first object 418 whose name contains the given string. 419 420 Examples: 421 - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py) 422 """ 423 if i is None: 424 return self.objects 425 elif isinstance(i, int): 426 return self.objects[i] 427 elif isinstance(i, str): 428 for m in self.objects: 429 if i in m.name: 430 return m
432 def recursive_unpack(self): 433 """Flatten out an Assembly.""" 434 435 def _genflatten(lst): 436 if not lst: 437 return [] 438 ## 439 if isinstance(lst[0], Assembly): 440 lst = lst[0].unpack() 441 ## 442 for elem in lst: 443 if isinstance(elem, Assembly): 444 apos = elem.GetPosition() 445 asum = np.sum(apos) 446 for x in elem.unpack(): 447 if asum: 448 yield x.clone().shift(apos) 449 else: 450 yield x 451 else: 452 yield elem 453 454 return list(_genflatten([self]))
Flatten out an Assembly.
456 def pickable(self, value=True): 457 """Set/get the pickability property of an assembly and its elements""" 458 self.SetPickable(value) 459 # set property to each element 460 for elem in self.recursive_unpack(): 461 elem.pickable(value) 462 return self
Set/get the pickability property of an assembly and its elements
464 def clone(self): 465 """Make a clone copy of the object. Same as `copy()`.""" 466 newlist = [] 467 for a in self.objects: 468 newlist.append(a.clone()) 469 return Assembly(newlist)
Make a clone copy of the object. Same as copy()
.
471 def copy(self): 472 """Return a copy of the object. Alias of `clone()`.""" 473 return self.clone()
Return a copy of the object. Alias of clone()
.
475 def write(self, filename="assembly.npy"): 476 """ 477 Write the object to file in `numpy` format. 478 """ 479 objs = [] 480 for ob in self.unpack(): 481 d = vedo.file_io._to_numpy(ob) 482 objs.append(d) 483 np.save(filename, objs) 484 return self
Write the object to file in numpy
format.
Inherited Members
25def procrustes_alignment(sources, rigid=False): 26 """ 27 Return an `Assembly` of aligned source meshes with the `Procrustes` algorithm. 28 The output `Assembly` is normalized in size. 29 30 The `Procrustes` algorithm takes N set of points and aligns them in a least-squares sense 31 to their mutual mean. The algorithm is iterated until convergence, 32 as the mean must be recomputed after each alignment. 33 34 The set of average points generated by the algorithm can be accessed with 35 `algoutput.info['mean']` as a numpy array. 36 37 Arguments: 38 rigid : bool 39 if `True` scaling is disabled. 40 41 Examples: 42 - [align4.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align4.py) 43 44  45 """ 46 47 group = vtk.new("MultiBlockDataGroupFilter") 48 for source in sources: 49 if sources[0].npoints != source.npoints: 50 vedo.logger.error("sources have different nr of points") 51 raise RuntimeError() 52 group.AddInputData(source.dataset) 53 procrustes = vtk.new("ProcrustesAlignmentFilter") 54 procrustes.StartFromCentroidOn() 55 procrustes.SetInputConnection(group.GetOutputPort()) 56 if rigid: 57 procrustes.GetLandmarkTransform().SetModeToRigidBody() 58 procrustes.Update() 59 60 acts = [] 61 for i, s in enumerate(sources): 62 poly = procrustes.GetOutput().GetBlock(i) 63 mesh = vedo.mesh.Mesh(poly) 64 mesh.actor.SetProperty(s.actor.GetProperty()) 65 mesh.properties = s.actor.GetProperty() 66 if hasattr(s, "name"): 67 mesh.name = s.name 68 acts.append(mesh) 69 assem = Assembly(acts) 70 assem.transform = procrustes.GetLandmarkTransform() 71 assem.info["mean"] = vedo.utils.vtk2numpy(procrustes.GetMeanPoints().GetData()) 72 return assem
Return an Assembly
of aligned source meshes with the Procrustes
algorithm.
The output Assembly
is normalized in size.
The Procrustes
algorithm takes N set of points and aligns them in a least-squares sense
to their mutual mean. The algorithm is iterated until convergence,
as the mean must be recomputed after each alignment.
The set of average points generated by the algorithm can be accessed with
algoutput.info['mean']
as a numpy array.
Arguments:
- rigid : bool
if
True
scaling is disabled.