vedo.picture
Submodule to work with common format images.
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import numpy as np 4 5try: 6 import vedo.vtkclasses as vtk 7except ImportError: 8 import vtkmodules.all as vtk 9 10import vedo 11from vedo import colors 12from vedo import utils 13 14__docformat__ = "google" 15 16__doc__ = """ 17Submodule to work with common format images. 18 19![](https://vedo.embl.es/images/basic/rotateImage.png) 20""" 21 22__all__ = ["Picture", "Picture2D"] 23 24 25################################################# 26def _get_img(obj, flip=False, translate=()): 27 # get vtkImageData from numpy array or filename 28 29 if isinstance(obj, str): 30 if "https://" in obj: 31 obj = vedo.file_io.download(obj, verbose=False) 32 33 fname = obj.lower() 34 if fname.endswith(".png"): 35 picr = vtk.vtkPNGReader() 36 elif fname.endswith(".jpg") or fname.endswith(".jpeg"): 37 picr = vtk.vtkJPEGReader() 38 elif fname.endswith(".bmp"): 39 picr = vtk.vtkBMPReader() 40 elif fname.endswith(".tif") or fname.endswith(".tiff"): 41 picr = vtk.vtkTIFFReader() 42 picr.SetOrientationType(vedo.settings.tiff_orientation_type) 43 else: 44 colors.printc("Cannot understand picture format", obj, c="r") 45 return 46 picr.SetFileName(obj) 47 picr.Update() 48 img = picr.GetOutput() 49 50 else: 51 obj = np.asarray(obj) 52 53 if obj.ndim == 3: # has shape (nx,ny, ncolor_alpha_chan) 54 iac = vtk.vtkImageAppendComponents() 55 nchan = obj.shape[2] # get number of channels in inputimage (L/LA/RGB/RGBA) 56 for i in range(nchan): 57 if flip: 58 arr = np.flip(np.flip(obj[:, :, i], 0), 0).ravel() 59 else: 60 arr = np.flip(obj[:, :, i], 0).ravel() 61 arr = np.clip(arr, 0, 255) 62 varb = utils.numpy2vtk(arr, dtype=np.uint8, name="RGBA") 63 imgb = vtk.vtkImageData() 64 imgb.SetDimensions(obj.shape[1], obj.shape[0], 1) 65 imgb.GetPointData().AddArray(varb) 66 imgb.GetPointData().SetActiveScalars("RGBA") 67 iac.AddInputData(imgb) 68 iac.Update() 69 img = iac.GetOutput() 70 71 elif obj.ndim == 2: # black and white 72 if flip: 73 arr = np.flip(obj[:, :], 0).ravel() 74 else: 75 arr = obj.ravel() 76 arr = np.clip(arr, 0, 255) 77 varb = utils.numpy2vtk(arr, dtype=np.uint8, name="RGBA") 78 img = vtk.vtkImageData() 79 img.SetDimensions(obj.shape[1], obj.shape[0], 1) 80 81 img.GetPointData().AddArray(varb) 82 img.GetPointData().SetActiveScalars("RGBA") 83 84 if len(translate) > 0: 85 translate_extent = vtk.vtkImageTranslateExtent() 86 translate_extent.SetTranslation(-translate[0], -translate[1], 0) 87 translate_extent.SetInputData(img) 88 translate_extent.Update() 89 img.DeepCopy(translate_extent.GetOutput()) 90 91 return img 92 93 94def _set_justification(img, pos): 95 96 if not isinstance(pos, str): 97 return img, pos 98 99 sx, sy = img.GetDimensions()[:2] 100 translate = () 101 if "top" in pos: 102 if "left" in pos: 103 pos = (0, 1) 104 translate = (0, sy) 105 elif "right" in pos: 106 pos = (1, 1) 107 translate = (sx, sy) 108 elif "mid" in pos or "cent" in pos: 109 pos = (0.5, 1) 110 translate = (sx / 2, sy) 111 elif "bottom" in pos: 112 if "left" in pos: 113 pos = (0, 0) 114 elif "right" in pos: 115 pos = (1, 0) 116 translate = (sx, 0) 117 elif "mid" in pos or "cent" in pos: 118 pos = (0.5, 0) 119 translate = (sx / 2, 0) 120 elif "mid" in pos or "cent" in pos: 121 if "left" in pos: 122 pos = (0, 0.5) 123 translate = (0, sy / 2) 124 elif "right" in pos: 125 pos = (1, 0.5) 126 translate = (sx, sy / 2) 127 else: 128 pos = (0.5, 0.5) 129 translate = (sx / 2, sy / 2) 130 131 if len(translate) > 0: 132 translate = np.array(translate).astype(int) 133 translate_extent = vtk.vtkImageTranslateExtent() 134 translate_extent.SetTranslation(-translate[0], -translate[1], 0) 135 translate_extent.SetInputData(img) 136 translate_extent.Update() 137 img = translate_extent.GetOutput() 138 139 return img, pos 140 141 142################################################# 143class Picture2D(vedo.BaseActor2D): 144 """ 145 Embed an image as a static 2D image in the canvas. 146 """ 147 148 def __init__(self, fig, pos=(0, 0), scale=1, ontop=False, padding=1, justify=""): 149 """ 150 Embed an image as a static 2D image in the canvas. 151 152 Arguments: 153 fig : Picture, matplotlib.Figure, matplotlib.pyplot, vtkImageData 154 the input image 155 pos : (list) 156 2D (x,y) position in range [0,1], 157 [0,0] being the bottom-left corner 158 scale : (float) 159 apply a scaling factor to the image 160 ontop : (bool) 161 keep image on top or not 162 padding : (int) 163 an internal padding space as a fraction of size 164 (matplotlib only) 165 justify : (str) 166 define the anchor point ("top-left", "top-center", ...) 167 """ 168 vedo.BaseActor2D.__init__(self) 169 # print("input type:", fig.__class__) 170 171 self.array = None 172 173 if utils.is_sequence(fig): 174 self.array = fig 175 self._data = _get_img(self.array) 176 177 elif isinstance(fig, Picture): 178 self._data = fig.inputdata() 179 180 elif isinstance(fig, vtk.vtkImageData): 181 assert fig.GetDimensions()[2] == 1, "Cannot create an Picture2D from Volume" 182 self._data = fig 183 184 elif isinstance(fig, str): 185 self._data = _get_img(fig) 186 self.filename = fig 187 188 elif "matplotlib" in str(fig.__class__): 189 if hasattr(fig, "gcf"): 190 fig = fig.gcf() 191 fig.tight_layout(pad=padding) 192 fig.canvas.draw() 193 194 # self.array = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) 195 # self.array = self.array.reshape(fig.canvas.get_width_height()[::-1] + (3,)) 196 width, height = fig.get_size_inches() * fig.get_dpi() 197 self.array = np.frombuffer(fig.canvas.buffer_rgba(), dtype=np.uint8).reshape( 198 (int(height), int(width), 4) 199 ) 200 self.array = self.array[:, :, :3] 201 202 self._data = _get_img(self.array) 203 204 ############# 205 if scale != 1: 206 newsize = np.array(self._data.GetDimensions()[:2]) * scale 207 newsize = newsize.astype(int) 208 rsz = vtk.vtkImageResize() 209 rsz.SetInputData(self._data) 210 rsz.SetResizeMethodToOutputDimensions() 211 rsz.SetOutputDimensions(newsize[0], newsize[1], 1) 212 rsz.Update() 213 self._data = rsz.GetOutput() 214 215 if padding: 216 pass # TODO 217 218 if justify: 219 self._data, pos = _set_justification(self._data, justify) 220 else: 221 self._data, pos = _set_justification(self._data, pos) 222 223 self._mapper = vtk.vtkImageMapper() 224 # self._mapper.RenderToRectangleOn() # NOT good because of aliasing 225 self._mapper.SetInputData(self._data) 226 self._mapper.SetColorWindow(255) 227 self._mapper.SetColorLevel(127.5) 228 self.SetMapper(self._mapper) 229 230 self.GetPositionCoordinate().SetCoordinateSystem(3) 231 self.SetPosition(pos) 232 233 if ontop: 234 self.GetProperty().SetDisplayLocationToForeground() 235 else: 236 self.GetProperty().SetDisplayLocationToBackground() 237 238 @property 239 def shape(self): 240 return np.array(self._data.GetDimensions()[:2]).astype(int) 241 242 243################################################# 244class Picture(vedo.base.Base3DProp, vtk.vtkImageActor): 245 """ 246 Derived class of `vtkImageActor`. Used to represent 2D pictures in a 3D world. 247 """ 248 249 def __init__(self, obj=None, channels=3, flip=False): 250 """ 251 Can be instantiated with a path file name or with a numpy array. 252 253 By default the transparency channel is disabled. 254 To enable it set channels=4. 255 256 Use `Picture.dimensions()` to access the number of pixels in x and y. 257 258 Arguments: 259 channels : (int, list) 260 only select these specific rgba channels (useful to remove alpha) 261 flip : (bool) 262 flip xy axis convention (when input is a numpy array) 263 """ 264 265 vtk.vtkImageActor.__init__(self) 266 vedo.base.Base3DProp.__init__(self) 267 268 if utils.is_sequence(obj) and len(obj) > 0: # passing array 269 img = _get_img(obj, flip) 270 271 elif isinstance(obj, vtk.vtkImageData): 272 img = obj 273 274 elif isinstance(obj, str): 275 img = _get_img(obj) 276 self.filename = obj 277 278 else: 279 img = vtk.vtkImageData() 280 281 # select channels 282 if isinstance(channels, int): 283 channels = list(range(channels)) 284 285 nchans = len(channels) 286 n = img.GetPointData().GetScalars().GetNumberOfComponents() 287 if nchans and n > nchans: 288 pec = vtk.vtkImageExtractComponents() 289 pec.SetInputData(img) 290 if nchans == 4: 291 pec.SetComponents(channels[0], channels[1], channels[2], channels[3]) 292 elif nchans == 3: 293 pec.SetComponents(channels[0], channels[1], channels[2]) 294 elif nchans == 2: 295 pec.SetComponents(channels[0], channels[1]) 296 elif nchans == 1: 297 pec.SetComponents(channels[0]) 298 pec.Update() 299 img = pec.GetOutput() 300 301 self._data = img 302 self.SetInputData(img) 303 304 sx, sy, _ = img.GetDimensions() 305 self.shape = np.array([sx, sy]) 306 307 self._mapper = self.GetMapper() 308 309 self.pipeline = utils.OperationNode("Picture", comment=f"#shape {self.shape}", c="#f28482") 310 ###################################################################### 311 312 def _repr_html_(self): 313 """ 314 HTML representation of the Picture object for Jupyter Notebooks. 315 316 Returns: 317 HTML text with the image and some properties. 318 """ 319 import io 320 import base64 321 from PIL import Image 322 323 library_name = "vedo.picture.Picture" 324 help_url = "https://vedo.embl.es/docs/vedo/picture.html" 325 326 arr = self.thumbnail(zoom=1.1) 327 328 im = Image.fromarray(arr) 329 buffered = io.BytesIO() 330 im.save(buffered, format="PNG", quality=100) 331 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 332 url = "data:image/png;base64," + encoded 333 image = f"<img src='{url}'></img>" 334 335 help_text = "" 336 if self.name: 337 help_text += f"<b> {self.name}:   </b>" 338 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 339 if self.filename: 340 dots = "" 341 if len(self.filename) > 30: 342 dots = "..." 343 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 344 345 pdata = "" 346 if self._data.GetPointData().GetScalars(): 347 if self._data.GetPointData().GetScalars().GetName(): 348 name = self._data.GetPointData().GetScalars().GetName() 349 pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>" 350 351 cdata = "" 352 if self._data.GetCellData().GetScalars(): 353 if self._data.GetCellData().GetScalars().GetName(): 354 name = self._data.GetCellData().GetScalars().GetName() 355 cdata = "<tr><td><b> voxel data array </b></td><td>" + name + "</td></tr>" 356 357 img = self.GetMapper().GetInput() 358 359 allt = [ 360 "<table>", 361 "<tr>", 362 "<td>", 363 image, 364 "</td>", 365 "<td style='text-align: center; vertical-align: center;'><br/>", 366 help_text, 367 "<table>", 368 "<tr><td><b> shape </b></td><td>" + str(img.GetDimensions()[:2]) + "</td></tr>", 369 "<tr><td><b> in memory size </b></td><td>" 370 + str(int(img.GetActualMemorySize())) 371 + " KB</td></tr>", 372 pdata, 373 cdata, 374 "<tr><td><b> intensity range </b></td><td>" + str(img.GetScalarRange()) + "</td></tr>", 375 "<tr><td><b> level / window </b></td><td>" 376 + str(self.level()) 377 + " / " 378 + str(self.window()) 379 + "</td></tr>", 380 "</table>", 381 "</table>", 382 ] 383 return "\n".join(allt) 384 385 def inputdata(self): 386 """Return the underlying ``vtkImagaData`` object.""" 387 return self._data 388 389 def dimensions(self): 390 """Return the picture dimension as number of pixels in x and y""" 391 nx, ny, _ = self._data.GetDimensions() 392 return np.array([nx, ny]) 393 394 def channels(self): 395 """Return the number of channels in picture""" 396 return self._data.GetPointData().GetScalars().GetNumberOfComponents() 397 398 def _update(self, data): 399 """Overwrite the Picture data mesh with a new data.""" 400 self._data = data 401 self._mapper.SetInputData(data) 402 self._mapper.Modified() 403 nx, ny, _ = self._data.GetDimensions() 404 self.shape = np.array([nx, ny]) 405 return self 406 407 def clone(self, transformed=False): 408 """Return an exact copy of the input Picture. 409 If transform is True, it is given the same scaling and position.""" 410 img = vtk.vtkImageData() 411 img.DeepCopy(self._data) 412 pic = Picture(img) 413 if transformed: 414 # assign the same transformation to the copy 415 pic.SetOrigin(self.GetOrigin()) 416 pic.SetScale(self.GetScale()) 417 pic.SetOrientation(self.GetOrientation()) 418 pic.SetPosition(self.GetPosition()) 419 420 pic.pipeline = utils.OperationNode("clone", parents=[self], c="#f7dada", shape="diamond") 421 return pic 422 423 def cmap(self, name, vmin=None, vmax=None): 424 """Colorize a picture with a colormap representing pixel intensity""" 425 n = self._data.GetPointData().GetNumberOfComponents() 426 if n > 1: 427 ecr = vtk.vtkImageExtractComponents() 428 ecr.SetInputData(self._data) 429 ecr.SetComponents(0, 1, 2) 430 ecr.Update() 431 ilum = vtk.vtkImageMagnitude() 432 ilum.SetInputData(self._data) 433 ilum.Update() 434 img = ilum.GetOutput() 435 else: 436 img = self._data 437 438 lut = vtk.vtkLookupTable() 439 _vmin, _vmax = img.GetScalarRange() 440 if vmin is not None: 441 _vmin = vmin 442 if vmax is not None: 443 _vmax = vmax 444 lut.SetRange(_vmin, _vmax) 445 446 ncols = 256 447 lut.SetNumberOfTableValues(ncols) 448 cols = colors.color_map(range(ncols), name, 0, ncols) 449 for i, c in enumerate(cols): 450 lut.SetTableValue(i, *c) 451 lut.Build() 452 453 imap = vtk.vtkImageMapToColors() 454 imap.SetLookupTable(lut) 455 imap.SetInputData(img) 456 imap.Update() 457 self._update(imap.GetOutput()) 458 self.pipeline = utils.OperationNode( 459 f"cmap", comment=f'"{name}"', parents=[self], c="#f28482" 460 ) 461 return self 462 463 def extent(self, ext=None): 464 """ 465 Get or set the physical extent that the picture spans. 466 Format is `ext=[minx, maxx, miny, maxy]`. 467 """ 468 if ext is None: 469 return self._data.GetExtent() 470 471 self._data.SetExtent(ext[0], ext[1], ext[2], ext[3], 0, 0) 472 self._mapper.Modified() 473 return self 474 475 def alpha(self, a=None): 476 """Set/get picture's transparency in the rendering scene.""" 477 if a is not None: 478 self.GetProperty().SetOpacity(a) 479 return self 480 return self.GetProperty().GetOpacity() 481 482 def level(self, value=None): 483 """Get/Set the image color level (brightness) in the rendering scene.""" 484 if value is None: 485 return self.GetProperty().GetColorLevel() 486 self.GetProperty().SetColorLevel(value) 487 return self 488 489 def window(self, value=None): 490 """Get/Set the image color window (contrast) in the rendering scene.""" 491 if value is None: 492 return self.GetProperty().GetColorWindow() 493 self.GetProperty().SetColorWindow(value) 494 return self 495 496 def crop(self, top=None, bottom=None, right=None, left=None, pixels=False): 497 """Crop picture. 498 499 Arguments: 500 top : (float) 501 fraction to crop from the top margin 502 bottom : (float) 503 fraction to crop from the bottom margin 504 left : (float) 505 fraction to crop from the left margin 506 right : (float) 507 fraction to crop from the right margin 508 pixels : (bool) 509 units are pixels 510 """ 511 extractVOI = vtk.vtkExtractVOI() 512 extractVOI.SetInputData(self._data) 513 extractVOI.IncludeBoundaryOn() 514 515 d = self.GetInput().GetDimensions() 516 if pixels: 517 extractVOI.SetVOI(left, d[0] - right - 1, bottom, d[1] - top - 1, 0, 0) 518 else: 519 bx0, bx1, by0, by1 = 0, d[0]-1, 0, d[1]-1 520 if left is not None: bx0 = int((d[0]-1)*left) 521 if right is not None: bx1 = int((d[0]-1)*(1-right)) 522 if bottom is not None: by0 = int((d[1]-1)*bottom) 523 if top is not None: by1 = int((d[1]-1)*(1-top)) 524 extractVOI.SetVOI(bx0, bx1, by0, by1, 0, 0) 525 extractVOI.Update() 526 527 self.shape = extractVOI.GetOutput().GetDimensions()[:2] 528 self._update(extractVOI.GetOutput()) 529 self.pipeline = utils.OperationNode( 530 "crop", comment=f"shape={tuple(self.shape)}", parents=[self], c="#f28482" 531 ) 532 return self 533 534 def pad(self, pixels=10, value=255): 535 """ 536 Add the specified number of pixels at the picture borders. 537 Pixels can be a list formatted as [left,right,bottom,top]. 538 539 Arguments: 540 pixels : (int, list) 541 number of pixels to be added (or a list of length 4) 542 value : (int) 543 intensity value (gray-scale color) of the padding 544 """ 545 x0, x1, y0, y1, _z0, _z1 = self._data.GetExtent() 546 pf = vtk.vtkImageConstantPad() 547 pf.SetInputData(self._data) 548 pf.SetConstant(value) 549 if utils.is_sequence(pixels): 550 pf.SetOutputWholeExtent( 551 x0 - pixels[0], x1 + pixels[1], 552 y0 - pixels[2], y1 + pixels[3], 553 0, 0 554 ) 555 else: 556 pf.SetOutputWholeExtent( 557 x0 - pixels, x1 + pixels, 558 y0 - pixels, y1 + pixels, 559 0, 0 560 ) 561 pf.Update() 562 self._update(pf.GetOutput()) 563 self.pipeline = utils.OperationNode( 564 "pad", comment=f"{pixels} pixels", parents=[self], c="#f28482" 565 ) 566 return self 567 568 def tile(self, nx=4, ny=4, shift=(0, 0)): 569 """ 570 Generate a tiling from the current picture by mirroring and repeating it. 571 572 Arguments: 573 nx : (float) 574 number of repeats along x 575 ny : (float) 576 number of repeats along x 577 shift : (list) 578 shift in x and y in pixels 579 """ 580 x0, x1, y0, y1, z0, z1 = self._data.GetExtent() 581 constant_pad = vtk.vtkImageMirrorPad() 582 constant_pad.SetInputData(self._data) 583 constant_pad.SetOutputWholeExtent( 584 int(x0 + shift[0] + 0.5), 585 int(x1 * nx + shift[0] + 0.5), 586 int(y0 + shift[1] + 0.5), 587 int(y1 * ny + shift[1] + 0.5), 588 z0, 589 z1, 590 ) 591 constant_pad.Update() 592 pic = Picture(constant_pad.GetOutput()) 593 594 pic.pipeline = utils.OperationNode( 595 "tile", comment=f"by {nx}x{ny}", parents=[self], c="#f28482" 596 ) 597 return pic 598 599 def append(self, pictures, axis="z", preserve_extents=False): 600 """ 601 Append the input images to the current one along the specified axis. 602 Except for the append axis, all inputs must have the same extent. 603 All inputs must have the same number of scalar components. 604 The output has the same origin and spacing as the first input. 605 The origin and spacing of all other inputs are ignored. 606 All inputs must have the same scalar type. 607 608 Arguments: 609 axis : (int, str) 610 axis expanded to hold the multiple images 611 preserve_extents : (bool) 612 if True, the extent of the inputs is used to place 613 the image in the output. The whole extent of the output is the union of the input 614 whole extents. Any portion of the output not covered by the inputs is set to zero. 615 The origin and spacing is taken from the first input. 616 617 Example: 618 ```python 619 from vedo import Picture, dataurl 620 pic = Picture(dataurl+'dog.jpg').pad() 621 pic.append([pic,pic], axis='y') 622 pic.append([pic,pic,pic], axis='x') 623 pic.show(axes=1).close() 624 ``` 625 ![](https://vedo.embl.es/images/feats/pict_append.png) 626 """ 627 ima = vtk.vtkImageAppend() 628 ima.SetInputData(self._data) 629 if not utils.is_sequence(pictures): 630 pictures = [pictures] 631 for p in pictures: 632 if isinstance(p, vtk.vtkImageData): 633 ima.AddInputData(p) 634 else: 635 ima.AddInputData(p.inputdata()) 636 ima.SetPreserveExtents(preserve_extents) 637 if axis == "x": 638 axis = 0 639 elif axis == "y": 640 axis = 1 641 ima.SetAppendAxis(axis) 642 ima.Update() 643 self._update(ima.GetOutput()) 644 self.pipeline = utils.OperationNode( 645 "append", comment=f"axis={axis}", parents=[self, *pictures], c="#f28482" 646 ) 647 return self 648 649 def resize(self, newsize): 650 """Resize the image resolution by specifying the number of pixels in width and height. 651 If left to zero, it will be automatically calculated to keep the original aspect ratio. 652 653 newsize is the shape of picture as [npx, npy], or it can be also expressed as a fraction. 654 """ 655 old_dims = np.array(self._data.GetDimensions()) 656 657 if not utils.is_sequence(newsize): 658 newsize = (old_dims * newsize + 0.5).astype(int) 659 660 if not newsize[1]: 661 ar = old_dims[1] / old_dims[0] 662 newsize = [newsize[0], int(newsize[0] * ar + 0.5)] 663 if not newsize[0]: 664 ar = old_dims[0] / old_dims[1] 665 newsize = [int(newsize[1] * ar + 0.5), newsize[1]] 666 newsize = [newsize[0], newsize[1], old_dims[2]] 667 668 rsz = vtk.vtkImageResize() 669 rsz.SetInputData(self._data) 670 rsz.SetResizeMethodToOutputDimensions() 671 rsz.SetOutputDimensions(newsize) 672 rsz.Update() 673 out = rsz.GetOutput() 674 out.SetSpacing(1, 1, 1) 675 self._update(out) 676 self.pipeline = utils.OperationNode( 677 "resize", comment=f"shape={tuple(self.shape)}", parents=[self], c="#f28482" 678 ) 679 return self 680 681 def mirror(self, axis="x"): 682 """Mirror picture along x or y axis. Same as `flip()`.""" 683 ff = vtk.vtkImageFlip() 684 ff.SetInputData(self.inputdata()) 685 if axis.lower() == "x": 686 ff.SetFilteredAxis(0) 687 elif axis.lower() == "y": 688 ff.SetFilteredAxis(1) 689 else: 690 colors.printc("Error in mirror(): mirror must be set to x or y.", c="r") 691 raise RuntimeError() 692 ff.Update() 693 self._update(ff.GetOutput()) 694 self.pipeline = utils.OperationNode(f"mirror {axis}", parents=[self], c="#f28482") 695 return self 696 697 def flip(self, axis="y"): 698 """Mirror picture along x or y axis. Same as `mirror()`.""" 699 return self.mirror(axis=axis) 700 701 def rotate(self, angle, center=(), scale=1, mirroring=False, bc="w", alpha=1): 702 """ 703 Rotate by the specified angle (anticlockwise). 704 705 Arguments: 706 angle : (float) 707 rotation angle in degrees 708 center : (list) 709 center of rotation (x,y) in pixels 710 """ 711 bounds = self.bounds() 712 pc = [0, 0, 0] 713 if center: 714 pc[0] = center[0] 715 pc[1] = center[1] 716 else: 717 pc[0] = (bounds[1] + bounds[0]) / 2.0 718 pc[1] = (bounds[3] + bounds[2]) / 2.0 719 pc[2] = (bounds[5] + bounds[4]) / 2.0 720 721 transform = vtk.vtkTransform() 722 transform.Translate(pc) 723 transform.RotateWXYZ(-angle, 0, 0, 1) 724 transform.Scale(1 / scale, 1 / scale, 1) 725 transform.Translate(-pc[0], -pc[1], -pc[2]) 726 727 reslice = vtk.vtkImageReslice() 728 reslice.SetMirror(mirroring) 729 c = np.array(colors.get_color(bc)) * 255 730 reslice.SetBackgroundColor([c[0], c[1], c[2], alpha * 255]) 731 reslice.SetInputData(self._data) 732 reslice.SetResliceTransform(transform) 733 reslice.SetOutputDimensionality(2) 734 reslice.SetInterpolationModeToCubic() 735 reslice.SetOutputSpacing(self._data.GetSpacing()) 736 reslice.SetOutputOrigin(self._data.GetOrigin()) 737 reslice.SetOutputExtent(self._data.GetExtent()) 738 reslice.Update() 739 self._update(reslice.GetOutput()) 740 741 self.pipeline = utils.OperationNode( 742 "rotate", comment=f"angle={angle}", parents=[self], c="#f28482" 743 ) 744 return self 745 746 def select(self, component): 747 """Select one single component of the rgb image.""" 748 ec = vtk.vtkImageExtractComponents() 749 ec.SetInputData(self._data) 750 ec.SetComponents(component) 751 ec.Update() 752 pic = Picture(ec.GetOutput()) 753 pic.pipeline = utils.OperationNode( 754 "select", comment=f"component {component}", parents=[self], c="#f28482" 755 ) 756 return pic 757 758 def bw(self): 759 """Make it black and white using luminance calibration.""" 760 n = self._data.GetPointData().GetNumberOfComponents() 761 if n == 4: 762 ecr = vtk.vtkImageExtractComponents() 763 ecr.SetInputData(self._data) 764 ecr.SetComponents(0, 1, 2) 765 ecr.Update() 766 img = ecr.GetOutput() 767 else: 768 img = self._data 769 770 ecr = vtk.vtkImageLuminance() 771 ecr.SetInputData(img) 772 ecr.Update() 773 self._update(ecr.GetOutput()) 774 self.pipeline = utils.OperationNode("black&white", parents=[self], c="#f28482") 775 return self 776 777 def smooth(self, sigma=3, radius=None): 778 """ 779 Smooth a Picture with Gaussian kernel. 780 781 Arguments: 782 sigma : (int) 783 number of sigmas in pixel units 784 radius : (float) 785 how far out the gaussian kernel will go before being clamped to zero 786 """ 787 gsf = vtk.vtkImageGaussianSmooth() 788 gsf.SetDimensionality(2) 789 gsf.SetInputData(self._data) 790 if radius is not None: 791 if utils.is_sequence(radius): 792 gsf.SetRadiusFactors(radius[0], radius[1]) 793 else: 794 gsf.SetRadiusFactor(radius) 795 796 if utils.is_sequence(sigma): 797 gsf.SetStandardDeviations(sigma[0], sigma[1]) 798 else: 799 gsf.SetStandardDeviation(sigma) 800 gsf.Update() 801 self._update(gsf.GetOutput()) 802 self.pipeline = utils.OperationNode( 803 "smooth", comment=f"sigma={sigma}", parents=[self], c="#f28482" 804 ) 805 return self 806 807 def median(self): 808 """ 809 Median filter that preserves thin lines and corners. 810 811 It operates on a 5x5 pixel neighborhood. It computes two values initially: 812 the median of the + neighbors and the median of the x neighbors. 813 It then computes the median of these two values plus the center pixel. 814 This result of this second median is the output pixel value. 815 """ 816 medf = vtk.vtkImageHybridMedian2D() 817 medf.SetInputData(self._data) 818 medf.Update() 819 self._update(medf.GetOutput()) 820 self.pipeline = utils.OperationNode("median", parents=[self], c="#f28482") 821 return self 822 823 def enhance(self): 824 """ 825 Enhance a b&w picture using the laplacian, enhancing high-freq edges. 826 827 Example: 828 ```python 829 from vedo import * 830 pic = Picture(vedo.dataurl+'images/dog.jpg').bw() 831 show(pic, pic.clone().enhance(), N=2, mode='image', zoom='tight') 832 ``` 833 ![](https://vedo.embl.es/images/feats/pict_enhance.png) 834 """ 835 img = self._data 836 scalarRange = img.GetPointData().GetScalars().GetRange() 837 838 cast = vtk.vtkImageCast() 839 cast.SetInputData(img) 840 cast.SetOutputScalarTypeToDouble() 841 cast.Update() 842 843 laplacian = vtk.vtkImageLaplacian() 844 laplacian.SetInputData(cast.GetOutput()) 845 laplacian.SetDimensionality(2) 846 laplacian.Update() 847 848 subtr = vtk.vtkImageMathematics() 849 subtr.SetInputData(0, cast.GetOutput()) 850 subtr.SetInputData(1, laplacian.GetOutput()) 851 subtr.SetOperationToSubtract() 852 subtr.Update() 853 854 color_window = scalarRange[1] - scalarRange[0] 855 color_level = color_window / 2 856 original_color = vtk.vtkImageMapToWindowLevelColors() 857 original_color.SetWindow(color_window) 858 original_color.SetLevel(color_level) 859 original_color.SetInputData(subtr.GetOutput()) 860 original_color.Update() 861 self._update(original_color.GetOutput()) 862 863 self.pipeline = utils.OperationNode("enhance", parents=[self], c="#f28482") 864 return self 865 866 def fft(self, mode="magnitude", logscale=12, center=True): 867 """ 868 Fast Fourier transform of a picture. 869 870 Arguments: 871 logscale : (float) 872 if non-zero, take the logarithm of the intensity and scale it by this factor. 873 mode : (str) 874 either [magnitude, real, imaginary, complex], compute the point array data accordingly. 875 center : (bool) 876 shift constant zero-frequency to the center of the image for display. 877 (FFT converts spatial images into frequency space, but puts the zero frequency at the origin) 878 """ 879 ffti = vtk.vtkImageFFT() 880 ffti.SetInputData(self._data) 881 ffti.Update() 882 883 if "mag" in mode: 884 mag = vtk.vtkImageMagnitude() 885 mag.SetInputData(ffti.GetOutput()) 886 mag.Update() 887 out = mag.GetOutput() 888 elif "real" in mode: 889 erf = vtk.vtkImageExtractComponents() 890 erf.SetInputData(ffti.GetOutput()) 891 erf.SetComponents(0) 892 erf.Update() 893 out = erf.GetOutput() 894 elif "imaginary" in mode: 895 eimf = vtk.vtkImageExtractComponents() 896 eimf.SetInputData(ffti.GetOutput()) 897 eimf.SetComponents(1) 898 eimf.Update() 899 out = eimf.GetOutput() 900 elif "complex" in mode: 901 out = ffti.GetOutput() 902 else: 903 colors.printc("Error in fft(): unknown mode", mode) 904 raise RuntimeError() 905 906 if center: 907 center = vtk.vtkImageFourierCenter() 908 center.SetInputData(out) 909 center.Update() 910 out = center.GetOutput() 911 912 if "complex" not in mode: 913 if logscale: 914 ils = vtk.vtkImageLogarithmicScale() 915 ils.SetInputData(out) 916 ils.SetConstant(logscale) 917 ils.Update() 918 out = ils.GetOutput() 919 920 pic = Picture(out) 921 pic.pipeline = utils.OperationNode("FFT", parents=[self], c="#f28482") 922 return pic 923 924 def rfft(self, mode="magnitude"): 925 """Reverse Fast Fourier transform of a picture.""" 926 927 ffti = vtk.vtkImageRFFT() 928 ffti.SetInputData(self._data) 929 ffti.Update() 930 931 if "mag" in mode: 932 mag = vtk.vtkImageMagnitude() 933 mag.SetInputData(ffti.GetOutput()) 934 mag.Update() 935 out = mag.GetOutput() 936 elif "real" in mode: 937 erf = vtk.vtkImageExtractComponents() 938 erf.SetInputData(ffti.GetOutput()) 939 erf.SetComponents(0) 940 erf.Update() 941 out = erf.GetOutput() 942 elif "imaginary" in mode: 943 eimf = vtk.vtkImageExtractComponents() 944 eimf.SetInputData(ffti.GetOutput()) 945 eimf.SetComponents(1) 946 eimf.Update() 947 out = eimf.GetOutput() 948 elif "complex" in mode: 949 out = ffti.GetOutput() 950 else: 951 colors.printc("Error in rfft(): unknown mode", mode) 952 raise RuntimeError() 953 954 pic = Picture(out) 955 pic.pipeline = utils.OperationNode("rFFT", parents=[self], c="#f28482") 956 return pic 957 958 def filterpass(self, lowcutoff=None, highcutoff=None, order=3): 959 """ 960 Low-pass and high-pass filtering become trivial in the frequency domain. 961 A portion of the pixels/voxels are simply masked or attenuated. 962 This function applies a high pass Butterworth filter that attenuates the 963 frequency domain image with the function 964 965 The gradual attenuation of the filter is important. 966 A simple high-pass filter would simply mask a set of pixels in the frequency domain, 967 but the abrupt transition would cause a ringing effect in the spatial domain. 968 969 Arguments: 970 lowcutoff : (list) 971 the cutoff frequencies 972 highcutoff : (list) 973 the cutoff frequencies 974 order : (int) 975 order determines sharpness of the cutoff curve 976 """ 977 # https://lorensen.github.io/VTKExamples/site/Cxx/ImageProcessing/IdealHighPass 978 fft = vtk.vtkImageFFT() 979 fft.SetInputData(self._data) 980 fft.Update() 981 out = fft.GetOutput() 982 983 if highcutoff: 984 blp = vtk.vtkImageButterworthLowPass() 985 blp.SetInputData(out) 986 blp.SetCutOff(highcutoff) 987 blp.SetOrder(order) 988 blp.Update() 989 out = blp.GetOutput() 990 991 if lowcutoff: 992 bhp = vtk.vtkImageButterworthHighPass() 993 bhp.SetInputData(out) 994 bhp.SetCutOff(lowcutoff) 995 bhp.SetOrder(order) 996 bhp.Update() 997 out = bhp.GetOutput() 998 999 rfft = vtk.vtkImageRFFT() 1000 rfft.SetInputData(out) 1001 rfft.Update() 1002 1003 ecomp = vtk.vtkImageExtractComponents() 1004 ecomp.SetInputData(rfft.GetOutput()) 1005 ecomp.SetComponents(0) 1006 ecomp.Update() 1007 1008 caster = vtk.vtkImageCast() 1009 caster.SetOutputScalarTypeToUnsignedChar() 1010 caster.SetInputData(ecomp.GetOutput()) 1011 caster.Update() 1012 self._update(caster.GetOutput()) 1013 self.pipeline = utils.OperationNode("filterpass", parents=[self], c="#f28482") 1014 return self 1015 1016 def blend(self, pic, alpha1=0.5, alpha2=0.5): 1017 """ 1018 Take L, LA, RGB, or RGBA images as input and blends 1019 them according to the alpha values and/or the opacity setting for each input. 1020 """ 1021 blf = vtk.vtkImageBlend() 1022 blf.AddInputData(self._data) 1023 blf.AddInputData(pic.inputdata()) 1024 blf.SetOpacity(0, alpha1) 1025 blf.SetOpacity(1, alpha2) 1026 blf.SetBlendModeToNormal() 1027 blf.Update() 1028 self._update(blf.GetOutput()) 1029 self.pipeline = utils.OperationNode("blend", parents=[self, pic], c="#f28482") 1030 return self 1031 1032 def warp( 1033 self, 1034 source_pts=(), 1035 target_pts=(), 1036 transform=None, 1037 sigma=1, 1038 mirroring=False, 1039 bc="w", 1040 alpha=1, 1041 ): 1042 """ 1043 Warp an image using thin-plate splines. 1044 1045 Arguments: 1046 source_pts : (list) 1047 source points 1048 target_pts : (list) 1049 target points 1050 transform : (vtkTransform) 1051 a vtkTransform object can be supplied 1052 sigma : (float), optional 1053 stiffness of the interpolation 1054 mirroring : (bool) 1055 fill the margins with a reflection of the original image 1056 bc : (color) 1057 fill the margins with a solid color 1058 alpha : (float) 1059 opacity of the filled margins 1060 """ 1061 if transform is None: 1062 # source and target must be filled 1063 transform = vtk.vtkThinPlateSplineTransform() 1064 transform.SetBasisToR2LogR() 1065 1066 parents = [self] 1067 if isinstance(source_pts, vedo.Points): 1068 parents.append(source_pts) 1069 source_pts = source_pts.points() 1070 if isinstance(target_pts, vedo.Points): 1071 parents.append(target_pts) 1072 target_pts = target_pts.points() 1073 1074 ns = len(source_pts) 1075 nt = len(target_pts) 1076 if ns != nt: 1077 colors.printc("Error in picture.warp(): #source != #target points", ns, nt, c="r") 1078 raise RuntimeError() 1079 1080 ptsou = vtk.vtkPoints() 1081 ptsou.SetNumberOfPoints(ns) 1082 1083 pttar = vtk.vtkPoints() 1084 pttar.SetNumberOfPoints(nt) 1085 1086 for i in range(ns): 1087 p = source_pts[i] 1088 ptsou.SetPoint(i, [p[0], p[1], 0]) 1089 p = target_pts[i] 1090 pttar.SetPoint(i, [p[0], p[1], 0]) 1091 1092 transform.SetSigma(sigma) 1093 transform.SetSourceLandmarks(pttar) 1094 transform.SetTargetLandmarks(ptsou) 1095 else: 1096 # ignore source and target 1097 pass 1098 1099 reslice = vtk.vtkImageReslice() 1100 reslice.SetInputData(self._data) 1101 reslice.SetOutputDimensionality(2) 1102 reslice.SetResliceTransform(transform) 1103 reslice.SetInterpolationModeToCubic() 1104 reslice.SetMirror(mirroring) 1105 c = np.array(colors.get_color(bc)) * 255 1106 reslice.SetBackgroundColor([c[0], c[1], c[2], alpha * 255]) 1107 reslice.Update() 1108 self.transform = transform 1109 self._update(reslice.GetOutput()) 1110 self.pipeline = utils.OperationNode("warp", parents=parents, c="#f28482") 1111 return self 1112 1113 def invert(self): 1114 """ 1115 Return an inverted picture (inverted in each color channel). 1116 """ 1117 rgb = self.tonumpy() 1118 data = 255 - np.array(rgb) 1119 self._update(_get_img(data)) 1120 self.pipeline = utils.OperationNode("invert", parents=[self], c="#f28482") 1121 return self 1122 1123 def binarize(self, threshold=None, invert=False): 1124 """ 1125 Return a new Picture where pixel above threshold are set to 255 1126 and pixels below are set to 0. 1127 1128 Arguments: 1129 threshold : (float) 1130 input threshold value 1131 invert : (bool) 1132 invert threshold direction 1133 1134 Example: 1135 ```python 1136 from vedo import Picture, show 1137 pic1 = Picture("https://aws.glamour.es/prod/designs/v1/assets/620x459/547577.jpg") 1138 pic2 = pic1.clone().invert() 1139 pic3 = pic1.clone().binarize() 1140 show(pic1, pic2, pic3, N=3, bg="blue9").close() 1141 ``` 1142 ![](https://vedo.embl.es/images/feats/pict_binarize.png) 1143 """ 1144 rgb = self.tonumpy() 1145 if rgb.ndim == 3: 1146 intensity = np.sum(rgb, axis=2) / 3 1147 else: 1148 intensity = rgb 1149 1150 if threshold is None: 1151 vmin, vmax = np.min(intensity), np.max(intensity) 1152 threshold = (vmax + vmin) / 2 1153 1154 data = np.zeros_like(intensity).astype(np.uint8) 1155 mask = np.where(intensity > threshold) 1156 if invert: 1157 data += 255 1158 data[mask] = 0 1159 else: 1160 data[mask] = 255 1161 1162 self._update(_get_img(data, flip=True)) 1163 1164 self.pipeline = utils.OperationNode( 1165 "binarize", comment=f"threshold={threshold}", parents=[self], c="#f28482" 1166 ) 1167 return self 1168 1169 def threshold(self, value=None, flip=False): 1170 """ 1171 Create a polygonal Mesh from a Picture by filling regions with pixels 1172 luminosity above a specified value. 1173 1174 Arguments: 1175 value : (float) 1176 The default is None, e.i. 1/3 of the scalar range. 1177 flip: (bool) 1178 Flip polygon orientations 1179 1180 Returns: 1181 A polygonal mesh. 1182 """ 1183 mgf = vtk.vtkImageMagnitude() 1184 mgf.SetInputData(self._data) 1185 mgf.Update() 1186 msq = vtk.vtkMarchingSquares() 1187 msq.SetInputData(mgf.GetOutput()) 1188 if value is None: 1189 r0, r1 = self._data.GetScalarRange() 1190 value = r0 + (r1 - r0) / 3 1191 msq.SetValue(0, value) 1192 msq.Update() 1193 if flip: 1194 rs = vtk.vtkReverseSense() 1195 rs.SetInputData(msq.GetOutput()) 1196 rs.ReverseCellsOn() 1197 rs.ReverseNormalsOff() 1198 rs.Update() 1199 output = rs.GetOutput() 1200 else: 1201 output = msq.GetOutput() 1202 ctr = vtk.vtkContourTriangulator() 1203 ctr.SetInputData(output) 1204 ctr.Update() 1205 out = vedo.Mesh(ctr.GetOutput(), c="k").bc("t").lighting("off") 1206 1207 out.pipeline = utils.OperationNode( 1208 "threshold", comment=f"{value: .2f}", parents=[self], c="#f28482:#e9c46a" 1209 ) 1210 return out 1211 1212 def tomesh(self): 1213 """ 1214 Convert an image to polygonal data (quads), 1215 with each polygon vertex assigned a RGBA value. 1216 """ 1217 dims = self._data.GetDimensions() 1218 gr = vedo.shapes.Grid(s=dims[:2], res=(dims[0] - 1, dims[1] - 1)) 1219 gr.pos(int(dims[0] / 2), int(dims[1] / 2)).pickable(True).wireframe(False).lw(0) 1220 self._data.GetPointData().GetScalars().SetName("RGBA") 1221 gr.inputdata().GetPointData().AddArray(self._data.GetPointData().GetScalars()) 1222 gr.inputdata().GetPointData().SetActiveScalars("RGBA") 1223 gr.mapper().SetArrayName("RGBA") 1224 gr.mapper().SetScalarModeToUsePointData() 1225 gr.mapper().ScalarVisibilityOn() 1226 gr.name = self.name 1227 gr.filename = self.filename 1228 gr.pipeline = utils.OperationNode("tomesh", parents=[self], c="#f28482:#e9c46a") 1229 return gr 1230 1231 def tonumpy(self): 1232 """ 1233 Get read-write access to pixels of a Picture object as a numpy array. 1234 Note that the shape is (nrofchannels, nx, ny). 1235 1236 When you set values in the output image, you don't want numpy to reallocate the array 1237 but instead set values in the existing array, so use the [:] operator. 1238 Example: arr[:] = arr - 15 1239 1240 If the array is modified call: 1241 ``picture.modified()`` 1242 when all your modifications are completed. 1243 """ 1244 nx, ny, _ = self._data.GetDimensions() 1245 nchan = self._data.GetPointData().GetScalars().GetNumberOfComponents() 1246 narray = utils.vtk2numpy(self._data.GetPointData().GetScalars()).reshape(ny, nx, nchan) 1247 narray = np.flip(narray, axis=0).astype(np.uint8) 1248 return narray.squeeze() 1249 1250 def rectangle(self, xspan, yspan, c="green5", alpha=1): 1251 """Draw a rectangle box on top of current image. Units are pixels. 1252 1253 Example: 1254 ```python 1255 import vedo 1256 pic = vedo.Picture(vedo.dataurl+"images/dog.jpg") 1257 pic.rectangle([100,300], [100,200], c='green4', alpha=0.7) 1258 pic.line([100,100],[400,500], lw=2, alpha=1) 1259 pic.triangle([250,300], [100,300], [200,400], c='blue5') 1260 vedo.show(pic, axes=1).close() 1261 ``` 1262 ![](https://vedo.embl.es/images/feats/pict_drawon.png) 1263 """ 1264 x1, x2 = xspan 1265 y1, y2 = yspan 1266 1267 r, g, b = vedo.colors.get_color(c) 1268 c = np.array([r, g, b]) * 255 1269 c = c.astype(np.uint8) 1270 1271 alpha = min(alpha, 1) 1272 if alpha <= 0: 1273 return self 1274 alpha2 = alpha 1275 alpha1 = 1 - alpha 1276 1277 nx, ny = self.dimensions() 1278 if x2 > nx: 1279 x2 = nx - 1 1280 if y2 > ny: 1281 y2 = ny - 1 1282 1283 nchan = self.channels() 1284 narrayA = self.tonumpy() 1285 1286 canvas_source = vtk.vtkImageCanvasSource2D() 1287 canvas_source.SetExtent(0, nx - 1, 0, ny - 1, 0, 0) 1288 canvas_source.SetScalarTypeToUnsignedChar() 1289 canvas_source.SetNumberOfScalarComponents(nchan) 1290 canvas_source.SetDrawColor(255, 255, 255) 1291 canvas_source.FillBox(x1, x2, y1, y2) 1292 canvas_source.Update() 1293 image_data = canvas_source.GetOutput() 1294 1295 vscals = image_data.GetPointData().GetScalars() 1296 narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny, nx, nchan) 1297 narrayB = np.flip(narrayB, axis=0) 1298 narrayC = np.where(narrayB < 255, narrayA, alpha1 * narrayA + alpha2 * c) 1299 self._update(_get_img(narrayC)) 1300 self.pipeline = utils.OperationNode("rectangle", parents=[self], c="#f28482") 1301 return self 1302 1303 def line(self, p1, p2, lw=2, c="k2", alpha=1): 1304 """Draw a line on top of current image. Units are pixels.""" 1305 x1, x2 = p1 1306 y1, y2 = p2 1307 1308 r, g, b = vedo.colors.get_color(c) 1309 c = np.array([r, g, b]) * 255 1310 c = c.astype(np.uint8) 1311 1312 alpha = min(alpha, 1) 1313 if alpha <= 0: 1314 return self 1315 alpha2 = alpha 1316 alpha1 = 1 - alpha 1317 1318 nx, ny = self.dimensions() 1319 if x2 > nx: 1320 x2 = nx - 1 1321 if y2 > ny: 1322 y2 = ny - 1 1323 1324 nchan = self.channels() 1325 narrayA = self.tonumpy() 1326 1327 canvas_source = vtk.vtkImageCanvasSource2D() 1328 canvas_source.SetExtent(0, nx - 1, 0, ny - 1, 0, 0) 1329 canvas_source.SetScalarTypeToUnsignedChar() 1330 canvas_source.SetNumberOfScalarComponents(nchan) 1331 canvas_source.SetDrawColor(255, 255, 255) 1332 canvas_source.FillTube(x1, x2, y1, y2, lw) 1333 canvas_source.Update() 1334 image_data = canvas_source.GetOutput() 1335 1336 vscals = image_data.GetPointData().GetScalars() 1337 narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny, nx, nchan) 1338 narrayB = np.flip(narrayB, axis=0) 1339 narrayC = np.where(narrayB < 255, narrayA, alpha1 * narrayA + alpha2 * c) 1340 self._update(_get_img(narrayC)) 1341 self.pipeline = utils.OperationNode("line", parents=[self], c="#f28482") 1342 return self 1343 1344 def triangle(self, p1, p2, p3, c="red3", alpha=1): 1345 """Draw a triangle on top of current image. Units are pixels.""" 1346 x1, y1 = p1 1347 x2, y2 = p2 1348 x3, y3 = p3 1349 1350 r, g, b = vedo.colors.get_color(c) 1351 c = np.array([r, g, b]) * 255 1352 c = c.astype(np.uint8) 1353 1354 alpha = min(alpha, 1) 1355 if alpha <= 0: 1356 return self 1357 alpha2 = alpha 1358 alpha1 = 1 - alpha 1359 1360 nx, ny = self.dimensions() 1361 x1 = min(x1, nx) 1362 x2 = min(x2, nx) 1363 x3 = min(x3, nx) 1364 1365 y1 = min(y1, ny) 1366 y2 = min(y2, ny) 1367 y3 = min(y3, ny) 1368 1369 nchan = self.channels() 1370 narrayA = self.tonumpy() 1371 1372 canvas_source = vtk.vtkImageCanvasSource2D() 1373 canvas_source.SetExtent(0, nx - 1, 0, ny - 1, 0, 0) 1374 canvas_source.SetScalarTypeToUnsignedChar() 1375 canvas_source.SetNumberOfScalarComponents(nchan) 1376 canvas_source.SetDrawColor(255, 255, 255) 1377 canvas_source.FillTriangle(x1, y1, x2, y2, x3, y3) 1378 canvas_source.Update() 1379 image_data = canvas_source.GetOutput() 1380 1381 vscals = image_data.GetPointData().GetScalars() 1382 narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny, nx, nchan) 1383 narrayB = np.flip(narrayB, axis=0) 1384 narrayC = np.where(narrayB < 255, narrayA, alpha1 * narrayA + alpha2 * c) 1385 self._update(_get_img(narrayC)) 1386 self.pipeline = utils.OperationNode("triangle", parents=[self], c="#f28482") 1387 return self 1388 1389 def add_text( 1390 self, 1391 txt, 1392 # pos=(0, 0), # TODO 1393 width=400, 1394 height=200, 1395 alpha=1, 1396 c="black", 1397 bg=None, 1398 alpha_bg=1, 1399 font="Theemim", 1400 dpi=200, 1401 justify="bottom-left", 1402 ): 1403 """Add text to an image.""" 1404 1405 tp = vtk.vtkTextProperty() 1406 tp.BoldOff() 1407 tp.FrameOff() 1408 tp.SetColor(colors.get_color(c)) 1409 tp.SetJustificationToLeft() 1410 if "top" in justify: 1411 tp.SetVerticalJustificationToTop() 1412 if "bottom" in justify: 1413 tp.SetVerticalJustificationToBottom() 1414 if "cent" in justify: 1415 tp.SetVerticalJustificationToCentered() 1416 tp.SetJustificationToCentered() 1417 if "left" in justify: 1418 tp.SetJustificationToLeft() 1419 if "right" in justify: 1420 tp.SetJustificationToRight() 1421 1422 if font.lower() == "courier": tp.SetFontFamilyToCourier() 1423 elif font.lower() == "times": tp.SetFontFamilyToTimes() 1424 elif font.lower() == "arial": tp.SetFontFamilyToArial() 1425 else: 1426 tp.SetFontFamily(vtk.VTK_FONT_FILE) 1427 tp.SetFontFile(utils.get_font_path(font)) 1428 1429 if bg: 1430 bgcol = colors.get_color(bg) 1431 tp.SetBackgroundColor(bgcol) 1432 tp.SetBackgroundOpacity(alpha_bg) 1433 tp.SetFrameColor(bgcol) 1434 tp.FrameOn() 1435 1436 tr = vtk.vtkTextRenderer() 1437 # GetConstrainedFontSize (const vtkUnicodeString &str, 1438 # vtkTextProperty(*tprop, int targetWidth, int targetHeight, int dpi) 1439 fs = tr.GetConstrainedFontSize(txt, tp, width, height, dpi) 1440 tp.SetFontSize(fs) 1441 1442 img = vtk.vtkImageData() 1443 # img.SetOrigin(*pos,1) 1444 tr.RenderString(tp, txt, img, [width, height], dpi) 1445 # RenderString (vtkTextProperty *tprop, const vtkStdString &str, 1446 # vtkImageData *data, int textDims[2], int dpi, int backend=Default) 1447 1448 blf = vtk.vtkImageBlend() 1449 blf.AddInputData(self._data) 1450 blf.AddInputData(img) 1451 blf.SetOpacity(0, 1) 1452 blf.SetOpacity(1, alpha) 1453 blf.SetBlendModeToNormal() 1454 blf.Update() 1455 1456 self._update(blf.GetOutput()) 1457 self.pipeline = utils.OperationNode( 1458 "add_text", comment=f"{txt}", parents=[self], c="#f28482" 1459 ) 1460 return self 1461 1462 def modified(self): 1463 """Use in conjunction with ``tonumpy()`` to update any modifications to the picture array""" 1464 self._data.GetPointData().GetScalars().Modified() 1465 return self 1466 1467 def write(self, filename): 1468 """Write picture to file as png or jpg.""" 1469 vedo.file_io.write(self._data, filename) 1470 self.pipeline = utils.OperationNode( 1471 "write", comment=filename[:15], parents=[self], c="#8a817c", shape="cylinder" 1472 ) 1473 return self
245class Picture(vedo.base.Base3DProp, vtk.vtkImageActor): 246 """ 247 Derived class of `vtkImageActor`. Used to represent 2D pictures in a 3D world. 248 """ 249 250 def __init__(self, obj=None, channels=3, flip=False): 251 """ 252 Can be instantiated with a path file name or with a numpy array. 253 254 By default the transparency channel is disabled. 255 To enable it set channels=4. 256 257 Use `Picture.dimensions()` to access the number of pixels in x and y. 258 259 Arguments: 260 channels : (int, list) 261 only select these specific rgba channels (useful to remove alpha) 262 flip : (bool) 263 flip xy axis convention (when input is a numpy array) 264 """ 265 266 vtk.vtkImageActor.__init__(self) 267 vedo.base.Base3DProp.__init__(self) 268 269 if utils.is_sequence(obj) and len(obj) > 0: # passing array 270 img = _get_img(obj, flip) 271 272 elif isinstance(obj, vtk.vtkImageData): 273 img = obj 274 275 elif isinstance(obj, str): 276 img = _get_img(obj) 277 self.filename = obj 278 279 else: 280 img = vtk.vtkImageData() 281 282 # select channels 283 if isinstance(channels, int): 284 channels = list(range(channels)) 285 286 nchans = len(channels) 287 n = img.GetPointData().GetScalars().GetNumberOfComponents() 288 if nchans and n > nchans: 289 pec = vtk.vtkImageExtractComponents() 290 pec.SetInputData(img) 291 if nchans == 4: 292 pec.SetComponents(channels[0], channels[1], channels[2], channels[3]) 293 elif nchans == 3: 294 pec.SetComponents(channels[0], channels[1], channels[2]) 295 elif nchans == 2: 296 pec.SetComponents(channels[0], channels[1]) 297 elif nchans == 1: 298 pec.SetComponents(channels[0]) 299 pec.Update() 300 img = pec.GetOutput() 301 302 self._data = img 303 self.SetInputData(img) 304 305 sx, sy, _ = img.GetDimensions() 306 self.shape = np.array([sx, sy]) 307 308 self._mapper = self.GetMapper() 309 310 self.pipeline = utils.OperationNode("Picture", comment=f"#shape {self.shape}", c="#f28482") 311 ###################################################################### 312 313 def _repr_html_(self): 314 """ 315 HTML representation of the Picture object for Jupyter Notebooks. 316 317 Returns: 318 HTML text with the image and some properties. 319 """ 320 import io 321 import base64 322 from PIL import Image 323 324 library_name = "vedo.picture.Picture" 325 help_url = "https://vedo.embl.es/docs/vedo/picture.html" 326 327 arr = self.thumbnail(zoom=1.1) 328 329 im = Image.fromarray(arr) 330 buffered = io.BytesIO() 331 im.save(buffered, format="PNG", quality=100) 332 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 333 url = "data:image/png;base64," + encoded 334 image = f"<img src='{url}'></img>" 335 336 help_text = "" 337 if self.name: 338 help_text += f"<b> {self.name}:   </b>" 339 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 340 if self.filename: 341 dots = "" 342 if len(self.filename) > 30: 343 dots = "..." 344 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 345 346 pdata = "" 347 if self._data.GetPointData().GetScalars(): 348 if self._data.GetPointData().GetScalars().GetName(): 349 name = self._data.GetPointData().GetScalars().GetName() 350 pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>" 351 352 cdata = "" 353 if self._data.GetCellData().GetScalars(): 354 if self._data.GetCellData().GetScalars().GetName(): 355 name = self._data.GetCellData().GetScalars().GetName() 356 cdata = "<tr><td><b> voxel data array </b></td><td>" + name + "</td></tr>" 357 358 img = self.GetMapper().GetInput() 359 360 allt = [ 361 "<table>", 362 "<tr>", 363 "<td>", 364 image, 365 "</td>", 366 "<td style='text-align: center; vertical-align: center;'><br/>", 367 help_text, 368 "<table>", 369 "<tr><td><b> shape </b></td><td>" + str(img.GetDimensions()[:2]) + "</td></tr>", 370 "<tr><td><b> in memory size </b></td><td>" 371 + str(int(img.GetActualMemorySize())) 372 + " KB</td></tr>", 373 pdata, 374 cdata, 375 "<tr><td><b> intensity range </b></td><td>" + str(img.GetScalarRange()) + "</td></tr>", 376 "<tr><td><b> level / window </b></td><td>" 377 + str(self.level()) 378 + " / " 379 + str(self.window()) 380 + "</td></tr>", 381 "</table>", 382 "</table>", 383 ] 384 return "\n".join(allt) 385 386 def inputdata(self): 387 """Return the underlying ``vtkImagaData`` object.""" 388 return self._data 389 390 def dimensions(self): 391 """Return the picture dimension as number of pixels in x and y""" 392 nx, ny, _ = self._data.GetDimensions() 393 return np.array([nx, ny]) 394 395 def channels(self): 396 """Return the number of channels in picture""" 397 return self._data.GetPointData().GetScalars().GetNumberOfComponents() 398 399 def _update(self, data): 400 """Overwrite the Picture data mesh with a new data.""" 401 self._data = data 402 self._mapper.SetInputData(data) 403 self._mapper.Modified() 404 nx, ny, _ = self._data.GetDimensions() 405 self.shape = np.array([nx, ny]) 406 return self 407 408 def clone(self, transformed=False): 409 """Return an exact copy of the input Picture. 410 If transform is True, it is given the same scaling and position.""" 411 img = vtk.vtkImageData() 412 img.DeepCopy(self._data) 413 pic = Picture(img) 414 if transformed: 415 # assign the same transformation to the copy 416 pic.SetOrigin(self.GetOrigin()) 417 pic.SetScale(self.GetScale()) 418 pic.SetOrientation(self.GetOrientation()) 419 pic.SetPosition(self.GetPosition()) 420 421 pic.pipeline = utils.OperationNode("clone", parents=[self], c="#f7dada", shape="diamond") 422 return pic 423 424 def cmap(self, name, vmin=None, vmax=None): 425 """Colorize a picture with a colormap representing pixel intensity""" 426 n = self._data.GetPointData().GetNumberOfComponents() 427 if n > 1: 428 ecr = vtk.vtkImageExtractComponents() 429 ecr.SetInputData(self._data) 430 ecr.SetComponents(0, 1, 2) 431 ecr.Update() 432 ilum = vtk.vtkImageMagnitude() 433 ilum.SetInputData(self._data) 434 ilum.Update() 435 img = ilum.GetOutput() 436 else: 437 img = self._data 438 439 lut = vtk.vtkLookupTable() 440 _vmin, _vmax = img.GetScalarRange() 441 if vmin is not None: 442 _vmin = vmin 443 if vmax is not None: 444 _vmax = vmax 445 lut.SetRange(_vmin, _vmax) 446 447 ncols = 256 448 lut.SetNumberOfTableValues(ncols) 449 cols = colors.color_map(range(ncols), name, 0, ncols) 450 for i, c in enumerate(cols): 451 lut.SetTableValue(i, *c) 452 lut.Build() 453 454 imap = vtk.vtkImageMapToColors() 455 imap.SetLookupTable(lut) 456 imap.SetInputData(img) 457 imap.Update() 458 self._update(imap.GetOutput()) 459 self.pipeline = utils.OperationNode( 460 f"cmap", comment=f'"{name}"', parents=[self], c="#f28482" 461 ) 462 return self 463 464 def extent(self, ext=None): 465 """ 466 Get or set the physical extent that the picture spans. 467 Format is `ext=[minx, maxx, miny, maxy]`. 468 """ 469 if ext is None: 470 return self._data.GetExtent() 471 472 self._data.SetExtent(ext[0], ext[1], ext[2], ext[3], 0, 0) 473 self._mapper.Modified() 474 return self 475 476 def alpha(self, a=None): 477 """Set/get picture's transparency in the rendering scene.""" 478 if a is not None: 479 self.GetProperty().SetOpacity(a) 480 return self 481 return self.GetProperty().GetOpacity() 482 483 def level(self, value=None): 484 """Get/Set the image color level (brightness) in the rendering scene.""" 485 if value is None: 486 return self.GetProperty().GetColorLevel() 487 self.GetProperty().SetColorLevel(value) 488 return self 489 490 def window(self, value=None): 491 """Get/Set the image color window (contrast) in the rendering scene.""" 492 if value is None: 493 return self.GetProperty().GetColorWindow() 494 self.GetProperty().SetColorWindow(value) 495 return self 496 497 def crop(self, top=None, bottom=None, right=None, left=None, pixels=False): 498 """Crop picture. 499 500 Arguments: 501 top : (float) 502 fraction to crop from the top margin 503 bottom : (float) 504 fraction to crop from the bottom margin 505 left : (float) 506 fraction to crop from the left margin 507 right : (float) 508 fraction to crop from the right margin 509 pixels : (bool) 510 units are pixels 511 """ 512 extractVOI = vtk.vtkExtractVOI() 513 extractVOI.SetInputData(self._data) 514 extractVOI.IncludeBoundaryOn() 515 516 d = self.GetInput().GetDimensions() 517 if pixels: 518 extractVOI.SetVOI(left, d[0] - right - 1, bottom, d[1] - top - 1, 0, 0) 519 else: 520 bx0, bx1, by0, by1 = 0, d[0]-1, 0, d[1]-1 521 if left is not None: bx0 = int((d[0]-1)*left) 522 if right is not None: bx1 = int((d[0]-1)*(1-right)) 523 if bottom is not None: by0 = int((d[1]-1)*bottom) 524 if top is not None: by1 = int((d[1]-1)*(1-top)) 525 extractVOI.SetVOI(bx0, bx1, by0, by1, 0, 0) 526 extractVOI.Update() 527 528 self.shape = extractVOI.GetOutput().GetDimensions()[:2] 529 self._update(extractVOI.GetOutput()) 530 self.pipeline = utils.OperationNode( 531 "crop", comment=f"shape={tuple(self.shape)}", parents=[self], c="#f28482" 532 ) 533 return self 534 535 def pad(self, pixels=10, value=255): 536 """ 537 Add the specified number of pixels at the picture borders. 538 Pixels can be a list formatted as [left,right,bottom,top]. 539 540 Arguments: 541 pixels : (int, list) 542 number of pixels to be added (or a list of length 4) 543 value : (int) 544 intensity value (gray-scale color) of the padding 545 """ 546 x0, x1, y0, y1, _z0, _z1 = self._data.GetExtent() 547 pf = vtk.vtkImageConstantPad() 548 pf.SetInputData(self._data) 549 pf.SetConstant(value) 550 if utils.is_sequence(pixels): 551 pf.SetOutputWholeExtent( 552 x0 - pixels[0], x1 + pixels[1], 553 y0 - pixels[2], y1 + pixels[3], 554 0, 0 555 ) 556 else: 557 pf.SetOutputWholeExtent( 558 x0 - pixels, x1 + pixels, 559 y0 - pixels, y1 + pixels, 560 0, 0 561 ) 562 pf.Update() 563 self._update(pf.GetOutput()) 564 self.pipeline = utils.OperationNode( 565 "pad", comment=f"{pixels} pixels", parents=[self], c="#f28482" 566 ) 567 return self 568 569 def tile(self, nx=4, ny=4, shift=(0, 0)): 570 """ 571 Generate a tiling from the current picture by mirroring and repeating it. 572 573 Arguments: 574 nx : (float) 575 number of repeats along x 576 ny : (float) 577 number of repeats along x 578 shift : (list) 579 shift in x and y in pixels 580 """ 581 x0, x1, y0, y1, z0, z1 = self._data.GetExtent() 582 constant_pad = vtk.vtkImageMirrorPad() 583 constant_pad.SetInputData(self._data) 584 constant_pad.SetOutputWholeExtent( 585 int(x0 + shift[0] + 0.5), 586 int(x1 * nx + shift[0] + 0.5), 587 int(y0 + shift[1] + 0.5), 588 int(y1 * ny + shift[1] + 0.5), 589 z0, 590 z1, 591 ) 592 constant_pad.Update() 593 pic = Picture(constant_pad.GetOutput()) 594 595 pic.pipeline = utils.OperationNode( 596 "tile", comment=f"by {nx}x{ny}", parents=[self], c="#f28482" 597 ) 598 return pic 599 600 def append(self, pictures, axis="z", preserve_extents=False): 601 """ 602 Append the input images to the current one along the specified axis. 603 Except for the append axis, all inputs must have the same extent. 604 All inputs must have the same number of scalar components. 605 The output has the same origin and spacing as the first input. 606 The origin and spacing of all other inputs are ignored. 607 All inputs must have the same scalar type. 608 609 Arguments: 610 axis : (int, str) 611 axis expanded to hold the multiple images 612 preserve_extents : (bool) 613 if True, the extent of the inputs is used to place 614 the image in the output. The whole extent of the output is the union of the input 615 whole extents. Any portion of the output not covered by the inputs is set to zero. 616 The origin and spacing is taken from the first input. 617 618 Example: 619 ```python 620 from vedo import Picture, dataurl 621 pic = Picture(dataurl+'dog.jpg').pad() 622 pic.append([pic,pic], axis='y') 623 pic.append([pic,pic,pic], axis='x') 624 pic.show(axes=1).close() 625 ``` 626 ![](https://vedo.embl.es/images/feats/pict_append.png) 627 """ 628 ima = vtk.vtkImageAppend() 629 ima.SetInputData(self._data) 630 if not utils.is_sequence(pictures): 631 pictures = [pictures] 632 for p in pictures: 633 if isinstance(p, vtk.vtkImageData): 634 ima.AddInputData(p) 635 else: 636 ima.AddInputData(p.inputdata()) 637 ima.SetPreserveExtents(preserve_extents) 638 if axis == "x": 639 axis = 0 640 elif axis == "y": 641 axis = 1 642 ima.SetAppendAxis(axis) 643 ima.Update() 644 self._update(ima.GetOutput()) 645 self.pipeline = utils.OperationNode( 646 "append", comment=f"axis={axis}", parents=[self, *pictures], c="#f28482" 647 ) 648 return self 649 650 def resize(self, newsize): 651 """Resize the image resolution by specifying the number of pixels in width and height. 652 If left to zero, it will be automatically calculated to keep the original aspect ratio. 653 654 newsize is the shape of picture as [npx, npy], or it can be also expressed as a fraction. 655 """ 656 old_dims = np.array(self._data.GetDimensions()) 657 658 if not utils.is_sequence(newsize): 659 newsize = (old_dims * newsize + 0.5).astype(int) 660 661 if not newsize[1]: 662 ar = old_dims[1] / old_dims[0] 663 newsize = [newsize[0], int(newsize[0] * ar + 0.5)] 664 if not newsize[0]: 665 ar = old_dims[0] / old_dims[1] 666 newsize = [int(newsize[1] * ar + 0.5), newsize[1]] 667 newsize = [newsize[0], newsize[1], old_dims[2]] 668 669 rsz = vtk.vtkImageResize() 670 rsz.SetInputData(self._data) 671 rsz.SetResizeMethodToOutputDimensions() 672 rsz.SetOutputDimensions(newsize) 673 rsz.Update() 674 out = rsz.GetOutput() 675 out.SetSpacing(1, 1, 1) 676 self._update(out) 677 self.pipeline = utils.OperationNode( 678 "resize", comment=f"shape={tuple(self.shape)}", parents=[self], c="#f28482" 679 ) 680 return self 681 682 def mirror(self, axis="x"): 683 """Mirror picture along x or y axis. Same as `flip()`.""" 684 ff = vtk.vtkImageFlip() 685 ff.SetInputData(self.inputdata()) 686 if axis.lower() == "x": 687 ff.SetFilteredAxis(0) 688 elif axis.lower() == "y": 689 ff.SetFilteredAxis(1) 690 else: 691 colors.printc("Error in mirror(): mirror must be set to x or y.", c="r") 692 raise RuntimeError() 693 ff.Update() 694 self._update(ff.GetOutput()) 695 self.pipeline = utils.OperationNode(f"mirror {axis}", parents=[self], c="#f28482") 696 return self 697 698 def flip(self, axis="y"): 699 """Mirror picture along x or y axis. Same as `mirror()`.""" 700 return self.mirror(axis=axis) 701 702 def rotate(self, angle, center=(), scale=1, mirroring=False, bc="w", alpha=1): 703 """ 704 Rotate by the specified angle (anticlockwise). 705 706 Arguments: 707 angle : (float) 708 rotation angle in degrees 709 center : (list) 710 center of rotation (x,y) in pixels 711 """ 712 bounds = self.bounds() 713 pc = [0, 0, 0] 714 if center: 715 pc[0] = center[0] 716 pc[1] = center[1] 717 else: 718 pc[0] = (bounds[1] + bounds[0]) / 2.0 719 pc[1] = (bounds[3] + bounds[2]) / 2.0 720 pc[2] = (bounds[5] + bounds[4]) / 2.0 721 722 transform = vtk.vtkTransform() 723 transform.Translate(pc) 724 transform.RotateWXYZ(-angle, 0, 0, 1) 725 transform.Scale(1 / scale, 1 / scale, 1) 726 transform.Translate(-pc[0], -pc[1], -pc[2]) 727 728 reslice = vtk.vtkImageReslice() 729 reslice.SetMirror(mirroring) 730 c = np.array(colors.get_color(bc)) * 255 731 reslice.SetBackgroundColor([c[0], c[1], c[2], alpha * 255]) 732 reslice.SetInputData(self._data) 733 reslice.SetResliceTransform(transform) 734 reslice.SetOutputDimensionality(2) 735 reslice.SetInterpolationModeToCubic() 736 reslice.SetOutputSpacing(self._data.GetSpacing()) 737 reslice.SetOutputOrigin(self._data.GetOrigin()) 738 reslice.SetOutputExtent(self._data.GetExtent()) 739 reslice.Update() 740 self._update(reslice.GetOutput()) 741 742 self.pipeline = utils.OperationNode( 743 "rotate", comment=f"angle={angle}", parents=[self], c="#f28482" 744 ) 745 return self 746 747 def select(self, component): 748 """Select one single component of the rgb image.""" 749 ec = vtk.vtkImageExtractComponents() 750 ec.SetInputData(self._data) 751 ec.SetComponents(component) 752 ec.Update() 753 pic = Picture(ec.GetOutput()) 754 pic.pipeline = utils.OperationNode( 755 "select", comment=f"component {component}", parents=[self], c="#f28482" 756 ) 757 return pic 758 759 def bw(self): 760 """Make it black and white using luminance calibration.""" 761 n = self._data.GetPointData().GetNumberOfComponents() 762 if n == 4: 763 ecr = vtk.vtkImageExtractComponents() 764 ecr.SetInputData(self._data) 765 ecr.SetComponents(0, 1, 2) 766 ecr.Update() 767 img = ecr.GetOutput() 768 else: 769 img = self._data 770 771 ecr = vtk.vtkImageLuminance() 772 ecr.SetInputData(img) 773 ecr.Update() 774 self._update(ecr.GetOutput()) 775 self.pipeline = utils.OperationNode("black&white", parents=[self], c="#f28482") 776 return self 777 778 def smooth(self, sigma=3, radius=None): 779 """ 780 Smooth a Picture with Gaussian kernel. 781 782 Arguments: 783 sigma : (int) 784 number of sigmas in pixel units 785 radius : (float) 786 how far out the gaussian kernel will go before being clamped to zero 787 """ 788 gsf = vtk.vtkImageGaussianSmooth() 789 gsf.SetDimensionality(2) 790 gsf.SetInputData(self._data) 791 if radius is not None: 792 if utils.is_sequence(radius): 793 gsf.SetRadiusFactors(radius[0], radius[1]) 794 else: 795 gsf.SetRadiusFactor(radius) 796 797 if utils.is_sequence(sigma): 798 gsf.SetStandardDeviations(sigma[0], sigma[1]) 799 else: 800 gsf.SetStandardDeviation(sigma) 801 gsf.Update() 802 self._update(gsf.GetOutput()) 803 self.pipeline = utils.OperationNode( 804 "smooth", comment=f"sigma={sigma}", parents=[self], c="#f28482" 805 ) 806 return self 807 808 def median(self): 809 """ 810 Median filter that preserves thin lines and corners. 811 812 It operates on a 5x5 pixel neighborhood. It computes two values initially: 813 the median of the + neighbors and the median of the x neighbors. 814 It then computes the median of these two values plus the center pixel. 815 This result of this second median is the output pixel value. 816 """ 817 medf = vtk.vtkImageHybridMedian2D() 818 medf.SetInputData(self._data) 819 medf.Update() 820 self._update(medf.GetOutput()) 821 self.pipeline = utils.OperationNode("median", parents=[self], c="#f28482") 822 return self 823 824 def enhance(self): 825 """ 826 Enhance a b&w picture using the laplacian, enhancing high-freq edges. 827 828 Example: 829 ```python 830 from vedo import * 831 pic = Picture(vedo.dataurl+'images/dog.jpg').bw() 832 show(pic, pic.clone().enhance(), N=2, mode='image', zoom='tight') 833 ``` 834 ![](https://vedo.embl.es/images/feats/pict_enhance.png) 835 """ 836 img = self._data 837 scalarRange = img.GetPointData().GetScalars().GetRange() 838 839 cast = vtk.vtkImageCast() 840 cast.SetInputData(img) 841 cast.SetOutputScalarTypeToDouble() 842 cast.Update() 843 844 laplacian = vtk.vtkImageLaplacian() 845 laplacian.SetInputData(cast.GetOutput()) 846 laplacian.SetDimensionality(2) 847 laplacian.Update() 848 849 subtr = vtk.vtkImageMathematics() 850 subtr.SetInputData(0, cast.GetOutput()) 851 subtr.SetInputData(1, laplacian.GetOutput()) 852 subtr.SetOperationToSubtract() 853 subtr.Update() 854 855 color_window = scalarRange[1] - scalarRange[0] 856 color_level = color_window / 2 857 original_color = vtk.vtkImageMapToWindowLevelColors() 858 original_color.SetWindow(color_window) 859 original_color.SetLevel(color_level) 860 original_color.SetInputData(subtr.GetOutput()) 861 original_color.Update() 862 self._update(original_color.GetOutput()) 863 864 self.pipeline = utils.OperationNode("enhance", parents=[self], c="#f28482") 865 return self 866 867 def fft(self, mode="magnitude", logscale=12, center=True): 868 """ 869 Fast Fourier transform of a picture. 870 871 Arguments: 872 logscale : (float) 873 if non-zero, take the logarithm of the intensity and scale it by this factor. 874 mode : (str) 875 either [magnitude, real, imaginary, complex], compute the point array data accordingly. 876 center : (bool) 877 shift constant zero-frequency to the center of the image for display. 878 (FFT converts spatial images into frequency space, but puts the zero frequency at the origin) 879 """ 880 ffti = vtk.vtkImageFFT() 881 ffti.SetInputData(self._data) 882 ffti.Update() 883 884 if "mag" in mode: 885 mag = vtk.vtkImageMagnitude() 886 mag.SetInputData(ffti.GetOutput()) 887 mag.Update() 888 out = mag.GetOutput() 889 elif "real" in mode: 890 erf = vtk.vtkImageExtractComponents() 891 erf.SetInputData(ffti.GetOutput()) 892 erf.SetComponents(0) 893 erf.Update() 894 out = erf.GetOutput() 895 elif "imaginary" in mode: 896 eimf = vtk.vtkImageExtractComponents() 897 eimf.SetInputData(ffti.GetOutput()) 898 eimf.SetComponents(1) 899 eimf.Update() 900 out = eimf.GetOutput() 901 elif "complex" in mode: 902 out = ffti.GetOutput() 903 else: 904 colors.printc("Error in fft(): unknown mode", mode) 905 raise RuntimeError() 906 907 if center: 908 center = vtk.vtkImageFourierCenter() 909 center.SetInputData(out) 910 center.Update() 911 out = center.GetOutput() 912 913 if "complex" not in mode: 914 if logscale: 915 ils = vtk.vtkImageLogarithmicScale() 916 ils.SetInputData(out) 917 ils.SetConstant(logscale) 918 ils.Update() 919 out = ils.GetOutput() 920 921 pic = Picture(out) 922 pic.pipeline = utils.OperationNode("FFT", parents=[self], c="#f28482") 923 return pic 924 925 def rfft(self, mode="magnitude"): 926 """Reverse Fast Fourier transform of a picture.""" 927 928 ffti = vtk.vtkImageRFFT() 929 ffti.SetInputData(self._data) 930 ffti.Update() 931 932 if "mag" in mode: 933 mag = vtk.vtkImageMagnitude() 934 mag.SetInputData(ffti.GetOutput()) 935 mag.Update() 936 out = mag.GetOutput() 937 elif "real" in mode: 938 erf = vtk.vtkImageExtractComponents() 939 erf.SetInputData(ffti.GetOutput()) 940 erf.SetComponents(0) 941 erf.Update() 942 out = erf.GetOutput() 943 elif "imaginary" in mode: 944 eimf = vtk.vtkImageExtractComponents() 945 eimf.SetInputData(ffti.GetOutput()) 946 eimf.SetComponents(1) 947 eimf.Update() 948 out = eimf.GetOutput() 949 elif "complex" in mode: 950 out = ffti.GetOutput() 951 else: 952 colors.printc("Error in rfft(): unknown mode", mode) 953 raise RuntimeError() 954 955 pic = Picture(out) 956 pic.pipeline = utils.OperationNode("rFFT", parents=[self], c="#f28482") 957 return pic 958 959 def filterpass(self, lowcutoff=None, highcutoff=None, order=3): 960 """ 961 Low-pass and high-pass filtering become trivial in the frequency domain. 962 A portion of the pixels/voxels are simply masked or attenuated. 963 This function applies a high pass Butterworth filter that attenuates the 964 frequency domain image with the function 965 966 The gradual attenuation of the filter is important. 967 A simple high-pass filter would simply mask a set of pixels in the frequency domain, 968 but the abrupt transition would cause a ringing effect in the spatial domain. 969 970 Arguments: 971 lowcutoff : (list) 972 the cutoff frequencies 973 highcutoff : (list) 974 the cutoff frequencies 975 order : (int) 976 order determines sharpness of the cutoff curve 977 """ 978 # https://lorensen.github.io/VTKExamples/site/Cxx/ImageProcessing/IdealHighPass 979 fft = vtk.vtkImageFFT() 980 fft.SetInputData(self._data) 981 fft.Update() 982 out = fft.GetOutput() 983 984 if highcutoff: 985 blp = vtk.vtkImageButterworthLowPass() 986 blp.SetInputData(out) 987 blp.SetCutOff(highcutoff) 988 blp.SetOrder(order) 989 blp.Update() 990 out = blp.GetOutput() 991 992 if lowcutoff: 993 bhp = vtk.vtkImageButterworthHighPass() 994 bhp.SetInputData(out) 995 bhp.SetCutOff(lowcutoff) 996 bhp.SetOrder(order) 997 bhp.Update() 998 out = bhp.GetOutput() 999 1000 rfft = vtk.vtkImageRFFT() 1001 rfft.SetInputData(out) 1002 rfft.Update() 1003 1004 ecomp = vtk.vtkImageExtractComponents() 1005 ecomp.SetInputData(rfft.GetOutput()) 1006 ecomp.SetComponents(0) 1007 ecomp.Update() 1008 1009 caster = vtk.vtkImageCast() 1010 caster.SetOutputScalarTypeToUnsignedChar() 1011 caster.SetInputData(ecomp.GetOutput()) 1012 caster.Update() 1013 self._update(caster.GetOutput()) 1014 self.pipeline = utils.OperationNode("filterpass", parents=[self], c="#f28482") 1015 return self 1016 1017 def blend(self, pic, alpha1=0.5, alpha2=0.5): 1018 """ 1019 Take L, LA, RGB, or RGBA images as input and blends 1020 them according to the alpha values and/or the opacity setting for each input. 1021 """ 1022 blf = vtk.vtkImageBlend() 1023 blf.AddInputData(self._data) 1024 blf.AddInputData(pic.inputdata()) 1025 blf.SetOpacity(0, alpha1) 1026 blf.SetOpacity(1, alpha2) 1027 blf.SetBlendModeToNormal() 1028 blf.Update() 1029 self._update(blf.GetOutput()) 1030 self.pipeline = utils.OperationNode("blend", parents=[self, pic], c="#f28482") 1031 return self 1032 1033 def warp( 1034 self, 1035 source_pts=(), 1036 target_pts=(), 1037 transform=None, 1038 sigma=1, 1039 mirroring=False, 1040 bc="w", 1041 alpha=1, 1042 ): 1043 """ 1044 Warp an image using thin-plate splines. 1045 1046 Arguments: 1047 source_pts : (list) 1048 source points 1049 target_pts : (list) 1050 target points 1051 transform : (vtkTransform) 1052 a vtkTransform object can be supplied 1053 sigma : (float), optional 1054 stiffness of the interpolation 1055 mirroring : (bool) 1056 fill the margins with a reflection of the original image 1057 bc : (color) 1058 fill the margins with a solid color 1059 alpha : (float) 1060 opacity of the filled margins 1061 """ 1062 if transform is None: 1063 # source and target must be filled 1064 transform = vtk.vtkThinPlateSplineTransform() 1065 transform.SetBasisToR2LogR() 1066 1067 parents = [self] 1068 if isinstance(source_pts, vedo.Points): 1069 parents.append(source_pts) 1070 source_pts = source_pts.points() 1071 if isinstance(target_pts, vedo.Points): 1072 parents.append(target_pts) 1073 target_pts = target_pts.points() 1074 1075 ns = len(source_pts) 1076 nt = len(target_pts) 1077 if ns != nt: 1078 colors.printc("Error in picture.warp(): #source != #target points", ns, nt, c="r") 1079 raise RuntimeError() 1080 1081 ptsou = vtk.vtkPoints() 1082 ptsou.SetNumberOfPoints(ns) 1083 1084 pttar = vtk.vtkPoints() 1085 pttar.SetNumberOfPoints(nt) 1086 1087 for i in range(ns): 1088 p = source_pts[i] 1089 ptsou.SetPoint(i, [p[0], p[1], 0]) 1090 p = target_pts[i] 1091 pttar.SetPoint(i, [p[0], p[1], 0]) 1092 1093 transform.SetSigma(sigma) 1094 transform.SetSourceLandmarks(pttar) 1095 transform.SetTargetLandmarks(ptsou) 1096 else: 1097 # ignore source and target 1098 pass 1099 1100 reslice = vtk.vtkImageReslice() 1101 reslice.SetInputData(self._data) 1102 reslice.SetOutputDimensionality(2) 1103 reslice.SetResliceTransform(transform) 1104 reslice.SetInterpolationModeToCubic() 1105 reslice.SetMirror(mirroring) 1106 c = np.array(colors.get_color(bc)) * 255 1107 reslice.SetBackgroundColor([c[0], c[1], c[2], alpha * 255]) 1108 reslice.Update() 1109 self.transform = transform 1110 self._update(reslice.GetOutput()) 1111 self.pipeline = utils.OperationNode("warp", parents=parents, c="#f28482") 1112 return self 1113 1114 def invert(self): 1115 """ 1116 Return an inverted picture (inverted in each color channel). 1117 """ 1118 rgb = self.tonumpy() 1119 data = 255 - np.array(rgb) 1120 self._update(_get_img(data)) 1121 self.pipeline = utils.OperationNode("invert", parents=[self], c="#f28482") 1122 return self 1123 1124 def binarize(self, threshold=None, invert=False): 1125 """ 1126 Return a new Picture where pixel above threshold are set to 255 1127 and pixels below are set to 0. 1128 1129 Arguments: 1130 threshold : (float) 1131 input threshold value 1132 invert : (bool) 1133 invert threshold direction 1134 1135 Example: 1136 ```python 1137 from vedo import Picture, show 1138 pic1 = Picture("https://aws.glamour.es/prod/designs/v1/assets/620x459/547577.jpg") 1139 pic2 = pic1.clone().invert() 1140 pic3 = pic1.clone().binarize() 1141 show(pic1, pic2, pic3, N=3, bg="blue9").close() 1142 ``` 1143 ![](https://vedo.embl.es/images/feats/pict_binarize.png) 1144 """ 1145 rgb = self.tonumpy() 1146 if rgb.ndim == 3: 1147 intensity = np.sum(rgb, axis=2) / 3 1148 else: 1149 intensity = rgb 1150 1151 if threshold is None: 1152 vmin, vmax = np.min(intensity), np.max(intensity) 1153 threshold = (vmax + vmin) / 2 1154 1155 data = np.zeros_like(intensity).astype(np.uint8) 1156 mask = np.where(intensity > threshold) 1157 if invert: 1158 data += 255 1159 data[mask] = 0 1160 else: 1161 data[mask] = 255 1162 1163 self._update(_get_img(data, flip=True)) 1164 1165 self.pipeline = utils.OperationNode( 1166 "binarize", comment=f"threshold={threshold}", parents=[self], c="#f28482" 1167 ) 1168 return self 1169 1170 def threshold(self, value=None, flip=False): 1171 """ 1172 Create a polygonal Mesh from a Picture by filling regions with pixels 1173 luminosity above a specified value. 1174 1175 Arguments: 1176 value : (float) 1177 The default is None, e.i. 1/3 of the scalar range. 1178 flip: (bool) 1179 Flip polygon orientations 1180 1181 Returns: 1182 A polygonal mesh. 1183 """ 1184 mgf = vtk.vtkImageMagnitude() 1185 mgf.SetInputData(self._data) 1186 mgf.Update() 1187 msq = vtk.vtkMarchingSquares() 1188 msq.SetInputData(mgf.GetOutput()) 1189 if value is None: 1190 r0, r1 = self._data.GetScalarRange() 1191 value = r0 + (r1 - r0) / 3 1192 msq.SetValue(0, value) 1193 msq.Update() 1194 if flip: 1195 rs = vtk.vtkReverseSense() 1196 rs.SetInputData(msq.GetOutput()) 1197 rs.ReverseCellsOn() 1198 rs.ReverseNormalsOff() 1199 rs.Update() 1200 output = rs.GetOutput() 1201 else: 1202 output = msq.GetOutput() 1203 ctr = vtk.vtkContourTriangulator() 1204 ctr.SetInputData(output) 1205 ctr.Update() 1206 out = vedo.Mesh(ctr.GetOutput(), c="k").bc("t").lighting("off") 1207 1208 out.pipeline = utils.OperationNode( 1209 "threshold", comment=f"{value: .2f}", parents=[self], c="#f28482:#e9c46a" 1210 ) 1211 return out 1212 1213 def tomesh(self): 1214 """ 1215 Convert an image to polygonal data (quads), 1216 with each polygon vertex assigned a RGBA value. 1217 """ 1218 dims = self._data.GetDimensions() 1219 gr = vedo.shapes.Grid(s=dims[:2], res=(dims[0] - 1, dims[1] - 1)) 1220 gr.pos(int(dims[0] / 2), int(dims[1] / 2)).pickable(True).wireframe(False).lw(0) 1221 self._data.GetPointData().GetScalars().SetName("RGBA") 1222 gr.inputdata().GetPointData().AddArray(self._data.GetPointData().GetScalars()) 1223 gr.inputdata().GetPointData().SetActiveScalars("RGBA") 1224 gr.mapper().SetArrayName("RGBA") 1225 gr.mapper().SetScalarModeToUsePointData() 1226 gr.mapper().ScalarVisibilityOn() 1227 gr.name = self.name 1228 gr.filename = self.filename 1229 gr.pipeline = utils.OperationNode("tomesh", parents=[self], c="#f28482:#e9c46a") 1230 return gr 1231 1232 def tonumpy(self): 1233 """ 1234 Get read-write access to pixels of a Picture object as a numpy array. 1235 Note that the shape is (nrofchannels, nx, ny). 1236 1237 When you set values in the output image, you don't want numpy to reallocate the array 1238 but instead set values in the existing array, so use the [:] operator. 1239 Example: arr[:] = arr - 15 1240 1241 If the array is modified call: 1242 ``picture.modified()`` 1243 when all your modifications are completed. 1244 """ 1245 nx, ny, _ = self._data.GetDimensions() 1246 nchan = self._data.GetPointData().GetScalars().GetNumberOfComponents() 1247 narray = utils.vtk2numpy(self._data.GetPointData().GetScalars()).reshape(ny, nx, nchan) 1248 narray = np.flip(narray, axis=0).astype(np.uint8) 1249 return narray.squeeze() 1250 1251 def rectangle(self, xspan, yspan, c="green5", alpha=1): 1252 """Draw a rectangle box on top of current image. Units are pixels. 1253 1254 Example: 1255 ```python 1256 import vedo 1257 pic = vedo.Picture(vedo.dataurl+"images/dog.jpg") 1258 pic.rectangle([100,300], [100,200], c='green4', alpha=0.7) 1259 pic.line([100,100],[400,500], lw=2, alpha=1) 1260 pic.triangle([250,300], [100,300], [200,400], c='blue5') 1261 vedo.show(pic, axes=1).close() 1262 ``` 1263 ![](https://vedo.embl.es/images/feats/pict_drawon.png) 1264 """ 1265 x1, x2 = xspan 1266 y1, y2 = yspan 1267 1268 r, g, b = vedo.colors.get_color(c) 1269 c = np.array([r, g, b]) * 255 1270 c = c.astype(np.uint8) 1271 1272 alpha = min(alpha, 1) 1273 if alpha <= 0: 1274 return self 1275 alpha2 = alpha 1276 alpha1 = 1 - alpha 1277 1278 nx, ny = self.dimensions() 1279 if x2 > nx: 1280 x2 = nx - 1 1281 if y2 > ny: 1282 y2 = ny - 1 1283 1284 nchan = self.channels() 1285 narrayA = self.tonumpy() 1286 1287 canvas_source = vtk.vtkImageCanvasSource2D() 1288 canvas_source.SetExtent(0, nx - 1, 0, ny - 1, 0, 0) 1289 canvas_source.SetScalarTypeToUnsignedChar() 1290 canvas_source.SetNumberOfScalarComponents(nchan) 1291 canvas_source.SetDrawColor(255, 255, 255) 1292 canvas_source.FillBox(x1, x2, y1, y2) 1293 canvas_source.Update() 1294 image_data = canvas_source.GetOutput() 1295 1296 vscals = image_data.GetPointData().GetScalars() 1297 narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny, nx, nchan) 1298 narrayB = np.flip(narrayB, axis=0) 1299 narrayC = np.where(narrayB < 255, narrayA, alpha1 * narrayA + alpha2 * c) 1300 self._update(_get_img(narrayC)) 1301 self.pipeline = utils.OperationNode("rectangle", parents=[self], c="#f28482") 1302 return self 1303 1304 def line(self, p1, p2, lw=2, c="k2", alpha=1): 1305 """Draw a line on top of current image. Units are pixels.""" 1306 x1, x2 = p1 1307 y1, y2 = p2 1308 1309 r, g, b = vedo.colors.get_color(c) 1310 c = np.array([r, g, b]) * 255 1311 c = c.astype(np.uint8) 1312 1313 alpha = min(alpha, 1) 1314 if alpha <= 0: 1315 return self 1316 alpha2 = alpha 1317 alpha1 = 1 - alpha 1318 1319 nx, ny = self.dimensions() 1320 if x2 > nx: 1321 x2 = nx - 1 1322 if y2 > ny: 1323 y2 = ny - 1 1324 1325 nchan = self.channels() 1326 narrayA = self.tonumpy() 1327 1328 canvas_source = vtk.vtkImageCanvasSource2D() 1329 canvas_source.SetExtent(0, nx - 1, 0, ny - 1, 0, 0) 1330 canvas_source.SetScalarTypeToUnsignedChar() 1331 canvas_source.SetNumberOfScalarComponents(nchan) 1332 canvas_source.SetDrawColor(255, 255, 255) 1333 canvas_source.FillTube(x1, x2, y1, y2, lw) 1334 canvas_source.Update() 1335 image_data = canvas_source.GetOutput() 1336 1337 vscals = image_data.GetPointData().GetScalars() 1338 narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny, nx, nchan) 1339 narrayB = np.flip(narrayB, axis=0) 1340 narrayC = np.where(narrayB < 255, narrayA, alpha1 * narrayA + alpha2 * c) 1341 self._update(_get_img(narrayC)) 1342 self.pipeline = utils.OperationNode("line", parents=[self], c="#f28482") 1343 return self 1344 1345 def triangle(self, p1, p2, p3, c="red3", alpha=1): 1346 """Draw a triangle on top of current image. Units are pixels.""" 1347 x1, y1 = p1 1348 x2, y2 = p2 1349 x3, y3 = p3 1350 1351 r, g, b = vedo.colors.get_color(c) 1352 c = np.array([r, g, b]) * 255 1353 c = c.astype(np.uint8) 1354 1355 alpha = min(alpha, 1) 1356 if alpha <= 0: 1357 return self 1358 alpha2 = alpha 1359 alpha1 = 1 - alpha 1360 1361 nx, ny = self.dimensions() 1362 x1 = min(x1, nx) 1363 x2 = min(x2, nx) 1364 x3 = min(x3, nx) 1365 1366 y1 = min(y1, ny) 1367 y2 = min(y2, ny) 1368 y3 = min(y3, ny) 1369 1370 nchan = self.channels() 1371 narrayA = self.tonumpy() 1372 1373 canvas_source = vtk.vtkImageCanvasSource2D() 1374 canvas_source.SetExtent(0, nx - 1, 0, ny - 1, 0, 0) 1375 canvas_source.SetScalarTypeToUnsignedChar() 1376 canvas_source.SetNumberOfScalarComponents(nchan) 1377 canvas_source.SetDrawColor(255, 255, 255) 1378 canvas_source.FillTriangle(x1, y1, x2, y2, x3, y3) 1379 canvas_source.Update() 1380 image_data = canvas_source.GetOutput() 1381 1382 vscals = image_data.GetPointData().GetScalars() 1383 narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny, nx, nchan) 1384 narrayB = np.flip(narrayB, axis=0) 1385 narrayC = np.where(narrayB < 255, narrayA, alpha1 * narrayA + alpha2 * c) 1386 self._update(_get_img(narrayC)) 1387 self.pipeline = utils.OperationNode("triangle", parents=[self], c="#f28482") 1388 return self 1389 1390 def add_text( 1391 self, 1392 txt, 1393 # pos=(0, 0), # TODO 1394 width=400, 1395 height=200, 1396 alpha=1, 1397 c="black", 1398 bg=None, 1399 alpha_bg=1, 1400 font="Theemim", 1401 dpi=200, 1402 justify="bottom-left", 1403 ): 1404 """Add text to an image.""" 1405 1406 tp = vtk.vtkTextProperty() 1407 tp.BoldOff() 1408 tp.FrameOff() 1409 tp.SetColor(colors.get_color(c)) 1410 tp.SetJustificationToLeft() 1411 if "top" in justify: 1412 tp.SetVerticalJustificationToTop() 1413 if "bottom" in justify: 1414 tp.SetVerticalJustificationToBottom() 1415 if "cent" in justify: 1416 tp.SetVerticalJustificationToCentered() 1417 tp.SetJustificationToCentered() 1418 if "left" in justify: 1419 tp.SetJustificationToLeft() 1420 if "right" in justify: 1421 tp.SetJustificationToRight() 1422 1423 if font.lower() == "courier": tp.SetFontFamilyToCourier() 1424 elif font.lower() == "times": tp.SetFontFamilyToTimes() 1425 elif font.lower() == "arial": tp.SetFontFamilyToArial() 1426 else: 1427 tp.SetFontFamily(vtk.VTK_FONT_FILE) 1428 tp.SetFontFile(utils.get_font_path(font)) 1429 1430 if bg: 1431 bgcol = colors.get_color(bg) 1432 tp.SetBackgroundColor(bgcol) 1433 tp.SetBackgroundOpacity(alpha_bg) 1434 tp.SetFrameColor(bgcol) 1435 tp.FrameOn() 1436 1437 tr = vtk.vtkTextRenderer() 1438 # GetConstrainedFontSize (const vtkUnicodeString &str, 1439 # vtkTextProperty(*tprop, int targetWidth, int targetHeight, int dpi) 1440 fs = tr.GetConstrainedFontSize(txt, tp, width, height, dpi) 1441 tp.SetFontSize(fs) 1442 1443 img = vtk.vtkImageData() 1444 # img.SetOrigin(*pos,1) 1445 tr.RenderString(tp, txt, img, [width, height], dpi) 1446 # RenderString (vtkTextProperty *tprop, const vtkStdString &str, 1447 # vtkImageData *data, int textDims[2], int dpi, int backend=Default) 1448 1449 blf = vtk.vtkImageBlend() 1450 blf.AddInputData(self._data) 1451 blf.AddInputData(img) 1452 blf.SetOpacity(0, 1) 1453 blf.SetOpacity(1, alpha) 1454 blf.SetBlendModeToNormal() 1455 blf.Update() 1456 1457 self._update(blf.GetOutput()) 1458 self.pipeline = utils.OperationNode( 1459 "add_text", comment=f"{txt}", parents=[self], c="#f28482" 1460 ) 1461 return self 1462 1463 def modified(self): 1464 """Use in conjunction with ``tonumpy()`` to update any modifications to the picture array""" 1465 self._data.GetPointData().GetScalars().Modified() 1466 return self 1467 1468 def write(self, filename): 1469 """Write picture to file as png or jpg.""" 1470 vedo.file_io.write(self._data, filename) 1471 self.pipeline = utils.OperationNode( 1472 "write", comment=filename[:15], parents=[self], c="#8a817c", shape="cylinder" 1473 ) 1474 return self
Derived class of vtkImageActor
. Used to represent 2D pictures in a 3D world.
250 def __init__(self, obj=None, channels=3, flip=False): 251 """ 252 Can be instantiated with a path file name or with a numpy array. 253 254 By default the transparency channel is disabled. 255 To enable it set channels=4. 256 257 Use `Picture.dimensions()` to access the number of pixels in x and y. 258 259 Arguments: 260 channels : (int, list) 261 only select these specific rgba channels (useful to remove alpha) 262 flip : (bool) 263 flip xy axis convention (when input is a numpy array) 264 """ 265 266 vtk.vtkImageActor.__init__(self) 267 vedo.base.Base3DProp.__init__(self) 268 269 if utils.is_sequence(obj) and len(obj) > 0: # passing array 270 img = _get_img(obj, flip) 271 272 elif isinstance(obj, vtk.vtkImageData): 273 img = obj 274 275 elif isinstance(obj, str): 276 img = _get_img(obj) 277 self.filename = obj 278 279 else: 280 img = vtk.vtkImageData() 281 282 # select channels 283 if isinstance(channels, int): 284 channels = list(range(channels)) 285 286 nchans = len(channels) 287 n = img.GetPointData().GetScalars().GetNumberOfComponents() 288 if nchans and n > nchans: 289 pec = vtk.vtkImageExtractComponents() 290 pec.SetInputData(img) 291 if nchans == 4: 292 pec.SetComponents(channels[0], channels[1], channels[2], channels[3]) 293 elif nchans == 3: 294 pec.SetComponents(channels[0], channels[1], channels[2]) 295 elif nchans == 2: 296 pec.SetComponents(channels[0], channels[1]) 297 elif nchans == 1: 298 pec.SetComponents(channels[0]) 299 pec.Update() 300 img = pec.GetOutput() 301 302 self._data = img 303 self.SetInputData(img) 304 305 sx, sy, _ = img.GetDimensions() 306 self.shape = np.array([sx, sy]) 307 308 self._mapper = self.GetMapper() 309 310 self.pipeline = utils.OperationNode("Picture", comment=f"#shape {self.shape}", c="#f28482") 311 ######################################################################
Can be instantiated with a path file name or with a numpy array.
By default the transparency channel is disabled. To enable it set channels=4.
Use Picture.dimensions()
to access the number of pixels in x and y.
Arguments:
- channels : (int, list) only select these specific rgba channels (useful to remove alpha)
- flip : (bool) flip xy axis convention (when input is a numpy array)
386 def inputdata(self): 387 """Return the underlying ``vtkImagaData`` object.""" 388 return self._data
Return the underlying vtkImagaData
object.
390 def dimensions(self): 391 """Return the picture dimension as number of pixels in x and y""" 392 nx, ny, _ = self._data.GetDimensions() 393 return np.array([nx, ny])
Return the picture dimension as number of pixels in x and y
395 def channels(self): 396 """Return the number of channels in picture""" 397 return self._data.GetPointData().GetScalars().GetNumberOfComponents()
Return the number of channels in picture
408 def clone(self, transformed=False): 409 """Return an exact copy of the input Picture. 410 If transform is True, it is given the same scaling and position.""" 411 img = vtk.vtkImageData() 412 img.DeepCopy(self._data) 413 pic = Picture(img) 414 if transformed: 415 # assign the same transformation to the copy 416 pic.SetOrigin(self.GetOrigin()) 417 pic.SetScale(self.GetScale()) 418 pic.SetOrientation(self.GetOrientation()) 419 pic.SetPosition(self.GetPosition()) 420 421 pic.pipeline = utils.OperationNode("clone", parents=[self], c="#f7dada", shape="diamond") 422 return pic
Return an exact copy of the input Picture. If transform is True, it is given the same scaling and position.
424 def cmap(self, name, vmin=None, vmax=None): 425 """Colorize a picture with a colormap representing pixel intensity""" 426 n = self._data.GetPointData().GetNumberOfComponents() 427 if n > 1: 428 ecr = vtk.vtkImageExtractComponents() 429 ecr.SetInputData(self._data) 430 ecr.SetComponents(0, 1, 2) 431 ecr.Update() 432 ilum = vtk.vtkImageMagnitude() 433 ilum.SetInputData(self._data) 434 ilum.Update() 435 img = ilum.GetOutput() 436 else: 437 img = self._data 438 439 lut = vtk.vtkLookupTable() 440 _vmin, _vmax = img.GetScalarRange() 441 if vmin is not None: 442 _vmin = vmin 443 if vmax is not None: 444 _vmax = vmax 445 lut.SetRange(_vmin, _vmax) 446 447 ncols = 256 448 lut.SetNumberOfTableValues(ncols) 449 cols = colors.color_map(range(ncols), name, 0, ncols) 450 for i, c in enumerate(cols): 451 lut.SetTableValue(i, *c) 452 lut.Build() 453 454 imap = vtk.vtkImageMapToColors() 455 imap.SetLookupTable(lut) 456 imap.SetInputData(img) 457 imap.Update() 458 self._update(imap.GetOutput()) 459 self.pipeline = utils.OperationNode( 460 f"cmap", comment=f'"{name}"', parents=[self], c="#f28482" 461 ) 462 return self
Colorize a picture with a colormap representing pixel intensity
464 def extent(self, ext=None): 465 """ 466 Get or set the physical extent that the picture spans. 467 Format is `ext=[minx, maxx, miny, maxy]`. 468 """ 469 if ext is None: 470 return self._data.GetExtent() 471 472 self._data.SetExtent(ext[0], ext[1], ext[2], ext[3], 0, 0) 473 self._mapper.Modified() 474 return self
Get or set the physical extent that the picture spans.
Format is ext=[minx, maxx, miny, maxy]
.
476 def alpha(self, a=None): 477 """Set/get picture's transparency in the rendering scene.""" 478 if a is not None: 479 self.GetProperty().SetOpacity(a) 480 return self 481 return self.GetProperty().GetOpacity()
Set/get picture's transparency in the rendering scene.
483 def level(self, value=None): 484 """Get/Set the image color level (brightness) in the rendering scene.""" 485 if value is None: 486 return self.GetProperty().GetColorLevel() 487 self.GetProperty().SetColorLevel(value) 488 return self
Get/Set the image color level (brightness) in the rendering scene.
490 def window(self, value=None): 491 """Get/Set the image color window (contrast) in the rendering scene.""" 492 if value is None: 493 return self.GetProperty().GetColorWindow() 494 self.GetProperty().SetColorWindow(value) 495 return self
Get/Set the image color window (contrast) in the rendering scene.
497 def crop(self, top=None, bottom=None, right=None, left=None, pixels=False): 498 """Crop picture. 499 500 Arguments: 501 top : (float) 502 fraction to crop from the top margin 503 bottom : (float) 504 fraction to crop from the bottom margin 505 left : (float) 506 fraction to crop from the left margin 507 right : (float) 508 fraction to crop from the right margin 509 pixels : (bool) 510 units are pixels 511 """ 512 extractVOI = vtk.vtkExtractVOI() 513 extractVOI.SetInputData(self._data) 514 extractVOI.IncludeBoundaryOn() 515 516 d = self.GetInput().GetDimensions() 517 if pixels: 518 extractVOI.SetVOI(left, d[0] - right - 1, bottom, d[1] - top - 1, 0, 0) 519 else: 520 bx0, bx1, by0, by1 = 0, d[0]-1, 0, d[1]-1 521 if left is not None: bx0 = int((d[0]-1)*left) 522 if right is not None: bx1 = int((d[0]-1)*(1-right)) 523 if bottom is not None: by0 = int((d[1]-1)*bottom) 524 if top is not None: by1 = int((d[1]-1)*(1-top)) 525 extractVOI.SetVOI(bx0, bx1, by0, by1, 0, 0) 526 extractVOI.Update() 527 528 self.shape = extractVOI.GetOutput().GetDimensions()[:2] 529 self._update(extractVOI.GetOutput()) 530 self.pipeline = utils.OperationNode( 531 "crop", comment=f"shape={tuple(self.shape)}", parents=[self], c="#f28482" 532 ) 533 return self
Crop picture.
Arguments:
- top : (float) fraction to crop from the top margin
- bottom : (float) fraction to crop from the bottom margin
- left : (float) fraction to crop from the left margin
- right : (float) fraction to crop from the right margin
- pixels : (bool) units are pixels
535 def pad(self, pixels=10, value=255): 536 """ 537 Add the specified number of pixels at the picture borders. 538 Pixels can be a list formatted as [left,right,bottom,top]. 539 540 Arguments: 541 pixels : (int, list) 542 number of pixels to be added (or a list of length 4) 543 value : (int) 544 intensity value (gray-scale color) of the padding 545 """ 546 x0, x1, y0, y1, _z0, _z1 = self._data.GetExtent() 547 pf = vtk.vtkImageConstantPad() 548 pf.SetInputData(self._data) 549 pf.SetConstant(value) 550 if utils.is_sequence(pixels): 551 pf.SetOutputWholeExtent( 552 x0 - pixels[0], x1 + pixels[1], 553 y0 - pixels[2], y1 + pixels[3], 554 0, 0 555 ) 556 else: 557 pf.SetOutputWholeExtent( 558 x0 - pixels, x1 + pixels, 559 y0 - pixels, y1 + pixels, 560 0, 0 561 ) 562 pf.Update() 563 self._update(pf.GetOutput()) 564 self.pipeline = utils.OperationNode( 565 "pad", comment=f"{pixels} pixels", parents=[self], c="#f28482" 566 ) 567 return self
Add the specified number of pixels at the picture borders. Pixels can be a list formatted as [left,right,bottom,top].
Arguments:
- pixels : (int, list) number of pixels to be added (or a list of length 4)
- value : (int) intensity value (gray-scale color) of the padding
569 def tile(self, nx=4, ny=4, shift=(0, 0)): 570 """ 571 Generate a tiling from the current picture by mirroring and repeating it. 572 573 Arguments: 574 nx : (float) 575 number of repeats along x 576 ny : (float) 577 number of repeats along x 578 shift : (list) 579 shift in x and y in pixels 580 """ 581 x0, x1, y0, y1, z0, z1 = self._data.GetExtent() 582 constant_pad = vtk.vtkImageMirrorPad() 583 constant_pad.SetInputData(self._data) 584 constant_pad.SetOutputWholeExtent( 585 int(x0 + shift[0] + 0.5), 586 int(x1 * nx + shift[0] + 0.5), 587 int(y0 + shift[1] + 0.5), 588 int(y1 * ny + shift[1] + 0.5), 589 z0, 590 z1, 591 ) 592 constant_pad.Update() 593 pic = Picture(constant_pad.GetOutput()) 594 595 pic.pipeline = utils.OperationNode( 596 "tile", comment=f"by {nx}x{ny}", parents=[self], c="#f28482" 597 ) 598 return pic
Generate a tiling from the current picture by mirroring and repeating it.
Arguments:
- nx : (float) number of repeats along x
- ny : (float) number of repeats along x
- shift : (list) shift in x and y in pixels
600 def append(self, pictures, axis="z", preserve_extents=False): 601 """ 602 Append the input images to the current one along the specified axis. 603 Except for the append axis, all inputs must have the same extent. 604 All inputs must have the same number of scalar components. 605 The output has the same origin and spacing as the first input. 606 The origin and spacing of all other inputs are ignored. 607 All inputs must have the same scalar type. 608 609 Arguments: 610 axis : (int, str) 611 axis expanded to hold the multiple images 612 preserve_extents : (bool) 613 if True, the extent of the inputs is used to place 614 the image in the output. The whole extent of the output is the union of the input 615 whole extents. Any portion of the output not covered by the inputs is set to zero. 616 The origin and spacing is taken from the first input. 617 618 Example: 619 ```python 620 from vedo import Picture, dataurl 621 pic = Picture(dataurl+'dog.jpg').pad() 622 pic.append([pic,pic], axis='y') 623 pic.append([pic,pic,pic], axis='x') 624 pic.show(axes=1).close() 625 ``` 626 ![](https://vedo.embl.es/images/feats/pict_append.png) 627 """ 628 ima = vtk.vtkImageAppend() 629 ima.SetInputData(self._data) 630 if not utils.is_sequence(pictures): 631 pictures = [pictures] 632 for p in pictures: 633 if isinstance(p, vtk.vtkImageData): 634 ima.AddInputData(p) 635 else: 636 ima.AddInputData(p.inputdata()) 637 ima.SetPreserveExtents(preserve_extents) 638 if axis == "x": 639 axis = 0 640 elif axis == "y": 641 axis = 1 642 ima.SetAppendAxis(axis) 643 ima.Update() 644 self._update(ima.GetOutput()) 645 self.pipeline = utils.OperationNode( 646 "append", comment=f"axis={axis}", parents=[self, *pictures], c="#f28482" 647 ) 648 return self
Append the input images to the current one along the specified axis. Except for the append axis, all inputs must have the same extent. All inputs must have the same number of scalar components. The output has the same origin and spacing as the first input. The origin and spacing of all other inputs are ignored. All inputs must have the same scalar type.
Arguments:
- axis : (int, str) axis expanded to hold the multiple images
- preserve_extents : (bool) if True, the extent of the inputs is used to place the image in the output. The whole extent of the output is the union of the input whole extents. Any portion of the output not covered by the inputs is set to zero. The origin and spacing is taken from the first input.
Example:
from vedo import Picture, dataurl pic = Picture(dataurl+'dog.jpg').pad() pic.append([pic,pic], axis='y') pic.append([pic,pic,pic], axis='x') pic.show(axes=1).close()
650 def resize(self, newsize): 651 """Resize the image resolution by specifying the number of pixels in width and height. 652 If left to zero, it will be automatically calculated to keep the original aspect ratio. 653 654 newsize is the shape of picture as [npx, npy], or it can be also expressed as a fraction. 655 """ 656 old_dims = np.array(self._data.GetDimensions()) 657 658 if not utils.is_sequence(newsize): 659 newsize = (old_dims * newsize + 0.5).astype(int) 660 661 if not newsize[1]: 662 ar = old_dims[1] / old_dims[0] 663 newsize = [newsize[0], int(newsize[0] * ar + 0.5)] 664 if not newsize[0]: 665 ar = old_dims[0] / old_dims[1] 666 newsize = [int(newsize[1] * ar + 0.5), newsize[1]] 667 newsize = [newsize[0], newsize[1], old_dims[2]] 668 669 rsz = vtk.vtkImageResize() 670 rsz.SetInputData(self._data) 671 rsz.SetResizeMethodToOutputDimensions() 672 rsz.SetOutputDimensions(newsize) 673 rsz.Update() 674 out = rsz.GetOutput() 675 out.SetSpacing(1, 1, 1) 676 self._update(out) 677 self.pipeline = utils.OperationNode( 678 "resize", comment=f"shape={tuple(self.shape)}", parents=[self], c="#f28482" 679 ) 680 return self
Resize the image resolution by specifying the number of pixels in width and height. If left to zero, it will be automatically calculated to keep the original aspect ratio.
newsize is the shape of picture as [npx, npy], or it can be also expressed as a fraction.
682 def mirror(self, axis="x"): 683 """Mirror picture along x or y axis. Same as `flip()`.""" 684 ff = vtk.vtkImageFlip() 685 ff.SetInputData(self.inputdata()) 686 if axis.lower() == "x": 687 ff.SetFilteredAxis(0) 688 elif axis.lower() == "y": 689 ff.SetFilteredAxis(1) 690 else: 691 colors.printc("Error in mirror(): mirror must be set to x or y.", c="r") 692 raise RuntimeError() 693 ff.Update() 694 self._update(ff.GetOutput()) 695 self.pipeline = utils.OperationNode(f"mirror {axis}", parents=[self], c="#f28482") 696 return self
Mirror picture along x or y axis. Same as flip()
.
698 def flip(self, axis="y"): 699 """Mirror picture along x or y axis. Same as `mirror()`.""" 700 return self.mirror(axis=axis)
Mirror picture along x or y axis. Same as mirror()
.
702 def rotate(self, angle, center=(), scale=1, mirroring=False, bc="w", alpha=1): 703 """ 704 Rotate by the specified angle (anticlockwise). 705 706 Arguments: 707 angle : (float) 708 rotation angle in degrees 709 center : (list) 710 center of rotation (x,y) in pixels 711 """ 712 bounds = self.bounds() 713 pc = [0, 0, 0] 714 if center: 715 pc[0] = center[0] 716 pc[1] = center[1] 717 else: 718 pc[0] = (bounds[1] + bounds[0]) / 2.0 719 pc[1] = (bounds[3] + bounds[2]) / 2.0 720 pc[2] = (bounds[5] + bounds[4]) / 2.0 721 722 transform = vtk.vtkTransform() 723 transform.Translate(pc) 724 transform.RotateWXYZ(-angle, 0, 0, 1) 725 transform.Scale(1 / scale, 1 / scale, 1) 726 transform.Translate(-pc[0], -pc[1], -pc[2]) 727 728 reslice = vtk.vtkImageReslice() 729 reslice.SetMirror(mirroring) 730 c = np.array(colors.get_color(bc)) * 255 731 reslice.SetBackgroundColor([c[0], c[1], c[2], alpha * 255]) 732 reslice.SetInputData(self._data) 733 reslice.SetResliceTransform(transform) 734 reslice.SetOutputDimensionality(2) 735 reslice.SetInterpolationModeToCubic() 736 reslice.SetOutputSpacing(self._data.GetSpacing()) 737 reslice.SetOutputOrigin(self._data.GetOrigin()) 738 reslice.SetOutputExtent(self._data.GetExtent()) 739 reslice.Update() 740 self._update(reslice.GetOutput()) 741 742 self.pipeline = utils.OperationNode( 743 "rotate", comment=f"angle={angle}", parents=[self], c="#f28482" 744 ) 745 return self
Rotate by the specified angle (anticlockwise).
Arguments:
- angle : (float) rotation angle in degrees
- center : (list) center of rotation (x,y) in pixels
747 def select(self, component): 748 """Select one single component of the rgb image.""" 749 ec = vtk.vtkImageExtractComponents() 750 ec.SetInputData(self._data) 751 ec.SetComponents(component) 752 ec.Update() 753 pic = Picture(ec.GetOutput()) 754 pic.pipeline = utils.OperationNode( 755 "select", comment=f"component {component}", parents=[self], c="#f28482" 756 ) 757 return pic
Select one single component of the rgb image.
759 def bw(self): 760 """Make it black and white using luminance calibration.""" 761 n = self._data.GetPointData().GetNumberOfComponents() 762 if n == 4: 763 ecr = vtk.vtkImageExtractComponents() 764 ecr.SetInputData(self._data) 765 ecr.SetComponents(0, 1, 2) 766 ecr.Update() 767 img = ecr.GetOutput() 768 else: 769 img = self._data 770 771 ecr = vtk.vtkImageLuminance() 772 ecr.SetInputData(img) 773 ecr.Update() 774 self._update(ecr.GetOutput()) 775 self.pipeline = utils.OperationNode("black&white", parents=[self], c="#f28482") 776 return self
Make it black and white using luminance calibration.
778 def smooth(self, sigma=3, radius=None): 779 """ 780 Smooth a Picture with Gaussian kernel. 781 782 Arguments: 783 sigma : (int) 784 number of sigmas in pixel units 785 radius : (float) 786 how far out the gaussian kernel will go before being clamped to zero 787 """ 788 gsf = vtk.vtkImageGaussianSmooth() 789 gsf.SetDimensionality(2) 790 gsf.SetInputData(self._data) 791 if radius is not None: 792 if utils.is_sequence(radius): 793 gsf.SetRadiusFactors(radius[0], radius[1]) 794 else: 795 gsf.SetRadiusFactor(radius) 796 797 if utils.is_sequence(sigma): 798 gsf.SetStandardDeviations(sigma[0], sigma[1]) 799 else: 800 gsf.SetStandardDeviation(sigma) 801 gsf.Update() 802 self._update(gsf.GetOutput()) 803 self.pipeline = utils.OperationNode( 804 "smooth", comment=f"sigma={sigma}", parents=[self], c="#f28482" 805 ) 806 return self
Smooth a Picture with Gaussian kernel.
Arguments:
- sigma : (int) number of sigmas in pixel units
- radius : (float) how far out the gaussian kernel will go before being clamped to zero
808 def median(self): 809 """ 810 Median filter that preserves thin lines and corners. 811 812 It operates on a 5x5 pixel neighborhood. It computes two values initially: 813 the median of the + neighbors and the median of the x neighbors. 814 It then computes the median of these two values plus the center pixel. 815 This result of this second median is the output pixel value. 816 """ 817 medf = vtk.vtkImageHybridMedian2D() 818 medf.SetInputData(self._data) 819 medf.Update() 820 self._update(medf.GetOutput()) 821 self.pipeline = utils.OperationNode("median", parents=[self], c="#f28482") 822 return self
Median filter that preserves thin lines and corners.
It operates on a 5x5 pixel neighborhood. It computes two values initially: the median of the + neighbors and the median of the x neighbors. It then computes the median of these two values plus the center pixel. This result of this second median is the output pixel value.
824 def enhance(self): 825 """ 826 Enhance a b&w picture using the laplacian, enhancing high-freq edges. 827 828 Example: 829 ```python 830 from vedo import * 831 pic = Picture(vedo.dataurl+'images/dog.jpg').bw() 832 show(pic, pic.clone().enhance(), N=2, mode='image', zoom='tight') 833 ``` 834 ![](https://vedo.embl.es/images/feats/pict_enhance.png) 835 """ 836 img = self._data 837 scalarRange = img.GetPointData().GetScalars().GetRange() 838 839 cast = vtk.vtkImageCast() 840 cast.SetInputData(img) 841 cast.SetOutputScalarTypeToDouble() 842 cast.Update() 843 844 laplacian = vtk.vtkImageLaplacian() 845 laplacian.SetInputData(cast.GetOutput()) 846 laplacian.SetDimensionality(2) 847 laplacian.Update() 848 849 subtr = vtk.vtkImageMathematics() 850 subtr.SetInputData(0, cast.GetOutput()) 851 subtr.SetInputData(1, laplacian.GetOutput()) 852 subtr.SetOperationToSubtract() 853 subtr.Update() 854 855 color_window = scalarRange[1] - scalarRange[0] 856 color_level = color_window / 2 857 original_color = vtk.vtkImageMapToWindowLevelColors() 858 original_color.SetWindow(color_window) 859 original_color.SetLevel(color_level) 860 original_color.SetInputData(subtr.GetOutput()) 861 original_color.Update() 862 self._update(original_color.GetOutput()) 863 864 self.pipeline = utils.OperationNode("enhance", parents=[self], c="#f28482") 865 return self
Enhance a b&w picture using the laplacian, enhancing high-freq edges.
Example:
from vedo import * pic = Picture(vedo.dataurl+'images/dog.jpg').bw() show(pic, pic.clone().enhance(), N=2, mode='image', zoom='tight')
867 def fft(self, mode="magnitude", logscale=12, center=True): 868 """ 869 Fast Fourier transform of a picture. 870 871 Arguments: 872 logscale : (float) 873 if non-zero, take the logarithm of the intensity and scale it by this factor. 874 mode : (str) 875 either [magnitude, real, imaginary, complex], compute the point array data accordingly. 876 center : (bool) 877 shift constant zero-frequency to the center of the image for display. 878 (FFT converts spatial images into frequency space, but puts the zero frequency at the origin) 879 """ 880 ffti = vtk.vtkImageFFT() 881 ffti.SetInputData(self._data) 882 ffti.Update() 883 884 if "mag" in mode: 885 mag = vtk.vtkImageMagnitude() 886 mag.SetInputData(ffti.GetOutput()) 887 mag.Update() 888 out = mag.GetOutput() 889 elif "real" in mode: 890 erf = vtk.vtkImageExtractComponents() 891 erf.SetInputData(ffti.GetOutput()) 892 erf.SetComponents(0) 893 erf.Update() 894 out = erf.GetOutput() 895 elif "imaginary" in mode: 896 eimf = vtk.vtkImageExtractComponents() 897 eimf.SetInputData(ffti.GetOutput()) 898 eimf.SetComponents(1) 899 eimf.Update() 900 out = eimf.GetOutput() 901 elif "complex" in mode: 902 out = ffti.GetOutput() 903 else: 904 colors.printc("Error in fft(): unknown mode", mode) 905 raise RuntimeError() 906 907 if center: 908 center = vtk.vtkImageFourierCenter() 909 center.SetInputData(out) 910 center.Update() 911 out = center.GetOutput() 912 913 if "complex" not in mode: 914 if logscale: 915 ils = vtk.vtkImageLogarithmicScale() 916 ils.SetInputData(out) 917 ils.SetConstant(logscale) 918 ils.Update() 919 out = ils.GetOutput() 920 921 pic = Picture(out) 922 pic.pipeline = utils.OperationNode("FFT", parents=[self], c="#f28482") 923 return pic
Fast Fourier transform of a picture.
Arguments:
- logscale : (float) if non-zero, take the logarithm of the intensity and scale it by this factor.
- mode : (str) either [magnitude, real, imaginary, complex], compute the point array data accordingly.
- center : (bool) shift constant zero-frequency to the center of the image for display. (FFT converts spatial images into frequency space, but puts the zero frequency at the origin)
925 def rfft(self, mode="magnitude"): 926 """Reverse Fast Fourier transform of a picture.""" 927 928 ffti = vtk.vtkImageRFFT() 929 ffti.SetInputData(self._data) 930 ffti.Update() 931 932 if "mag" in mode: 933 mag = vtk.vtkImageMagnitude() 934 mag.SetInputData(ffti.GetOutput()) 935 mag.Update() 936 out = mag.GetOutput() 937 elif "real" in mode: 938 erf = vtk.vtkImageExtractComponents() 939 erf.SetInputData(ffti.GetOutput()) 940 erf.SetComponents(0) 941 erf.Update() 942 out = erf.GetOutput() 943 elif "imaginary" in mode: 944 eimf = vtk.vtkImageExtractComponents() 945 eimf.SetInputData(ffti.GetOutput()) 946 eimf.SetComponents(1) 947 eimf.Update() 948 out = eimf.GetOutput() 949 elif "complex" in mode: 950 out = ffti.GetOutput() 951 else: 952 colors.printc("Error in rfft(): unknown mode", mode) 953 raise RuntimeError() 954 955 pic = Picture(out) 956 pic.pipeline = utils.OperationNode("rFFT", parents=[self], c="#f28482") 957 return pic
Reverse Fast Fourier transform of a picture.
959 def filterpass(self, lowcutoff=None, highcutoff=None, order=3): 960 """ 961 Low-pass and high-pass filtering become trivial in the frequency domain. 962 A portion of the pixels/voxels are simply masked or attenuated. 963 This function applies a high pass Butterworth filter that attenuates the 964 frequency domain image with the function 965 966 The gradual attenuation of the filter is important. 967 A simple high-pass filter would simply mask a set of pixels in the frequency domain, 968 but the abrupt transition would cause a ringing effect in the spatial domain. 969 970 Arguments: 971 lowcutoff : (list) 972 the cutoff frequencies 973 highcutoff : (list) 974 the cutoff frequencies 975 order : (int) 976 order determines sharpness of the cutoff curve 977 """ 978 # https://lorensen.github.io/VTKExamples/site/Cxx/ImageProcessing/IdealHighPass 979 fft = vtk.vtkImageFFT() 980 fft.SetInputData(self._data) 981 fft.Update() 982 out = fft.GetOutput() 983 984 if highcutoff: 985 blp = vtk.vtkImageButterworthLowPass() 986 blp.SetInputData(out) 987 blp.SetCutOff(highcutoff) 988 blp.SetOrder(order) 989 blp.Update() 990 out = blp.GetOutput() 991 992 if lowcutoff: 993 bhp = vtk.vtkImageButterworthHighPass() 994 bhp.SetInputData(out) 995 bhp.SetCutOff(lowcutoff) 996 bhp.SetOrder(order) 997 bhp.Update() 998 out = bhp.GetOutput() 999 1000 rfft = vtk.vtkImageRFFT() 1001 rfft.SetInputData(out) 1002 rfft.Update() 1003 1004 ecomp = vtk.vtkImageExtractComponents() 1005 ecomp.SetInputData(rfft.GetOutput()) 1006 ecomp.SetComponents(0) 1007 ecomp.Update() 1008 1009 caster = vtk.vtkImageCast() 1010 caster.SetOutputScalarTypeToUnsignedChar() 1011 caster.SetInputData(ecomp.GetOutput()) 1012 caster.Update() 1013 self._update(caster.GetOutput()) 1014 self.pipeline = utils.OperationNode("filterpass", parents=[self], c="#f28482") 1015 return self
Low-pass and high-pass filtering become trivial in the frequency domain. A portion of the pixels/voxels are simply masked or attenuated. This function applies a high pass Butterworth filter that attenuates the frequency domain image with the function
The gradual attenuation of the filter is important. A simple high-pass filter would simply mask a set of pixels in the frequency domain, but the abrupt transition would cause a ringing effect in the spatial domain.
Arguments:
- lowcutoff : (list) the cutoff frequencies
- highcutoff : (list) the cutoff frequencies
- order : (int) order determines sharpness of the cutoff curve
1017 def blend(self, pic, alpha1=0.5, alpha2=0.5): 1018 """ 1019 Take L, LA, RGB, or RGBA images as input and blends 1020 them according to the alpha values and/or the opacity setting for each input. 1021 """ 1022 blf = vtk.vtkImageBlend() 1023 blf.AddInputData(self._data) 1024 blf.AddInputData(pic.inputdata()) 1025 blf.SetOpacity(0, alpha1) 1026 blf.SetOpacity(1, alpha2) 1027 blf.SetBlendModeToNormal() 1028 blf.Update() 1029 self._update(blf.GetOutput()) 1030 self.pipeline = utils.OperationNode("blend", parents=[self, pic], c="#f28482") 1031 return self
Take L, LA, RGB, or RGBA images as input and blends them according to the alpha values and/or the opacity setting for each input.
1033 def warp( 1034 self, 1035 source_pts=(), 1036 target_pts=(), 1037 transform=None, 1038 sigma=1, 1039 mirroring=False, 1040 bc="w", 1041 alpha=1, 1042 ): 1043 """ 1044 Warp an image using thin-plate splines. 1045 1046 Arguments: 1047 source_pts : (list) 1048 source points 1049 target_pts : (list) 1050 target points 1051 transform : (vtkTransform) 1052 a vtkTransform object can be supplied 1053 sigma : (float), optional 1054 stiffness of the interpolation 1055 mirroring : (bool) 1056 fill the margins with a reflection of the original image 1057 bc : (color) 1058 fill the margins with a solid color 1059 alpha : (float) 1060 opacity of the filled margins 1061 """ 1062 if transform is None: 1063 # source and target must be filled 1064 transform = vtk.vtkThinPlateSplineTransform() 1065 transform.SetBasisToR2LogR() 1066 1067 parents = [self] 1068 if isinstance(source_pts, vedo.Points): 1069 parents.append(source_pts) 1070 source_pts = source_pts.points() 1071 if isinstance(target_pts, vedo.Points): 1072 parents.append(target_pts) 1073 target_pts = target_pts.points() 1074 1075 ns = len(source_pts) 1076 nt = len(target_pts) 1077 if ns != nt: 1078 colors.printc("Error in picture.warp(): #source != #target points", ns, nt, c="r") 1079 raise RuntimeError() 1080 1081 ptsou = vtk.vtkPoints() 1082 ptsou.SetNumberOfPoints(ns) 1083 1084 pttar = vtk.vtkPoints() 1085 pttar.SetNumberOfPoints(nt) 1086 1087 for i in range(ns): 1088 p = source_pts[i] 1089 ptsou.SetPoint(i, [p[0], p[1], 0]) 1090 p = target_pts[i] 1091 pttar.SetPoint(i, [p[0], p[1], 0]) 1092 1093 transform.SetSigma(sigma) 1094 transform.SetSourceLandmarks(pttar) 1095 transform.SetTargetLandmarks(ptsou) 1096 else: 1097 # ignore source and target 1098 pass 1099 1100 reslice = vtk.vtkImageReslice() 1101 reslice.SetInputData(self._data) 1102 reslice.SetOutputDimensionality(2) 1103 reslice.SetResliceTransform(transform) 1104 reslice.SetInterpolationModeToCubic() 1105 reslice.SetMirror(mirroring) 1106 c = np.array(colors.get_color(bc)) * 255 1107 reslice.SetBackgroundColor([c[0], c[1], c[2], alpha * 255]) 1108 reslice.Update() 1109 self.transform = transform 1110 self._update(reslice.GetOutput()) 1111 self.pipeline = utils.OperationNode("warp", parents=parents, c="#f28482") 1112 return self
Warp an image using thin-plate splines.
Arguments:
- source_pts : (list) source points
- target_pts : (list) target points
- transform : (vtkTransform) a vtkTransform object can be supplied
- sigma : (float), optional stiffness of the interpolation
- mirroring : (bool) fill the margins with a reflection of the original image
- bc : (color) fill the margins with a solid color
- alpha : (float) opacity of the filled margins
1114 def invert(self): 1115 """ 1116 Return an inverted picture (inverted in each color channel). 1117 """ 1118 rgb = self.tonumpy() 1119 data = 255 - np.array(rgb) 1120 self._update(_get_img(data)) 1121 self.pipeline = utils.OperationNode("invert", parents=[self], c="#f28482") 1122 return self
Return an inverted picture (inverted in each color channel).
1124 def binarize(self, threshold=None, invert=False): 1125 """ 1126 Return a new Picture where pixel above threshold are set to 255 1127 and pixels below are set to 0. 1128 1129 Arguments: 1130 threshold : (float) 1131 input threshold value 1132 invert : (bool) 1133 invert threshold direction 1134 1135 Example: 1136 ```python 1137 from vedo import Picture, show 1138 pic1 = Picture("https://aws.glamour.es/prod/designs/v1/assets/620x459/547577.jpg") 1139 pic2 = pic1.clone().invert() 1140 pic3 = pic1.clone().binarize() 1141 show(pic1, pic2, pic3, N=3, bg="blue9").close() 1142 ``` 1143 ![](https://vedo.embl.es/images/feats/pict_binarize.png) 1144 """ 1145 rgb = self.tonumpy() 1146 if rgb.ndim == 3: 1147 intensity = np.sum(rgb, axis=2) / 3 1148 else: 1149 intensity = rgb 1150 1151 if threshold is None: 1152 vmin, vmax = np.min(intensity), np.max(intensity) 1153 threshold = (vmax + vmin) / 2 1154 1155 data = np.zeros_like(intensity).astype(np.uint8) 1156 mask = np.where(intensity > threshold) 1157 if invert: 1158 data += 255 1159 data[mask] = 0 1160 else: 1161 data[mask] = 255 1162 1163 self._update(_get_img(data, flip=True)) 1164 1165 self.pipeline = utils.OperationNode( 1166 "binarize", comment=f"threshold={threshold}", parents=[self], c="#f28482" 1167 ) 1168 return self
Return a new Picture where pixel above threshold are set to 255 and pixels below are set to 0.
Arguments:
- threshold : (float) input threshold value
- invert : (bool) invert threshold direction
Example:
from vedo import Picture, show pic1 = Picture("https://aws.glamour.es/prod/designs/v1/assets/620x459/547577.jpg") pic2 = pic1.clone().invert() pic3 = pic1.clone().binarize() show(pic1, pic2, pic3, N=3, bg="blue9").close()
1170 def threshold(self, value=None, flip=False): 1171 """ 1172 Create a polygonal Mesh from a Picture by filling regions with pixels 1173 luminosity above a specified value. 1174 1175 Arguments: 1176 value : (float) 1177 The default is None, e.i. 1/3 of the scalar range. 1178 flip: (bool) 1179 Flip polygon orientations 1180 1181 Returns: 1182 A polygonal mesh. 1183 """ 1184 mgf = vtk.vtkImageMagnitude() 1185 mgf.SetInputData(self._data) 1186 mgf.Update() 1187 msq = vtk.vtkMarchingSquares() 1188 msq.SetInputData(mgf.GetOutput()) 1189 if value is None: 1190 r0, r1 = self._data.GetScalarRange() 1191 value = r0 + (r1 - r0) / 3 1192 msq.SetValue(0, value) 1193 msq.Update() 1194 if flip: 1195 rs = vtk.vtkReverseSense() 1196 rs.SetInputData(msq.GetOutput()) 1197 rs.ReverseCellsOn() 1198 rs.ReverseNormalsOff() 1199 rs.Update() 1200 output = rs.GetOutput() 1201 else: 1202 output = msq.GetOutput() 1203 ctr = vtk.vtkContourTriangulator() 1204 ctr.SetInputData(output) 1205 ctr.Update() 1206 out = vedo.Mesh(ctr.GetOutput(), c="k").bc("t").lighting("off") 1207 1208 out.pipeline = utils.OperationNode( 1209 "threshold", comment=f"{value: .2f}", parents=[self], c="#f28482:#e9c46a" 1210 ) 1211 return out
Create a polygonal Mesh from a Picture by filling regions with pixels luminosity above a specified value.
Arguments:
- value : (float) The default is None, e.i. 1/3 of the scalar range.
- flip: (bool) Flip polygon orientations
Returns:
A polygonal mesh.
1213 def tomesh(self): 1214 """ 1215 Convert an image to polygonal data (quads), 1216 with each polygon vertex assigned a RGBA value. 1217 """ 1218 dims = self._data.GetDimensions() 1219 gr = vedo.shapes.Grid(s=dims[:2], res=(dims[0] - 1, dims[1] - 1)) 1220 gr.pos(int(dims[0] / 2), int(dims[1] / 2)).pickable(True).wireframe(False).lw(0) 1221 self._data.GetPointData().GetScalars().SetName("RGBA") 1222 gr.inputdata().GetPointData().AddArray(self._data.GetPointData().GetScalars()) 1223 gr.inputdata().GetPointData().SetActiveScalars("RGBA") 1224 gr.mapper().SetArrayName("RGBA") 1225 gr.mapper().SetScalarModeToUsePointData() 1226 gr.mapper().ScalarVisibilityOn() 1227 gr.name = self.name 1228 gr.filename = self.filename 1229 gr.pipeline = utils.OperationNode("tomesh", parents=[self], c="#f28482:#e9c46a") 1230 return gr
Convert an image to polygonal data (quads), with each polygon vertex assigned a RGBA value.
1232 def tonumpy(self): 1233 """ 1234 Get read-write access to pixels of a Picture object as a numpy array. 1235 Note that the shape is (nrofchannels, nx, ny). 1236 1237 When you set values in the output image, you don't want numpy to reallocate the array 1238 but instead set values in the existing array, so use the [:] operator. 1239 Example: arr[:] = arr - 15 1240 1241 If the array is modified call: 1242 ``picture.modified()`` 1243 when all your modifications are completed. 1244 """ 1245 nx, ny, _ = self._data.GetDimensions() 1246 nchan = self._data.GetPointData().GetScalars().GetNumberOfComponents() 1247 narray = utils.vtk2numpy(self._data.GetPointData().GetScalars()).reshape(ny, nx, nchan) 1248 narray = np.flip(narray, axis=0).astype(np.uint8) 1249 return narray.squeeze()
Get read-write access to pixels of a Picture object as a numpy array. Note that the shape is (nrofchannels, nx, ny).
When you set values in the output image, you don't want numpy to reallocate the array but instead set values in the existing array, so use the [:] operator. Example: arr[:] = arr - 15
If the array is modified call:
picture.modified()
when all your modifications are completed.
1251 def rectangle(self, xspan, yspan, c="green5", alpha=1): 1252 """Draw a rectangle box on top of current image. Units are pixels. 1253 1254 Example: 1255 ```python 1256 import vedo 1257 pic = vedo.Picture(vedo.dataurl+"images/dog.jpg") 1258 pic.rectangle([100,300], [100,200], c='green4', alpha=0.7) 1259 pic.line([100,100],[400,500], lw=2, alpha=1) 1260 pic.triangle([250,300], [100,300], [200,400], c='blue5') 1261 vedo.show(pic, axes=1).close() 1262 ``` 1263 ![](https://vedo.embl.es/images/feats/pict_drawon.png) 1264 """ 1265 x1, x2 = xspan 1266 y1, y2 = yspan 1267 1268 r, g, b = vedo.colors.get_color(c) 1269 c = np.array([r, g, b]) * 255 1270 c = c.astype(np.uint8) 1271 1272 alpha = min(alpha, 1) 1273 if alpha <= 0: 1274 return self 1275 alpha2 = alpha 1276 alpha1 = 1 - alpha 1277 1278 nx, ny = self.dimensions() 1279 if x2 > nx: 1280 x2 = nx - 1 1281 if y2 > ny: 1282 y2 = ny - 1 1283 1284 nchan = self.channels() 1285 narrayA = self.tonumpy() 1286 1287 canvas_source = vtk.vtkImageCanvasSource2D() 1288 canvas_source.SetExtent(0, nx - 1, 0, ny - 1, 0, 0) 1289 canvas_source.SetScalarTypeToUnsignedChar() 1290 canvas_source.SetNumberOfScalarComponents(nchan) 1291 canvas_source.SetDrawColor(255, 255, 255) 1292 canvas_source.FillBox(x1, x2, y1, y2) 1293 canvas_source.Update() 1294 image_data = canvas_source.GetOutput() 1295 1296 vscals = image_data.GetPointData().GetScalars() 1297 narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny, nx, nchan) 1298 narrayB = np.flip(narrayB, axis=0) 1299 narrayC = np.where(narrayB < 255, narrayA, alpha1 * narrayA + alpha2 * c) 1300 self._update(_get_img(narrayC)) 1301 self.pipeline = utils.OperationNode("rectangle", parents=[self], c="#f28482") 1302 return self
Draw a rectangle box on top of current image. Units are pixels.
Example:
import vedo pic = vedo.Picture(vedo.dataurl+"images/dog.jpg") pic.rectangle([100,300], [100,200], c='green4', alpha=0.7) pic.line([100,100],[400,500], lw=2, alpha=1) pic.triangle([250,300], [100,300], [200,400], c='blue5') vedo.show(pic, axes=1).close()
1304 def line(self, p1, p2, lw=2, c="k2", alpha=1): 1305 """Draw a line on top of current image. Units are pixels.""" 1306 x1, x2 = p1 1307 y1, y2 = p2 1308 1309 r, g, b = vedo.colors.get_color(c) 1310 c = np.array([r, g, b]) * 255 1311 c = c.astype(np.uint8) 1312 1313 alpha = min(alpha, 1) 1314 if alpha <= 0: 1315 return self 1316 alpha2 = alpha 1317 alpha1 = 1 - alpha 1318 1319 nx, ny = self.dimensions() 1320 if x2 > nx: 1321 x2 = nx - 1 1322 if y2 > ny: 1323 y2 = ny - 1 1324 1325 nchan = self.channels() 1326 narrayA = self.tonumpy() 1327 1328 canvas_source = vtk.vtkImageCanvasSource2D() 1329 canvas_source.SetExtent(0, nx - 1, 0, ny - 1, 0, 0) 1330 canvas_source.SetScalarTypeToUnsignedChar() 1331 canvas_source.SetNumberOfScalarComponents(nchan) 1332 canvas_source.SetDrawColor(255, 255, 255) 1333 canvas_source.FillTube(x1, x2, y1, y2, lw) 1334 canvas_source.Update() 1335 image_data = canvas_source.GetOutput() 1336 1337 vscals = image_data.GetPointData().GetScalars() 1338 narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny, nx, nchan) 1339 narrayB = np.flip(narrayB, axis=0) 1340 narrayC = np.where(narrayB < 255, narrayA, alpha1 * narrayA + alpha2 * c) 1341 self._update(_get_img(narrayC)) 1342 self.pipeline = utils.OperationNode("line", parents=[self], c="#f28482") 1343 return self
Draw a line on top of current image. Units are pixels.
1345 def triangle(self, p1, p2, p3, c="red3", alpha=1): 1346 """Draw a triangle on top of current image. Units are pixels.""" 1347 x1, y1 = p1 1348 x2, y2 = p2 1349 x3, y3 = p3 1350 1351 r, g, b = vedo.colors.get_color(c) 1352 c = np.array([r, g, b]) * 255 1353 c = c.astype(np.uint8) 1354 1355 alpha = min(alpha, 1) 1356 if alpha <= 0: 1357 return self 1358 alpha2 = alpha 1359 alpha1 = 1 - alpha 1360 1361 nx, ny = self.dimensions() 1362 x1 = min(x1, nx) 1363 x2 = min(x2, nx) 1364 x3 = min(x3, nx) 1365 1366 y1 = min(y1, ny) 1367 y2 = min(y2, ny) 1368 y3 = min(y3, ny) 1369 1370 nchan = self.channels() 1371 narrayA = self.tonumpy() 1372 1373 canvas_source = vtk.vtkImageCanvasSource2D() 1374 canvas_source.SetExtent(0, nx - 1, 0, ny - 1, 0, 0) 1375 canvas_source.SetScalarTypeToUnsignedChar() 1376 canvas_source.SetNumberOfScalarComponents(nchan) 1377 canvas_source.SetDrawColor(255, 255, 255) 1378 canvas_source.FillTriangle(x1, y1, x2, y2, x3, y3) 1379 canvas_source.Update() 1380 image_data = canvas_source.GetOutput() 1381 1382 vscals = image_data.GetPointData().GetScalars() 1383 narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny, nx, nchan) 1384 narrayB = np.flip(narrayB, axis=0) 1385 narrayC = np.where(narrayB < 255, narrayA, alpha1 * narrayA + alpha2 * c) 1386 self._update(_get_img(narrayC)) 1387 self.pipeline = utils.OperationNode("triangle", parents=[self], c="#f28482") 1388 return self
Draw a triangle on top of current image. Units are pixels.
1390 def add_text( 1391 self, 1392 txt, 1393 # pos=(0, 0), # TODO 1394 width=400, 1395 height=200, 1396 alpha=1, 1397 c="black", 1398 bg=None, 1399 alpha_bg=1, 1400 font="Theemim", 1401 dpi=200, 1402 justify="bottom-left", 1403 ): 1404 """Add text to an image.""" 1405 1406 tp = vtk.vtkTextProperty() 1407 tp.BoldOff() 1408 tp.FrameOff() 1409 tp.SetColor(colors.get_color(c)) 1410 tp.SetJustificationToLeft() 1411 if "top" in justify: 1412 tp.SetVerticalJustificationToTop() 1413 if "bottom" in justify: 1414 tp.SetVerticalJustificationToBottom() 1415 if "cent" in justify: 1416 tp.SetVerticalJustificationToCentered() 1417 tp.SetJustificationToCentered() 1418 if "left" in justify: 1419 tp.SetJustificationToLeft() 1420 if "right" in justify: 1421 tp.SetJustificationToRight() 1422 1423 if font.lower() == "courier": tp.SetFontFamilyToCourier() 1424 elif font.lower() == "times": tp.SetFontFamilyToTimes() 1425 elif font.lower() == "arial": tp.SetFontFamilyToArial() 1426 else: 1427 tp.SetFontFamily(vtk.VTK_FONT_FILE) 1428 tp.SetFontFile(utils.get_font_path(font)) 1429 1430 if bg: 1431 bgcol = colors.get_color(bg) 1432 tp.SetBackgroundColor(bgcol) 1433 tp.SetBackgroundOpacity(alpha_bg) 1434 tp.SetFrameColor(bgcol) 1435 tp.FrameOn() 1436 1437 tr = vtk.vtkTextRenderer() 1438 # GetConstrainedFontSize (const vtkUnicodeString &str, 1439 # vtkTextProperty(*tprop, int targetWidth, int targetHeight, int dpi) 1440 fs = tr.GetConstrainedFontSize(txt, tp, width, height, dpi) 1441 tp.SetFontSize(fs) 1442 1443 img = vtk.vtkImageData() 1444 # img.SetOrigin(*pos,1) 1445 tr.RenderString(tp, txt, img, [width, height], dpi) 1446 # RenderString (vtkTextProperty *tprop, const vtkStdString &str, 1447 # vtkImageData *data, int textDims[2], int dpi, int backend=Default) 1448 1449 blf = vtk.vtkImageBlend() 1450 blf.AddInputData(self._data) 1451 blf.AddInputData(img) 1452 blf.SetOpacity(0, 1) 1453 blf.SetOpacity(1, alpha) 1454 blf.SetBlendModeToNormal() 1455 blf.Update() 1456 1457 self._update(blf.GetOutput()) 1458 self.pipeline = utils.OperationNode( 1459 "add_text", comment=f"{txt}", parents=[self], c="#f28482" 1460 ) 1461 return self
Add text to an image.
1463 def modified(self): 1464 """Use in conjunction with ``tonumpy()`` to update any modifications to the picture array""" 1465 self._data.GetPointData().GetScalars().Modified() 1466 return self
Use in conjunction with tonumpy()
to update any modifications to the picture array
1468 def write(self, filename): 1469 """Write picture to file as png or jpg.""" 1470 vedo.file_io.write(self._data, filename) 1471 self.pipeline = utils.OperationNode( 1472 "write", comment=filename[:15], parents=[self], c="#8a817c", shape="cylinder" 1473 ) 1474 return self
Write picture to file as png or jpg.
144class Picture2D(vedo.BaseActor2D): 145 """ 146 Embed an image as a static 2D image in the canvas. 147 """ 148 149 def __init__(self, fig, pos=(0, 0), scale=1, ontop=False, padding=1, justify=""): 150 """ 151 Embed an image as a static 2D image in the canvas. 152 153 Arguments: 154 fig : Picture, matplotlib.Figure, matplotlib.pyplot, vtkImageData 155 the input image 156 pos : (list) 157 2D (x,y) position in range [0,1], 158 [0,0] being the bottom-left corner 159 scale : (float) 160 apply a scaling factor to the image 161 ontop : (bool) 162 keep image on top or not 163 padding : (int) 164 an internal padding space as a fraction of size 165 (matplotlib only) 166 justify : (str) 167 define the anchor point ("top-left", "top-center", ...) 168 """ 169 vedo.BaseActor2D.__init__(self) 170 # print("input type:", fig.__class__) 171 172 self.array = None 173 174 if utils.is_sequence(fig): 175 self.array = fig 176 self._data = _get_img(self.array) 177 178 elif isinstance(fig, Picture): 179 self._data = fig.inputdata() 180 181 elif isinstance(fig, vtk.vtkImageData): 182 assert fig.GetDimensions()[2] == 1, "Cannot create an Picture2D from Volume" 183 self._data = fig 184 185 elif isinstance(fig, str): 186 self._data = _get_img(fig) 187 self.filename = fig 188 189 elif "matplotlib" in str(fig.__class__): 190 if hasattr(fig, "gcf"): 191 fig = fig.gcf() 192 fig.tight_layout(pad=padding) 193 fig.canvas.draw() 194 195 # self.array = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) 196 # self.array = self.array.reshape(fig.canvas.get_width_height()[::-1] + (3,)) 197 width, height = fig.get_size_inches() * fig.get_dpi() 198 self.array = np.frombuffer(fig.canvas.buffer_rgba(), dtype=np.uint8).reshape( 199 (int(height), int(width), 4) 200 ) 201 self.array = self.array[:, :, :3] 202 203 self._data = _get_img(self.array) 204 205 ############# 206 if scale != 1: 207 newsize = np.array(self._data.GetDimensions()[:2]) * scale 208 newsize = newsize.astype(int) 209 rsz = vtk.vtkImageResize() 210 rsz.SetInputData(self._data) 211 rsz.SetResizeMethodToOutputDimensions() 212 rsz.SetOutputDimensions(newsize[0], newsize[1], 1) 213 rsz.Update() 214 self._data = rsz.GetOutput() 215 216 if padding: 217 pass # TODO 218 219 if justify: 220 self._data, pos = _set_justification(self._data, justify) 221 else: 222 self._data, pos = _set_justification(self._data, pos) 223 224 self._mapper = vtk.vtkImageMapper() 225 # self._mapper.RenderToRectangleOn() # NOT good because of aliasing 226 self._mapper.SetInputData(self._data) 227 self._mapper.SetColorWindow(255) 228 self._mapper.SetColorLevel(127.5) 229 self.SetMapper(self._mapper) 230 231 self.GetPositionCoordinate().SetCoordinateSystem(3) 232 self.SetPosition(pos) 233 234 if ontop: 235 self.GetProperty().SetDisplayLocationToForeground() 236 else: 237 self.GetProperty().SetDisplayLocationToBackground() 238 239 @property 240 def shape(self): 241 return np.array(self._data.GetDimensions()[:2]).astype(int)
Embed an image as a static 2D image in the canvas.
149 def __init__(self, fig, pos=(0, 0), scale=1, ontop=False, padding=1, justify=""): 150 """ 151 Embed an image as a static 2D image in the canvas. 152 153 Arguments: 154 fig : Picture, matplotlib.Figure, matplotlib.pyplot, vtkImageData 155 the input image 156 pos : (list) 157 2D (x,y) position in range [0,1], 158 [0,0] being the bottom-left corner 159 scale : (float) 160 apply a scaling factor to the image 161 ontop : (bool) 162 keep image on top or not 163 padding : (int) 164 an internal padding space as a fraction of size 165 (matplotlib only) 166 justify : (str) 167 define the anchor point ("top-left", "top-center", ...) 168 """ 169 vedo.BaseActor2D.__init__(self) 170 # print("input type:", fig.__class__) 171 172 self.array = None 173 174 if utils.is_sequence(fig): 175 self.array = fig 176 self._data = _get_img(self.array) 177 178 elif isinstance(fig, Picture): 179 self._data = fig.inputdata() 180 181 elif isinstance(fig, vtk.vtkImageData): 182 assert fig.GetDimensions()[2] == 1, "Cannot create an Picture2D from Volume" 183 self._data = fig 184 185 elif isinstance(fig, str): 186 self._data = _get_img(fig) 187 self.filename = fig 188 189 elif "matplotlib" in str(fig.__class__): 190 if hasattr(fig, "gcf"): 191 fig = fig.gcf() 192 fig.tight_layout(pad=padding) 193 fig.canvas.draw() 194 195 # self.array = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) 196 # self.array = self.array.reshape(fig.canvas.get_width_height()[::-1] + (3,)) 197 width, height = fig.get_size_inches() * fig.get_dpi() 198 self.array = np.frombuffer(fig.canvas.buffer_rgba(), dtype=np.uint8).reshape( 199 (int(height), int(width), 4) 200 ) 201 self.array = self.array[:, :, :3] 202 203 self._data = _get_img(self.array) 204 205 ############# 206 if scale != 1: 207 newsize = np.array(self._data.GetDimensions()[:2]) * scale 208 newsize = newsize.astype(int) 209 rsz = vtk.vtkImageResize() 210 rsz.SetInputData(self._data) 211 rsz.SetResizeMethodToOutputDimensions() 212 rsz.SetOutputDimensions(newsize[0], newsize[1], 1) 213 rsz.Update() 214 self._data = rsz.GetOutput() 215 216 if padding: 217 pass # TODO 218 219 if justify: 220 self._data, pos = _set_justification(self._data, justify) 221 else: 222 self._data, pos = _set_justification(self._data, pos) 223 224 self._mapper = vtk.vtkImageMapper() 225 # self._mapper.RenderToRectangleOn() # NOT good because of aliasing 226 self._mapper.SetInputData(self._data) 227 self._mapper.SetColorWindow(255) 228 self._mapper.SetColorLevel(127.5) 229 self.SetMapper(self._mapper) 230 231 self.GetPositionCoordinate().SetCoordinateSystem(3) 232 self.SetPosition(pos) 233 234 if ontop: 235 self.GetProperty().SetDisplayLocationToForeground() 236 else: 237 self.GetProperty().SetDisplayLocationToBackground()
Embed an image as a static 2D image in the canvas.
Arguments:
- fig : Picture, matplotlib.Figure, matplotlib.pyplot, vtkImageData the input image
- pos : (list) 2D (x,y) position in range [0,1], [0,0] being the bottom-left corner
- scale : (float) apply a scaling factor to the image
- ontop : (bool) keep image on top or not
- padding : (int) an internal padding space as a fraction of size (matplotlib only)
- justify : (str) define the anchor point ("top-left", "top-center", ...)