Module vedo.picture
Submodule to work with common format images
Expand source code
import numpy as np
import vedo
import vedo.colors as colors
import vedo.utils as utils
import vtk
__doc__ = """
Submodule to work with common format images
.. image:: https://vedo.embl.es/images/basic/rotateImage.png
"""
__all__ = ["Picture"]
#################################################
def _get_img(obj, flip=False):
# get vtkImageData from numpy array
obj = np.asarray(obj)
if obj.ndim == 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()
arr = np.clip(arr, 0, 255)
varb = utils.numpy2vtk(arr, dtype=np.uint8, name="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 obj.ndim == 2: # black and white
if flip:
arr = np.flip(obj[:,:], 0).ravel()
else:
arr = obj.ravel()
arr = np.clip(arr, 0, 255)
varb = utils.numpy2vtk(arr, dtype=np.uint8, name="RGBA")
img = vtk.vtkImageData()
img.SetDimensions(obj.shape[1], obj.shape[0], 1)
img.GetPointData().AddArray(varb)
img.GetPointData().SetActiveScalars("RGBA")
return img
#################################################
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.
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.
|rotateImage| |rotateImage.py|_
:param int,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=3, flip=False):
vtk.vtkImageActor.__init__(self)
vedo.base.Base3DProp.__init__(self)
if utils.isSequence(obj) and len(obj): # passing array
img = _get_img(obj, flip)
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
if isinstance(channels, int):
channels = list(range(channels))
nchans = len(channels)
n = img.GetPointData().GetScalars().GetNumberOfComponents()
if nchans and n > nchans:
pec = vtk.vtkImageExtractComponents()
pec.SetInputData(img)
if nchans == 4:
pec.SetComponents(channels[0], channels[1], channels[2], channels[3])
elif 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 channels(self):
return self._data.GetPointData().GetScalars().GetNumberOfComponents()
def _update(self, data):
"""Overwrite the Picture data mesh with a new data."""
self._data = data
self._mapper.SetInputData(data)
self._mapper.Modified()
nx, ny, _ = self._data.GetDimensions()
self.shape = np.array([nx,ny])
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()
else:
self._data.SetExtent(ext[0],ext[1],ext[2],ext[3],0,0)
self._mapper.Modified()
return self
def alpha(self, a=None):
"""Set/get picture's transparency in the rendering scene."""
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) in the rendering scene."""
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) in the rendering scene."""
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 tile(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. Same as flip()."""
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 flip(self, axis="y"):
"""Mirror picture along x or y axis. Same as mirror()."""
return self.mirror(axis=axis)
def rotate(self, angle, center=(), scale=1, mirroring=False, bc='w', alpha=1):
"""
Rotate by the specified 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 using luminance calibration"""
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 filterpass(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 invert(self):
"""
Return an inverted picture (inverted in each color channel).
"""
rgb = self.tonumpy()
data = 255 - np.array(rgb)
return self._update(_get_img(data))
def binarize(self, thresh=None, invert=False):
"""Return a new Picture where pixel above threshold are set to 255
and pixels below are set to 0.
Parameters
----------
invert : bool, optional
Invert threshold. Default is False.
Example
-------
.. code-block:: python
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")
"""
rgb = self.tonumpy()
if rgb.ndim == 3:
intensity = np.sum(rgb, axis=2)/3
else:
intensity = rgb
if thresh is None:
vmin, vmax = np.min(intensity), np.max(intensity)
thresh = (vmax+vmin)/2
data = np.zeros_like(intensity).astype(np.uint8)
mask = np.where(intensity>thresh)
if invert:
data += 255
data[mask] = 0
else:
data[mask] = 255
return self._update(_get_img(data, flip=True))
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(s=dims[:2], res=(dims[0]-1, 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).astype(np.uint8)
return narray
def rectangle(self, xspan, yspan, c='green5', alpha=1):
"""Draw a rectangle box on top of current image. Units are pixels.
.. code-block:: python
import vedo
pic = vedo.Picture("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])
vedo.show(pic, axes=1)
"""
x1, x2 = xspan
y1, y2 = yspan
r,g,b = vedo.colors.getColor(c)
c = np.array([r,g,b]) * 255
c = c.astype(np.uint8)
if alpha>1:
alpha=1
if alpha<=0:
return self
alpha2 = alpha
alpha1 = 1-alpha
nx, ny = self.dimensions()
if x2>nx : x2=nx-1
if y2>ny : y2=ny-1
nchan = self.channels()
narrayA = self.tonumpy()
canvas_source = vtk.vtkImageCanvasSource2D()
canvas_source.SetExtent(0, nx-1, 0, ny-1, 0, 0)
canvas_source.SetScalarTypeToUnsignedChar()
canvas_source.SetNumberOfScalarComponents(nchan)
canvas_source.SetDrawColor(255,255,255)
canvas_source.FillBox(x1, x2, y1, y2)
canvas_source.Update()
image_data = canvas_source.GetOutput()
vscals = image_data.GetPointData().GetScalars()
narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny,nx,nchan)
narrayB = np.flip(narrayB, axis=0)
narrayC = np.where(narrayB < 255, narrayA, alpha1*narrayA+alpha2*c)
return self._update(_get_img(narrayC))
def line(self, p1, p2, lw=2, c='k2', alpha=1):
"""Draw a line on top of current image. Units are pixels."""
x1, x2 = p1
y1, y2 = p2
r,g,b = vedo.colors.getColor(c)
c = np.array([r,g,b]) * 255
c = c.astype(np.uint8)
if alpha>1:
alpha=1
if alpha<=0:
return self
alpha2 = alpha
alpha1 = 1-alpha
nx, ny = self.dimensions()
if x2>nx : x2=nx-1
if y2>ny : y2=ny-1
nchan = self.channels()
narrayA = self.tonumpy()
canvas_source = vtk.vtkImageCanvasSource2D()
canvas_source.SetExtent(0, nx-1, 0, ny-1, 0, 0)
canvas_source.SetScalarTypeToUnsignedChar()
canvas_source.SetNumberOfScalarComponents(nchan)
canvas_source.SetDrawColor(255,255,255)
canvas_source.FillTube(x1, x2, y1, y2, lw)
canvas_source.Update()
image_data = canvas_source.GetOutput()
vscals = image_data.GetPointData().GetScalars()
narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny,nx,nchan)
narrayB = np.flip(narrayB, axis=0)
narrayC = np.where(narrayB < 255, narrayA, alpha1*narrayA+alpha2*c)
return self._update(_get_img(narrayC))
def triangle(self, p1, p2, p3, c='red3', alpha=1):
"""Draw a triangle on top of current image. Units are pixels."""
x1, y1 = p1
x2, y2 = p2
x3, y3 = p3
r,g,b = vedo.colors.getColor(c)
c = np.array([r,g,b]) * 255
c = c.astype(np.uint8)
if alpha>1:
alpha=1
if alpha<=0:
return self
alpha2 = alpha
alpha1 = 1-alpha
nx, ny = self.dimensions()
if x1>nx : x1=nx
if x2>nx : x2=nx
if x3>nx : x3=nx
if y1>ny : y1=ny
if y2>ny : y2=ny
if y3>ny : y3=ny
nchan = self.channels()
narrayA = self.tonumpy()
canvas_source = vtk.vtkImageCanvasSource2D()
canvas_source.SetExtent(0, nx-1, 0, ny-1, 0, 0)
canvas_source.SetScalarTypeToUnsignedChar()
canvas_source.SetNumberOfScalarComponents(nchan)
canvas_source.SetDrawColor(255,255,255)
canvas_source.FillTriangle(x1, y1, x2, y2, x3, y3)
canvas_source.Update()
image_data = canvas_source.GetOutput()
vscals = image_data.GetPointData().GetScalars()
narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny,nx,nchan)
narrayB = np.flip(narrayB, axis=0)
narrayC = np.where(narrayB < 255, narrayA, alpha1*narrayA+alpha2*c)
return self._update(_get_img(narrayC))
# def circle(self, center, radius, c='k3', alpha=1): # not working
# """Draw a box."""
# x1, y1 = center
#
# r,g,b = vedo.colors.getColor(c)
# c = np.array([r,g,b]) * 255
# c = c.astype(np.uint8)
#
# if alpha>1:
# alpha=1
# if alpha<=0:
# return self
# alpha2 = alpha
# alpha1 = 1-alpha
#
# nx, ny = self.dimensions()
# nchan = self.channels()
# narrayA = self.tonumpy()
#
# canvas_source = vtk.vtkImageCanvasSource2D()
# canvas_source.SetExtent(0, nx-1, 0, ny-1, 0, 0)
# canvas_source.SetScalarTypeToUnsignedChar()
# canvas_source.SetNumberOfScalarComponents(nchan)
# canvas_source.SetDrawColor(255,255,255)
# canvas_source.DrawCircle(x1, y1, radius)
# canvas_source.Update()
# image_data = canvas_source.GetOutput()
#
# vscals = image_data.GetPointData().GetScalars()
# narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny,nx,nchan)
# narrayB = np.flip(narrayB, axis=0)
# narrayC = np.where(narrayB < 255, narrayA, alpha1*narrayA+alpha2*c)
# return self._update(_get_img(narrayC))
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.plotter_instance and vedo.plotter_instance.renderer:
c = (0.9, 0.9, 0.9)
if np.sum(vedo.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 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
Classes
class Picture (obj=None, channels=3, flip=False)
-
Derived class of
vtkImageActor
. Used to represent 2D pictures. 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.|rotateImage| |rotateImage.py|_
:param int,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)
Expand source code
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. 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. |rotateImage| |rotateImage.py|_ :param int,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=3, flip=False): vtk.vtkImageActor.__init__(self) vedo.base.Base3DProp.__init__(self) if utils.isSequence(obj) and len(obj): # passing array img = _get_img(obj, flip) 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 if isinstance(channels, int): channels = list(range(channels)) nchans = len(channels) n = img.GetPointData().GetScalars().GetNumberOfComponents() if nchans and n > nchans: pec = vtk.vtkImageExtractComponents() pec.SetInputData(img) if nchans == 4: pec.SetComponents(channels[0], channels[1], channels[2], channels[3]) elif 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 channels(self): return self._data.GetPointData().GetScalars().GetNumberOfComponents() def _update(self, data): """Overwrite the Picture data mesh with a new data.""" self._data = data self._mapper.SetInputData(data) self._mapper.Modified() nx, ny, _ = self._data.GetDimensions() self.shape = np.array([nx,ny]) 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() else: self._data.SetExtent(ext[0],ext[1],ext[2],ext[3],0,0) self._mapper.Modified() return self def alpha(self, a=None): """Set/get picture's transparency in the rendering scene.""" 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) in the rendering scene.""" 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) in the rendering scene.""" 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 tile(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. Same as flip().""" 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 flip(self, axis="y"): """Mirror picture along x or y axis. Same as mirror().""" return self.mirror(axis=axis) def rotate(self, angle, center=(), scale=1, mirroring=False, bc='w', alpha=1): """ Rotate by the specified 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 using luminance calibration""" 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 filterpass(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 invert(self): """ Return an inverted picture (inverted in each color channel). """ rgb = self.tonumpy() data = 255 - np.array(rgb) return self._update(_get_img(data)) def binarize(self, thresh=None, invert=False): """Return a new Picture where pixel above threshold are set to 255 and pixels below are set to 0. Parameters ---------- invert : bool, optional Invert threshold. Default is False. Example ------- .. code-block:: python 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") """ rgb = self.tonumpy() if rgb.ndim == 3: intensity = np.sum(rgb, axis=2)/3 else: intensity = rgb if thresh is None: vmin, vmax = np.min(intensity), np.max(intensity) thresh = (vmax+vmin)/2 data = np.zeros_like(intensity).astype(np.uint8) mask = np.where(intensity>thresh) if invert: data += 255 data[mask] = 0 else: data[mask] = 255 return self._update(_get_img(data, flip=True)) 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(s=dims[:2], res=(dims[0]-1, 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).astype(np.uint8) return narray def rectangle(self, xspan, yspan, c='green5', alpha=1): """Draw a rectangle box on top of current image. Units are pixels. .. code-block:: python import vedo pic = vedo.Picture("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]) vedo.show(pic, axes=1) """ x1, x2 = xspan y1, y2 = yspan r,g,b = vedo.colors.getColor(c) c = np.array([r,g,b]) * 255 c = c.astype(np.uint8) if alpha>1: alpha=1 if alpha<=0: return self alpha2 = alpha alpha1 = 1-alpha nx, ny = self.dimensions() if x2>nx : x2=nx-1 if y2>ny : y2=ny-1 nchan = self.channels() narrayA = self.tonumpy() canvas_source = vtk.vtkImageCanvasSource2D() canvas_source.SetExtent(0, nx-1, 0, ny-1, 0, 0) canvas_source.SetScalarTypeToUnsignedChar() canvas_source.SetNumberOfScalarComponents(nchan) canvas_source.SetDrawColor(255,255,255) canvas_source.FillBox(x1, x2, y1, y2) canvas_source.Update() image_data = canvas_source.GetOutput() vscals = image_data.GetPointData().GetScalars() narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny,nx,nchan) narrayB = np.flip(narrayB, axis=0) narrayC = np.where(narrayB < 255, narrayA, alpha1*narrayA+alpha2*c) return self._update(_get_img(narrayC)) def line(self, p1, p2, lw=2, c='k2', alpha=1): """Draw a line on top of current image. Units are pixels.""" x1, x2 = p1 y1, y2 = p2 r,g,b = vedo.colors.getColor(c) c = np.array([r,g,b]) * 255 c = c.astype(np.uint8) if alpha>1: alpha=1 if alpha<=0: return self alpha2 = alpha alpha1 = 1-alpha nx, ny = self.dimensions() if x2>nx : x2=nx-1 if y2>ny : y2=ny-1 nchan = self.channels() narrayA = self.tonumpy() canvas_source = vtk.vtkImageCanvasSource2D() canvas_source.SetExtent(0, nx-1, 0, ny-1, 0, 0) canvas_source.SetScalarTypeToUnsignedChar() canvas_source.SetNumberOfScalarComponents(nchan) canvas_source.SetDrawColor(255,255,255) canvas_source.FillTube(x1, x2, y1, y2, lw) canvas_source.Update() image_data = canvas_source.GetOutput() vscals = image_data.GetPointData().GetScalars() narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny,nx,nchan) narrayB = np.flip(narrayB, axis=0) narrayC = np.where(narrayB < 255, narrayA, alpha1*narrayA+alpha2*c) return self._update(_get_img(narrayC)) def triangle(self, p1, p2, p3, c='red3', alpha=1): """Draw a triangle on top of current image. Units are pixels.""" x1, y1 = p1 x2, y2 = p2 x3, y3 = p3 r,g,b = vedo.colors.getColor(c) c = np.array([r,g,b]) * 255 c = c.astype(np.uint8) if alpha>1: alpha=1 if alpha<=0: return self alpha2 = alpha alpha1 = 1-alpha nx, ny = self.dimensions() if x1>nx : x1=nx if x2>nx : x2=nx if x3>nx : x3=nx if y1>ny : y1=ny if y2>ny : y2=ny if y3>ny : y3=ny nchan = self.channels() narrayA = self.tonumpy() canvas_source = vtk.vtkImageCanvasSource2D() canvas_source.SetExtent(0, nx-1, 0, ny-1, 0, 0) canvas_source.SetScalarTypeToUnsignedChar() canvas_source.SetNumberOfScalarComponents(nchan) canvas_source.SetDrawColor(255,255,255) canvas_source.FillTriangle(x1, y1, x2, y2, x3, y3) canvas_source.Update() image_data = canvas_source.GetOutput() vscals = image_data.GetPointData().GetScalars() narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny,nx,nchan) narrayB = np.flip(narrayB, axis=0) narrayC = np.where(narrayB < 255, narrayA, alpha1*narrayA+alpha2*c) return self._update(_get_img(narrayC)) # def circle(self, center, radius, c='k3', alpha=1): # not working # """Draw a box.""" # x1, y1 = center # # r,g,b = vedo.colors.getColor(c) # c = np.array([r,g,b]) * 255 # c = c.astype(np.uint8) # # if alpha>1: # alpha=1 # if alpha<=0: # return self # alpha2 = alpha # alpha1 = 1-alpha # # nx, ny = self.dimensions() # nchan = self.channels() # narrayA = self.tonumpy() # # canvas_source = vtk.vtkImageCanvasSource2D() # canvas_source.SetExtent(0, nx-1, 0, ny-1, 0, 0) # canvas_source.SetScalarTypeToUnsignedChar() # canvas_source.SetNumberOfScalarComponents(nchan) # canvas_source.SetDrawColor(255,255,255) # canvas_source.DrawCircle(x1, y1, radius) # canvas_source.Update() # image_data = canvas_source.GetOutput() # # vscals = image_data.GetPointData().GetScalars() # narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny,nx,nchan) # narrayB = np.flip(narrayB, axis=0) # narrayC = np.where(narrayB < 255, narrayA, alpha1*narrayA+alpha2*c) # return self._update(_get_img(narrayC)) 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.plotter_instance and vedo.plotter_instance.renderer: c = (0.9, 0.9, 0.9) if np.sum(vedo.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 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
Ancestors
- vtkmodules.vtkRenderingCore.vtkImageActor
- vtkmodules.vtkRenderingCore.vtkImageSlice
- vtkmodules.vtkRenderingCore.vtkProp3D
- vtkmodules.vtkRenderingCore.vtkProp
- vtkmodules.vtkCommonCore.vtkObject
- vtkmodules.vtkCommonCore.vtkObjectBase
- Base3DProp
Subclasses
Methods
def alpha(self, a=None)
-
Set/get picture's transparency in the rendering scene.
Expand source code
def alpha(self, a=None): """Set/get picture's transparency in the rendering scene.""" if a is not None: self.GetProperty().SetOpacity(a) return self else: return self.GetProperty().GetOpacity()
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)
Expand source code
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 binarize(self, thresh=None, invert=False)
-
Return a new Picture where pixel above threshold are set to 255 and pixels below are set to 0.
Parameters
invert
:bool
, optional- Invert threshold. Default is False.
Example
.. code-block:: python
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")
Expand source code
def binarize(self, thresh=None, invert=False): """Return a new Picture where pixel above threshold are set to 255 and pixels below are set to 0. Parameters ---------- invert : bool, optional Invert threshold. Default is False. Example ------- .. code-block:: python 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") """ rgb = self.tonumpy() if rgb.ndim == 3: intensity = np.sum(rgb, axis=2)/3 else: intensity = rgb if thresh is None: vmin, vmax = np.min(intensity), np.max(intensity) thresh = (vmax+vmin)/2 data = np.zeros_like(intensity).astype(np.uint8) mask = np.where(intensity>thresh) if invert: data += 255 data[mask] = 0 else: data[mask] = 255 return self._update(_get_img(data, flip=True))
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.
Expand source code
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 bw(self)
-
Make it black and white using luminance calibration
Expand source code
def bw(self): """Make it black and white using luminance calibration""" 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 channels(self)
-
Expand source code
def channels(self): return self._data.GetPointData().GetScalars().GetNumberOfComponents()
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.
Expand source code
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 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
Expand source code
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 dimensions(self)
-
Expand source code
def dimensions(self): nx, ny, _ = self._data.GetDimensions() return np.array([nx, ny])
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')
Expand source code
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 extent(self, ext=None)
-
Get or set the physical extent that the picture spans. Format is ext=[minx, maxx, miny, maxy].
Expand source code
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() else: self._data.SetExtent(ext[0],ext[1],ext[2],ext[3],0,0) self._mapper.Modified() return self
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)
Expand source code
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 filterpass(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
Expand source code
def filterpass(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 flip(self, axis='y')
-
Mirror picture along x or y axis. Same as mirror().
Expand source code
def flip(self, axis="y"): """Mirror picture along x or y axis. Same as mirror().""" return self.mirror(axis=axis)
def inputdata(self)
-
Return the underlying
vtkImagaData
object.Expand source code
def inputdata(self): """Return the underlying ``vtkImagaData`` object.""" return self._data
def invert(self)
-
Return an inverted picture (inverted in each color channel).
Expand source code
def invert(self): """ Return an inverted picture (inverted in each color channel). """ rgb = self.tonumpy() data = 255 - np.array(rgb) return self._update(_get_img(data))
def level(self, value=None)
-
Get/Set the image color level (brightness) in the rendering scene.
Expand source code
def level(self, value=None): """Get/Set the image color level (brightness) in the rendering scene.""" if value is None: return self.GetProperty().GetColorLevel() self.GetProperty().SetColorLevel(value) return self
def line(self, p1, p2, lw=2, c='k2', alpha=1)
-
Draw a line on top of current image. Units are pixels.
Expand source code
def line(self, p1, p2, lw=2, c='k2', alpha=1): """Draw a line on top of current image. Units are pixels.""" x1, x2 = p1 y1, y2 = p2 r,g,b = vedo.colors.getColor(c) c = np.array([r,g,b]) * 255 c = c.astype(np.uint8) if alpha>1: alpha=1 if alpha<=0: return self alpha2 = alpha alpha1 = 1-alpha nx, ny = self.dimensions() if x2>nx : x2=nx-1 if y2>ny : y2=ny-1 nchan = self.channels() narrayA = self.tonumpy() canvas_source = vtk.vtkImageCanvasSource2D() canvas_source.SetExtent(0, nx-1, 0, ny-1, 0, 0) canvas_source.SetScalarTypeToUnsignedChar() canvas_source.SetNumberOfScalarComponents(nchan) canvas_source.SetDrawColor(255,255,255) canvas_source.FillTube(x1, x2, y1, y2, lw) canvas_source.Update() image_data = canvas_source.GetOutput() vscals = image_data.GetPointData().GetScalars() narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny,nx,nchan) narrayB = np.flip(narrayB, axis=0) narrayC = np.where(narrayB < 255, narrayA, alpha1*narrayA+alpha2*c) return self._update(_get_img(narrayC))
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.
Expand source code
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 mirror(self, axis='x')
-
Mirror picture along x or y axis. Same as flip().
Expand source code
def mirror(self, axis="x"): """Mirror picture along x or y axis. Same as flip().""" 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 modified(self)
-
Use in conjunction with
tonumpy()
to update any modifications to the picture arrayExpand source code
def modified(self): """Use in conjunction with ``tonumpy()`` to update any modifications to the picture array""" self._data.GetPointData().GetScalars().Modified() return self
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.
Expand source code
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 rectangle(self, xspan, yspan, c='green5', alpha=1)
-
Draw a rectangle box on top of current image. Units are pixels.
.. code-block:: python
import vedo pic = vedo.Picture("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]) vedo.show(pic, axes=1)
Expand source code
def rectangle(self, xspan, yspan, c='green5', alpha=1): """Draw a rectangle box on top of current image. Units are pixels. .. code-block:: python import vedo pic = vedo.Picture("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]) vedo.show(pic, axes=1) """ x1, x2 = xspan y1, y2 = yspan r,g,b = vedo.colors.getColor(c) c = np.array([r,g,b]) * 255 c = c.astype(np.uint8) if alpha>1: alpha=1 if alpha<=0: return self alpha2 = alpha alpha1 = 1-alpha nx, ny = self.dimensions() if x2>nx : x2=nx-1 if y2>ny : y2=ny-1 nchan = self.channels() narrayA = self.tonumpy() canvas_source = vtk.vtkImageCanvasSource2D() canvas_source.SetExtent(0, nx-1, 0, ny-1, 0, 0) canvas_source.SetScalarTypeToUnsignedChar() canvas_source.SetNumberOfScalarComponents(nchan) canvas_source.SetDrawColor(255,255,255) canvas_source.FillBox(x1, x2, y1, y2) canvas_source.Update() image_data = canvas_source.GetOutput() vscals = image_data.GetPointData().GetScalars() narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny,nx,nchan) narrayB = np.flip(narrayB, axis=0) narrayC = np.where(narrayB < 255, narrayA, alpha1*narrayA+alpha2*c) return self._update(_get_img(narrayC))
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.
Expand source code
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 rfft(self, mode='magnitude')
-
Reverse Fast Fourier transform of a picture.
Expand source code
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 rotate(self, angle, center=(), scale=1, mirroring=False, bc='w', alpha=1)
-
Rotate by the specified angle (anticlockwise).
Parameters
angle
:float
- rotation angle in degrees.
center
:list
- center of rotation (x,y) in pixels.
Expand source code
def rotate(self, angle, center=(), scale=1, mirroring=False, bc='w', alpha=1): """ Rotate by the specified 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
Expand source code
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 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.
Expand source code
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 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.
Expand source code
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.plotter_instance and vedo.plotter_instance.renderer: c = (0.9, 0.9, 0.9) if np.sum(vedo.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 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.
Expand source code
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 tile(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.
Expand source code
def tile(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 tomesh(self)
-
Convert an image to polygonal data (quads), with each polygon vertex assigned a RGBA value.
Expand source code
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(s=dims[:2], res=(dims[0]-1, 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.Expand source code
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).astype(np.uint8) return narray
def triangle(self, p1, p2, p3, c='red3', alpha=1)
-
Draw a triangle on top of current image. Units are pixels.
Expand source code
def triangle(self, p1, p2, p3, c='red3', alpha=1): """Draw a triangle on top of current image. Units are pixels.""" x1, y1 = p1 x2, y2 = p2 x3, y3 = p3 r,g,b = vedo.colors.getColor(c) c = np.array([r,g,b]) * 255 c = c.astype(np.uint8) if alpha>1: alpha=1 if alpha<=0: return self alpha2 = alpha alpha1 = 1-alpha nx, ny = self.dimensions() if x1>nx : x1=nx if x2>nx : x2=nx if x3>nx : x3=nx if y1>ny : y1=ny if y2>ny : y2=ny if y3>ny : y3=ny nchan = self.channels() narrayA = self.tonumpy() canvas_source = vtk.vtkImageCanvasSource2D() canvas_source.SetExtent(0, nx-1, 0, ny-1, 0, 0) canvas_source.SetScalarTypeToUnsignedChar() canvas_source.SetNumberOfScalarComponents(nchan) canvas_source.SetDrawColor(255,255,255) canvas_source.FillTriangle(x1, y1, x2, y2, x3, y3) canvas_source.Update() image_data = canvas_source.GetOutput() vscals = image_data.GetPointData().GetScalars() narrayB = vedo.utils.vtk2numpy(vscals).reshape(ny,nx,nchan) narrayB = np.flip(narrayB, axis=0) narrayC = np.where(narrayB < 255, narrayA, alpha1*narrayA+alpha2*c) return self._update(_get_img(narrayC))
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.
Expand source code
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 window(self, value=None)
-
Get/Set the image color window (contrast) in the rendering scene.
Expand source code
def window(self, value=None): """Get/Set the image color window (contrast) in the rendering scene.""" if value is None: return self.GetProperty().GetColorWindow() self.GetProperty().SetColorWindow(value) return self
def write(self, filename)
-
Write picture to file as png or jpg.
Expand source code
def write(self, filename): """Write picture to file as png or jpg.""" vedo.io.write(self._data, filename) return self
Inherited members