text: import numpy as np
  import vtk
  import vedo
  import vedo.colors as colors
  import vedo.utils as utils
  from vtk.util.numpy_support import numpy_to_vtk
  
  __doc__ = (
      """
  Submodule extending the ``vtkImageActor`` object functionality.
  """
      + vedo.docs._defs
  )
  
  __all__ = ["Picture"]
  
  
  #################################################
  class Picture(vtk.vtkImageActor, vedo.base.Base3DProp):   
      """
      Derived class of ``vtkImageActor``. Used to represent 2D pictures.
      Can be instantiated with a path file name or with a numpy array.
  
      Use `Picture.shape` to access the number of pixels in x and y.
  
      |rotateImage| |rotateImage.py|_
      
      :param list channels: only select these specific rgba channels (useful to remove alpha)
      :param bool flip: flip xy axis convention (when input is a numpy array)
      """    
      def __init__(self, obj=None, channels=(), flip=False):
          vtk.vtkImageActor.__init__(self)
          vedo.base.Base3DProp.__init__(self)
  
  
          if utils.isSequence(obj) and len(obj): # passing array
              obj = np.asarray(obj)
  
              if len(obj.shape) == 3: # has shape (nx,ny, ncolor_alpha_chan)
                  iac = vtk.vtkImageAppendComponents()
                  nchan = obj.shape[2] # get number of channels in inputimage (L/LA/RGB/RGBA)
                  for i in range(nchan):
                      if flip:
                          arr = np.flip(np.flip(obj[:,:,i], 0), 0).ravel()
                      else:
                          arr = np.flip(obj[:,:,i], 0).ravel()
                      varb = numpy_to_vtk(arr, deep=True, array_type=vtk.VTK_UNSIGNED_CHAR)
                      varb.SetName("RGBA")
                      imgb = vtk.vtkImageData()
                      imgb.SetDimensions(obj.shape[1], obj.shape[0], 1)
                      imgb.GetPointData().AddArray(varb)
                      imgb.GetPointData().SetActiveScalars("RGBA")
                      iac.AddInputData(imgb)
                  iac.Update()
                  img = iac.GetOutput()
  
              elif len(obj.shape) == 2: # black and white
                  if flip:
                      arr = np.flip(obj[:,:], 0).ravel()
                  else:
                      arr = obj.ravel()
                  varb = numpy_to_vtk(arr, deep=True, array_type=vtk.VTK_UNSIGNED_CHAR)
                  varb.SetName("RGBA")
                  img = vtk.vtkImageData()
                  img.SetDimensions(obj.shape[1], obj.shape[0], 1)
                  img.GetPointData().AddArray(varb)
                  img.GetPointData().SetActiveScalars("RGBA")
  
          elif isinstance(obj, vtk.vtkImageData):
              img = obj
  
          elif isinstance(obj, str):
              if "https://" in obj:
                  obj = vedo.io.download(obj, verbose=False)
  
              fname = obj.lower()
              if fname.endswith(".png"):
                  picr = vtk.vtkPNGReader()
              elif fname.endswith(".jpg") or fname.endswith(".jpeg"):
                  picr = vtk.vtkJPEGReader()
              elif fname.endswith(".bmp"):
                  picr = vtk.vtkBMPReader()
              elif fname.endswith(".tif") or fname.endswith(".tiff"):
                  picr = vtk.vtkTIFFReader()
                  picr.SetOrientationType(vedo.settings.tiffOrientationType)
              else:
                  colors.printc("Cannot understand picture format", obj, c='r')
                  return
              picr.SetFileName(obj)
              self.filename = obj
              picr.Update()
              img = picr.GetOutput()
  
          else:
              img = vtk.vtkImageData()
  
          # select channels
          nchans = len(channels)
          if nchans and img.GetPointData().GetScalars().GetNumberOfComponents() > nchans:
              pec = vtk.vtkImageExtractComponents()
              pec.SetInputData(img)
              if nchans == 3:
                  pec.SetComponents(channels[0], channels[1], channels[2])
              elif nchans == 2:
                  pec.SetComponents(channels[0], channels[1])
              elif nchans == 1:
                  pec.SetComponents(channels[0])
              pec.Update()
              img = pec.GetOutput()
  
          self._data = img
          self.SetInputData(img)
  
          sx,sy,_ = img.GetDimensions()
          self.shape = np.array([sx,sy])
  
          self._mapper = self.GetMapper()
  
  
      def inputdata(self):
          """Return the underlying ``vtkImagaData`` object."""
          return self._data
      
      def dimensions(self):
          nx, ny, _ = self._data.GetDimensions()
          return np.array([nx, ny])
  
      def _update(self, data):
          """Overwrite the Picture data mesh with a new data."""
          self._data = data
          self._mapper.SetInputData(data)
          self._mapper.Modified()
          return self
  
      def clone(self, transform=False):
          """Return an exact copy of the input Picture.
          If transform is True, it is given the same scaling and position."""
          img = vtk.vtkImageData()
          img.DeepCopy(self._data)
          pic = Picture(img)
          if transform:
              # assign the same transformation to the copy
              pic.SetOrigin(self.GetOrigin())
              pic.SetScale(self.GetScale())
              pic.SetOrientation(self.GetOrientation())
              pic.SetPosition(self.GetPosition())
          return pic
  
      def extent(self, ext=None):
          """
          Get or set the physical extent that the picture spans.
          Format is ext=[minx, maxx, miny, maxy].
          """
          if ext is None:
              return self._data.GetExtent()
          self._data.SetExtent(ext[0],ext[1],ext[2],ext[3],0,0)
          self._mapper.Modified()
          return self
      
      def text(self, txt,
                     pos=(0,0,0),
                     s=1,
                     c=None,
                     alpha=1,
                     bg=None,
                     font="Theemim",
                     dpi=500,
                     justify="bottom-left",
          ):
          """Build an image from a string."""
  
          if c is None: # automatic black or white
              if vedo.settings.plotter_instance and vedo.settings.plotter_instance.renderer:
                  c = (0.9, 0.9, 0.9)
                  if np.sum(vedo.settings.plotter_instance.renderer.GetBackground()) > 1.5:
                      c = (0.1, 0.1, 0.1)
              else:
                  c = (0.3, 0.3, 0.3)
  
          r = vtk.vtkTextRenderer()
          img = vtk.vtkImageData()
  
          tp = vtk.vtkTextProperty()
          tp.BoldOff()
          tp.SetColor(colors.getColor(c))
          tp.SetJustificationToLeft()
          if "top" in justify:
              tp.SetVerticalJustificationToTop()
          if "bottom" in justify:
              tp.SetVerticalJustificationToBottom()
          if "cent" in justify:
              tp.SetVerticalJustificationToCentered()
              tp.SetJustificationToCentered()
          if "left" in justify:
              tp.SetJustificationToLeft()
          if "right" in justify:
              tp.SetJustificationToRight()
  
          if font.lower() == "courier": tp.SetFontFamilyToCourier()
          elif font.lower() == "times": tp.SetFontFamilyToTimes()
          elif font.lower() == "arial": tp.SetFontFamilyToArial()
          else:
              tp.SetFontFamily(vtk.VTK_FONT_FILE)
              tp.SetFontFile(utils.getFontPath(font))
  
          if bg:
              bgcol = colors.getColor(bg)
              tp.SetBackgroundColor(bgcol)
              tp.SetBackgroundOpacity(alpha * 0.5)
              tp.SetFrameColor(bgcol)
              tp.FrameOn()
  
          #GetConstrainedFontSize (const vtkUnicodeString &str,
          # vtkTextProperty *tprop, int targetWidth, int targetHeight, int dpi)
          fs = r.GetConstrainedFontSize(txt, tp, 900, 1000, dpi)
          tp.SetFontSize(fs)
  
          r.RenderString(tp, txt, img, [1,1], dpi)
          # RenderString (vtkTextProperty *tprop, const vtkStdString &str,
          #   vtkImageData *data, int textDims[2], int dpi, int backend=Default)
  
          self.SetInputData(img)
          self.GetMapper().Modified()
  
          self.SetPosition(pos)
          x0, x1 = self.xbounds()
          if x1 != x0:
              sc = s/(x1-x0)
              self.SetScale(sc,sc,sc)
          return self
  
  
      def alpha(self, a=None):
          """Set/get picture's transparency."""
          if a is not None:
              self.GetProperty().SetOpacity(a)
              return self
          else:
              return self.GetProperty().GetOpacity()
  
      def level(self, value=None):
          """Get/Set the image color level (brightness)."""
          if value is None:
              return self.GetProperty().GetColorLevel()
          self.GetProperty().SetColorLevel(value)
          return self
  
      def window(self, value=None):
          """Get/Set the image color window (contrast)."""
          if value is None:
              return self.GetProperty().GetColorWindow()
          self.GetProperty().SetColorWindow(value)
          return self
  
  
      def crop(self, top=None, bottom=None, right=None, left=None, pixels=False):
          """Crop picture.
  
          :param float top: fraction to crop from the top margin
          :param float bottom: fraction to crop from the bottom margin
          :param float left: fraction to crop from the left margin
          :param float right: fraction to crop from the right margin
          :param bool pixels: units are pixels
          """
          extractVOI = vtk.vtkExtractVOI()
          extractVOI.SetInputData(self._data)
          extractVOI.IncludeBoundaryOn()
  
          d = self.GetInput().GetDimensions()
          if pixels:
              extractVOI.SetVOI(right, d[0]-left, bottom, d[1]-top, 0, 0)
          else:
              bx0, bx1, by0, by1 = 0, d[0]-1, 0, d[1]-1
              if left is not None:   bx0 = int((d[0]-1)*left)
              if right is not None:  bx1 = int((d[0]-1)*(1-right))
              if bottom is not None: by0 = int((d[1]-1)*bottom)
              if top is not None:    by1 = int((d[1]-1)*(1-top))
              extractVOI.SetVOI(bx0, bx1, by0, by1, 0, 0)
          extractVOI.Update()
          return self._update(extractVOI.GetOutput())
  
      def pad(self, pixels=10, value=255):
          """
          Add the specified number of pixels at the picture borders.
          Pixels can be a list formatted as [left,right,bottom,top].
          
          Parameters
          ----------
          pixels : int,list , optional
              number of pixels to be added (or a list of length 4). The default is 10.
          value : int, optional
              intensity value (gray-scale color) of the padding. The default is 255.
          """
          x0,x1,y0,y1,_z0,_z1 = self._data.GetExtent()
          pf = vtk.vtkImageConstantPad()
          pf.SetInputData(self._data)
          pf.SetConstant(value)
          if utils.isSequence(pixels):
              pf.SetOutputWholeExtent(x0-pixels[0],x1+pixels[1],
                                      y0-pixels[2],y1+pixels[3], 0,0)
          else:
              pf.SetOutputWholeExtent(x0-pixels,x1+pixels, y0-pixels,y1+pixels, 0,0)
          pf.Update()
          img = pf.GetOutput()
          return self._update(img)
  
  
      def tiling(self, nx=4, ny=4, shift=(0,0)):
          """
          Generate a tiling from the current picture by mirroring and repeating it.
  
          Parameters
          ----------
          nx :  float, optional
              number of repeats along x. The default is 4.
          ny : float, optional
              number of repeats along x. The default is 4.
          shift : list, optional
              shift in x and y in pixels. The default is 4.
          """
          x0,x1,y0,y1,z0,z1 = self._data.GetExtent()
          constantPad = vtk.vtkImageMirrorPad()
          constantPad.SetInputData(self._data)
          constantPad.SetOutputWholeExtent(int(x0+shift[0]+0.5), int(x1*nx+shift[0]+0.5),
                                           int(y0+shift[1]+0.5), int(y1*ny+shift[1]+0.5), z0,z1)
          constantPad.Update()
          return Picture(constantPad.GetOutput())
  
  
      def append(self, pictures, axis='z', preserveExtents=False):
          """
          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.
  
          :param int,str axis: axis expanded to hold the multiple images.
          :param bool preserveExtents: 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.
  
          .. code-block:: python
  
              from vedo import Picture, dataurl
              pic = Picture(dataurl+'dog.jpg').pad()
              pic.append([pic,pic,pic], axis='y')
              pic.append([pic,pic,pic,pic], axis='x')
              pic.show(axes=1)
          """
          ima = vtk.vtkImageAppend()
          ima.SetInputData(self._data)
          if not utils.isSequence(pictures):
              pictures = [pictures]
          for p in pictures:
              if isinstance(p, vtk.vtkImageData):
                  ima.AddInputData(p)
              else:
                  ima.AddInputData(p._data)
          ima.SetPreserveExtents(preserveExtents)
          if axis   == "x":
              axis = 0
          elif axis == "y":
              axis = 1
          ima.SetAppendAxis(axis)
          ima.Update()
          return self._update(ima.GetOutput())
      
  
      def resize(self, newsize):
          """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.
  
          :param list,float newsize: shape of picture as [npx, npy], or as a fraction.
          """
          old_dims = np.array(self._data.GetDimensions())
  
          if not utils.isSequence(newsize):
              newsize = (old_dims * newsize + 0.5).astype(int)
  
          if not newsize[1]:
              ar = old_dims[1]/old_dims[0]
              newsize = [newsize[0], int(newsize[0]*ar+0.5)]
          if not newsize[0]:
              ar = old_dims[0]/old_dims[1]
              newsize = [int(newsize[1]*ar+0.5), newsize[1]]
          newsize = [newsize[0], newsize[1], old_dims[2]]
  
          rsz = vtk.vtkImageResize()
          rsz.SetInputData(self._data)
          rsz.SetResizeMethodToOutputDimensions()
          rsz.SetOutputDimensions(newsize)
          rsz.Update()
          out = rsz.GetOutput()
          out.SetSpacing(1,1,1)
          return self._update(out)
  
      def mirror(self, axis="x"):
          """Mirror picture along x or y axis."""
          ff = vtk.vtkImageFlip()
          ff.SetInputData(self.inputdata())
          if axis.lower() == "x":
              ff.SetFilteredAxis(0)
          elif axis.lower() == "y":
              ff.SetFilteredAxis(1)
          else:
              colors.printc("Error in mirror(): mirror must be set to x or y.", c='r')
              raise RuntimeError()
          ff.Update()
          return self._update(ff.GetOutput())
      
      def rotate(self, angle, center=(), scale=1, mirroring=False, bc='w', alpha=1):
          """
          Rotate an image by an angle (anticlockwise).
  
          Parameters
          ----------
          angle : float
              rotation angle in degrees.
          center: list
              center of rotation (x,y) in pixels.
          """        
          bounds = self.bounds()
          pc = [0,0,0]
          if center:
              pc[0] = center[0]
              pc[1] = center[1]
          else:
              pc[0] = (bounds[1] + bounds[0]) / 2.0
              pc[1] = (bounds[3] + bounds[2]) / 2.0
          pc[2] = (bounds[5] + bounds[4]) / 2.0
    
          transform = vtk.vtkTransform()
          transform.Translate(pc)
          transform.RotateWXYZ(-angle, 0, 0, 1)
          transform.Scale(1/scale,1/scale,1)
          transform.Translate(-pc[0], -pc[1], -pc[2])
  
          reslice = vtk.vtkImageReslice()
          reslice.SetMirror(mirroring)
          c = np.array(colors.getColor(bc))*255
          reslice.SetBackgroundColor([c[0],c[1],c[2], alpha*255])
          reslice.SetInputData(self._data)
          reslice.SetResliceTransform(transform)
          reslice.SetOutputDimensionality(2)
          reslice.SetInterpolationModeToCubic()
          reslice.SetOutputSpacing(self._data.GetSpacing())
          reslice.SetOutputOrigin(self._data.GetOrigin())
          reslice.SetOutputExtent(self._data.GetExtent())
          reslice.Update()
          return self._update(reslice.GetOutput())
  
      def select(self, component):
          """Select one single component of the rgb image"""
          ec = vtk.vtkImageExtractComponents()
          ec.SetInputData(self._data)
          ec.SetComponents(component)
          ec.Update()
          return Picture(ec.GetOutput())
      
      def bw(self):
          """Make it black and white"""
          n = self._data.GetPointData().GetNumberOfComponents()        
          if n==4:
              ecr = vtk.vtkImageExtractComponents()
              ecr.SetInputData(self._data)
              ecr.SetComponents(0,1,2)
              ecr.Update()
              img = ecr.GetOutput()
          else:
              img = self._data
          
          ecr = vtk.vtkImageLuminance()
          ecr.SetInputData(img)
          ecr.Update()
          return self._update(ecr.GetOutput())
  
      def smooth(self, sigma=3, radius=None):
          """
          Smooth a Picture with Gaussian kernel.
  
          Parameters
          ----------
          sigma : int, optional
              number of sigmas in pixel units. The default is 3.
          radius : TYPE, optional
              how far out the gaussian kernel will go before being clamped to zero. The default is None.
          """
          gsf = vtk.vtkImageGaussianSmooth()
          gsf.SetDimensionality(2)
          gsf.SetInputData(self._data)
          if radius is not None:
              if utils.isSequence(radius):
                  gsf.SetRadiusFactors(radius[0],radius[1])
              else:
                  gsf.SetRadiusFactor(radius)
          
          if utils.isSequence(sigma):
              gsf.SetStandardDeviations(sigma[0], sigma[1])
          else:
              gsf.SetStandardDeviation(sigma)
          gsf.Update()
          return self._update(gsf.GetOutput())
  
      def median(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.
          """
          medf = vtk.vtkImageHybridMedian2D()
          medf.SetInputData(self._data)
          medf.Update()        
          return self._update(medf.GetOutput())
      
      def enhance(self):
          """
          Enhance a b&w picture using the laplacian, enhancing high-freq edges.
          
          Example:
              
              .. code-block:: python
              
                  import vedo
                  p = vedo.Picture(vedo.dataurl+'images/dog.jpg').bw()
                  vedo.show(p, p.clone().enhance(), N=2, mode='image')
          """       
          img = self._data
          scalarRange = img.GetPointData().GetScalars().GetRange()
          
          cast = vtk.vtkImageCast()
          cast.SetInputData(img)
          cast.SetOutputScalarTypeToDouble()
          cast.Update()
         
          laplacian = vtk.vtkImageLaplacian()
          laplacian.SetInputData(cast.GetOutput())
          laplacian.SetDimensionality(2)
          laplacian.Update()
          
          subtr = vtk.vtkImageMathematics()
          subtr.SetInputData(0, cast.GetOutput())
          subtr.SetInputData(1, laplacian.GetOutput())
          subtr.SetOperationToSubtract()
          subtr.Update()    
          
          colorWindow = scalarRange[1] - scalarRange[0]
          colorLevel = colorWindow / 2    
          originalColor = vtk.vtkImageMapToWindowLevelColors()
          originalColor.SetWindow(colorWindow)
          originalColor.SetLevel(colorLevel)
          originalColor.SetInputData(subtr.GetOutput())
          originalColor.Update()        
          return self._update(originalColor.GetOutput())
  
      def fft(self, mode='magnitude', logscale=12, center=True):
          """Fast Fourier transform of a picture.
  
          :param float logscale: if non-zero, take the logarithm of the
              intensity and scale it by this factor.
  
          :param str mode: either [magnitude, real, imaginary, complex], compute the
              point array data accordingly.
          :param bool center: 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)
          """
          ffti = vtk.vtkImageFFT()
          ffti.SetInputData(self._data)
          ffti.Update()
  
          if 'mag' in mode:
              mag = vtk.vtkImageMagnitude()
              mag.SetInputData(ffti.GetOutput())
              mag.Update()
              out = mag.GetOutput()
          elif 'real' in mode:
              extractRealFilter = vtk.vtkImageExtractComponents()
              extractRealFilter.SetInputData(ffti.GetOutput())
              extractRealFilter.SetComponents(0)
              extractRealFilter.Update()
              out = extractRealFilter.GetOutput()
          elif 'imaginary' in mode:
              extractImgFilter = vtk.vtkImageExtractComponents()
              extractImgFilter.SetInputData(ffti.GetOutput())
              extractImgFilter.SetComponents(1)
              extractImgFilter.Update()
              out = extractImgFilter.GetOutput()
          elif 'complex' in mode:
              out = ffti.GetOutput()
          else:
              colors.printc("Error in fft(): unknown mode", mode)
              raise RuntimeError()
  
          if center:
              center = vtk.vtkImageFourierCenter()
              center.SetInputData(out)
              center.Update()
              out = center.GetOutput()
  
          if 'complex' not in mode:
              if logscale:
                  ils = vtk.vtkImageLogarithmicScale()
                  ils.SetInputData(out)
                  ils.SetConstant(logscale)
                  ils.Update()
                  out = ils.GetOutput()
  
          return Picture(out)
  
      def rfft(self, mode='magnitude'):
          """Reverse Fast Fourier transform of a picture."""
  
          ffti = vtk.vtkImageRFFT()
          ffti.SetInputData(self._data)
          ffti.Update()
  
          if 'mag' in mode:
              mag = vtk.vtkImageMagnitude()
              mag.SetInputData(ffti.GetOutput())
              mag.Update()
              out = mag.GetOutput()
          elif 'real' in mode:
              extractRealFilter = vtk.vtkImageExtractComponents()
              extractRealFilter.SetInputData(ffti.GetOutput())
              extractRealFilter.SetComponents(0)
              extractRealFilter.Update()
              out = extractRealFilter.GetOutput()
          elif 'imaginary' in mode:
              extractImgFilter = vtk.vtkImageExtractComponents()
              extractImgFilter.SetInputData(ffti.GetOutput())
              extractImgFilter.SetComponents(1)
              extractImgFilter.Update()
              out = extractImgFilter.GetOutput()
          elif 'complex' in mode:
              out = ffti.GetOutput()
          else:
              colors.printc("Error in rfft(): unknown mode", mode)
              raise RuntimeError()
  
          return Picture(out)
  
      def frequencyPassFilter(self, lowcutoff=None, highcutoff=None, order=3):
          """
          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
  
          |G_Of_Omega|
  
          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.
  
          :param list lowcutoff:  the cutoff frequencies
          :param list highcutoff: the cutoff frequencies
          :param int order: order determines sharpness of the cutoff curve
          """
          #https://lorensen.github.io/VTKExamples/site/Cxx/ImageProcessing/IdealHighPass
          fft = vtk.vtkImageFFT()
          fft.SetInputData(self._data)
          fft.Update()
          out = fft.GetOutput()
  
          if highcutoff:
              butterworthLowPass = vtk.vtkImageButterworthLowPass()
              butterworthLowPass.SetInputData(out)
              butterworthLowPass.SetCutOff(highcutoff)
              butterworthLowPass.SetOrder(order)
              butterworthLowPass.Update()
              out = butterworthLowPass.GetOutput()
  
          if lowcutoff:
              butterworthHighPass = vtk.vtkImageButterworthHighPass()
              butterworthHighPass.SetInputData(out)
              butterworthHighPass.SetCutOff(lowcutoff)
              butterworthHighPass.SetOrder(order)
              butterworthHighPass.Update()
              out = butterworthHighPass.GetOutput()
  
          butterworthRfft = vtk.vtkImageRFFT()
          butterworthRfft.SetInputData(out)
          butterworthRfft.Update()
  
          butterworthReal = vtk.vtkImageExtractComponents()
          butterworthReal.SetInputData(butterworthRfft.GetOutput())
          butterworthReal.SetComponents(0)
          butterworthReal.Update()
          
          caster = vtk.vtkImageCast()
          caster. SetOutputScalarTypeToUnsignedChar()
          caster.SetInputData(butterworthReal.GetOutput())
          caster.Update()
          return self._update(caster.GetOutput())
      
      
      def blend(self, pic, alpha1=0.5, alpha2=0.5):
          """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.
          """
          blf = vtk.vtkImageBlend()
          blf.AddInputData(self._data)
          blf.AddInputData(pic._data)
          blf.SetOpacity(0, alpha1)
          blf.SetOpacity(1, alpha2)
          blf.SetBlendModeToNormal()
          blf.Update()
          return self._update(blf.GetOutput())
      
      
      def warp(self, sourcePts=(), targetPts=(), transform=None, sigma=1, 
               mirroring=False, bc='w', alpha=1):
          """
          Warp an image using thin-plate splines.
  
          Parameters
          ----------
          sourcePts : list, optional
              source points. 
          targetPts : list, optional
              target points.
          transform : TYPE, optional
              a vtkTransform object can be supplied. The default is None.
          sigma : float, optional
              stiffness of the interpolation. The default is 1.
          mirroring : TYPE, optional
              fill the margins with a reflection of the original image. The default is False.
          bc : TYPE, optional
              fill the margins with a solid color. The default is 'w'.
          alpha : TYPE, optional
              opacity of the filled margins. The default is 1.
          """
          if transform is None:
              # source and target must be filled
              transform = vtk.vtkThinPlateSplineTransform()
              transform.SetBasisToR2LogR()
              if isinstance(sourcePts, vedo.Points):
                  sourcePts = sourcePts.points()
              if isinstance(targetPts, vedo.Points):
                  targetPts = targetPts.points()
      
              ns = len(sourcePts)
              nt = len(targetPts)
              if ns != nt:
                  colors.printc("Error in picture.warp(): #source != #target points", ns, nt, c='r')
                  raise RuntimeError()
                  
              ptsou = vtk.vtkPoints()
              ptsou.SetNumberOfPoints(ns)   
      
              pttar = vtk.vtkPoints()
              pttar.SetNumberOfPoints(nt)
              
              for i in range(ns):
                  p = sourcePts[i]
                  ptsou.SetPoint(i, [p[0],p[1],0])
                  p = targetPts[i]
                  pttar.SetPoint(i, [p[0],p[1],0])
  
              transform.SetSigma(sigma)
              transform.SetSourceLandmarks(pttar)
              transform.SetTargetLandmarks(ptsou)
          else:
              # ignore source and target
              pass
          
          reslice = vtk.vtkImageReslice()
          reslice.SetInputData(self._data)
          reslice.SetOutputDimensionality(2)
          reslice.SetResliceTransform(transform)
          reslice.SetInterpolationModeToCubic()
          reslice.SetMirror(mirroring)
          c = np.array(colors.getColor(bc))*255
          reslice.SetBackgroundColor([c[0],c[1],c[2], alpha*255])
          reslice.Update()
          self.transform = transform
          return self._update(reslice.GetOutput())
      
  
      def threshold(self, value=None, flip=False):
          """
          Create a polygonal Mesh from a Picture by filling regions with pixels
          luminosity above a specified value.
  
          Parameters
          ----------
          value : float, optional
              The default is None, e.i. 1/3 of the scalar range.
  
          flip: bool, optional
              Flip polygon orientations
  
          Returns
          -------
          Mesh
              A polygonal mesh.
          """
          mgf = vtk.vtkImageMagnitude()
          mgf.SetInputData(self._data)
          mgf.Update()
          msq = vtk.vtkMarchingSquares()
          msq.SetInputData(mgf.GetOutput())
          if value is None:
              r0,r1 = self._data.GetScalarRange()
              value = r0 + (r1-r0)/3
          msq.SetValue(0, value)
          msq.Update()
          if flip:
              rs = vtk.vtkReverseSense()
              rs.SetInputData(msq.GetOutput())
              rs.ReverseCellsOn()
              rs.ReverseNormalsOff()
              rs.Update()
              output = rs.GetOutput()
          else:
              output = msq.GetOutput()
          ctr = vtk.vtkContourTriangulator()
          ctr.SetInputData(output)
          ctr.Update()
          return vedo.Mesh(ctr.GetOutput(), c='k').bc('t').lighting('off')
  
  
      def tomesh(self):
          """
          Convert an image to polygonal data (quads),
          with each polygon vertex assigned a RGBA value.
          """
          dims = self._data.GetDimensions()
          gr = vedo.shapes.Grid(sx=dims[0], sy=dims[1], resx=dims[0]-1, resy=dims[1]-1)
          gr.pos(int(dims[0]/2), int(dims[1]/2)).pickable(True).wireframe(False).lw(0)
          self._data.GetPointData().GetScalars().SetName("RGBA")
          gr.inputdata().GetPointData().AddArray(self._data.GetPointData().GetScalars())
          gr.inputdata().GetPointData().SetActiveScalars("RGBA")
          gr._mapper.SetArrayName("RGBA")
          gr._mapper.SetScalarModeToUsePointData()
          # gr._mapper.SetColorModeToDirectScalars()
          gr._mapper.ScalarVisibilityOn()
          gr.name = self.name
          gr.filename = self.filename
          return gr
  
      def tonumpy(self):
          """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.
          """
          nx, ny, _ = self._data.GetDimensions()
          nchan = self._data.GetPointData().GetScalars().GetNumberOfComponents()
          narray = utils.vtk2numpy(self._data.GetPointData().GetScalars()).reshape(ny,nx,nchan)
          narray = np.flip(narray, axis=0)
          return narray
      
      def modified(self):
          """Use in conjunction with ``tonumpy()`` to update any modifications to the picture array"""
          self._data.GetPointData().GetScalars().Modified()
          return self
      
      def write(self, filename):
          """Write picture to file as png or jpg."""
          vedo.io.write(self._data, filename)
          return self
  
  
  
  