Removed all dependencies on scipy.
Merge branch 'make_scipy_optional' into develop
This commit is contained in:
commit
1edf1375ed
@ -23,6 +23,7 @@ pyqtgraph-0.9.9 [unreleased]
|
|||||||
- Added ViewBox.setLimits() method
|
- Added ViewBox.setLimits() method
|
||||||
- Adde ImageItem downsampling
|
- Adde ImageItem downsampling
|
||||||
- New HDF5 example for working with very large datasets
|
- New HDF5 example for working with very large datasets
|
||||||
|
- Removed all dependency on scipy
|
||||||
- Added Qt.loadUiType function for PySide
|
- Added Qt.loadUiType function for PySide
|
||||||
- Simplified Profilers; can be activated with environmental variables
|
- Simplified Profilers; can be activated with environmental variables
|
||||||
- Added Dock.raiseDock() method
|
- Added Dock.raiseDock() method
|
||||||
|
@ -11,7 +11,6 @@ a 2D plane and interpolate data along that plane to generate a slice image
|
|||||||
import initExample
|
import initExample
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import scipy
|
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ from pyqtgraph.flowchart.library.common import CtrlNode
|
|||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import scipy.ndimage
|
|
||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
|
|
||||||
@ -44,7 +43,7 @@ win.show()
|
|||||||
|
|
||||||
## generate random input data
|
## generate random input data
|
||||||
data = np.random.normal(size=(100,100))
|
data = np.random.normal(size=(100,100))
|
||||||
data = 25 * scipy.ndimage.gaussian_filter(data, (5,5))
|
data = 25 * pg.gaussianFilter(data, (5,5))
|
||||||
data += np.random.normal(size=(100,100))
|
data += np.random.normal(size=(100,100))
|
||||||
data[40:60, 40:60] += 15.0
|
data[40:60, 40:60] += 15.0
|
||||||
data[30:50, 30:50] += 15.0
|
data[30:50, 30:50] += 15.0
|
||||||
@ -90,7 +89,7 @@ class ImageViewNode(Node):
|
|||||||
## CtrlNode is just a convenience class that automatically creates its
|
## CtrlNode is just a convenience class that automatically creates its
|
||||||
## control widget based on a simple data structure.
|
## control widget based on a simple data structure.
|
||||||
class UnsharpMaskNode(CtrlNode):
|
class UnsharpMaskNode(CtrlNode):
|
||||||
"""Return the input data passed through scipy.ndimage.gaussian_filter."""
|
"""Return the input data passed through pg.gaussianFilter."""
|
||||||
nodeName = "UnsharpMask"
|
nodeName = "UnsharpMask"
|
||||||
uiTemplate = [
|
uiTemplate = [
|
||||||
('sigma', 'spin', {'value': 1.0, 'step': 1.0, 'range': [0.0, None]}),
|
('sigma', 'spin', {'value': 1.0, 'step': 1.0, 'range': [0.0, None]}),
|
||||||
@ -110,7 +109,7 @@ class UnsharpMaskNode(CtrlNode):
|
|||||||
# CtrlNode has created self.ctrls, which is a dict containing {ctrlName: widget}
|
# CtrlNode has created self.ctrls, which is a dict containing {ctrlName: widget}
|
||||||
sigma = self.ctrls['sigma'].value()
|
sigma = self.ctrls['sigma'].value()
|
||||||
strength = self.ctrls['strength'].value()
|
strength = self.ctrls['strength'].value()
|
||||||
output = dataIn - (strength * scipy.ndimage.gaussian_filter(dataIn, (sigma,sigma)))
|
output = dataIn - (strength * pg.gaussianFilter(dataIn, (sigma,sigma)))
|
||||||
return {'dataOut': output}
|
return {'dataOut': output}
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ from pyqtgraph.Qt import QtCore, QtGui
|
|||||||
import pyqtgraph.opengl as gl
|
import pyqtgraph.opengl as gl
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import scipy.ndimage as ndi
|
|
||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
w = gl.GLViewWidget()
|
w = gl.GLViewWidget()
|
||||||
@ -22,8 +21,8 @@ w.setWindowTitle('pyqtgraph example: GLImageItem')
|
|||||||
|
|
||||||
## create volume data set to slice three images from
|
## create volume data set to slice three images from
|
||||||
shape = (100,100,70)
|
shape = (100,100,70)
|
||||||
data = ndi.gaussian_filter(np.random.normal(size=shape), (4,4,4))
|
data = pg.gaussianFilter(np.random.normal(size=shape), (4,4,4))
|
||||||
data += ndi.gaussian_filter(np.random.normal(size=shape), (15,15,15))*15
|
data += pg.gaussianFilter(np.random.normal(size=shape), (15,15,15))*15
|
||||||
|
|
||||||
## slice out three planes, convert to RGBA for OpenGL texture
|
## slice out three planes, convert to RGBA for OpenGL texture
|
||||||
levels = (-0.08, 0.08)
|
levels = (-0.08, 0.08)
|
||||||
|
@ -10,7 +10,6 @@ import initExample
|
|||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
import pyqtgraph.opengl as gl
|
import pyqtgraph.opengl as gl
|
||||||
import scipy.ndimage as ndi
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
## Create a GL View widget to display data
|
## Create a GL View widget to display data
|
||||||
@ -29,7 +28,7 @@ w.addItem(g)
|
|||||||
|
|
||||||
## Simple surface plot example
|
## Simple surface plot example
|
||||||
## x, y values are not specified, so assumed to be 0:50
|
## x, y values are not specified, so assumed to be 0:50
|
||||||
z = ndi.gaussian_filter(np.random.normal(size=(50,50)), (1,1))
|
z = pg.gaussianFilter(np.random.normal(size=(50,50)), (1,1))
|
||||||
p1 = gl.GLSurfacePlotItem(z=z, shader='shaded', color=(0.5, 0.5, 1, 1))
|
p1 = gl.GLSurfacePlotItem(z=z, shader='shaded', color=(0.5, 0.5, 1, 1))
|
||||||
p1.scale(16./49., 16./49., 1.0)
|
p1.scale(16./49., 16./49., 1.0)
|
||||||
p1.translate(-18, 2, 0)
|
p1.translate(-18, 2, 0)
|
||||||
@ -46,7 +45,7 @@ w.addItem(p2)
|
|||||||
|
|
||||||
|
|
||||||
## Manually specified colors
|
## Manually specified colors
|
||||||
z = ndi.gaussian_filter(np.random.normal(size=(50,50)), (1,1))
|
z = pg.gaussianFilter(np.random.normal(size=(50,50)), (1,1))
|
||||||
x = np.linspace(-12, 12, 50)
|
x = np.linspace(-12, 12, 50)
|
||||||
y = np.linspace(-12, 12, 50)
|
y = np.linspace(-12, 12, 50)
|
||||||
colors = np.ones((50,50,4), dtype=float)
|
colors = np.ones((50,50,4), dtype=float)
|
||||||
|
@ -7,7 +7,6 @@ Use a HistogramLUTWidget to control the contrast / coloration of an image.
|
|||||||
import initExample
|
import initExample
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import scipy.ndimage as ndi
|
|
||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
|
|
||||||
@ -34,7 +33,7 @@ l.addWidget(v, 0, 0)
|
|||||||
w = pg.HistogramLUTWidget()
|
w = pg.HistogramLUTWidget()
|
||||||
l.addWidget(w, 0, 1)
|
l.addWidget(w, 0, 1)
|
||||||
|
|
||||||
data = ndi.gaussian_filter(np.random.normal(size=(256, 256)), (20, 20))
|
data = pg.gaussianFilter(np.random.normal(size=(256, 256)), (20, 20))
|
||||||
for i in range(32):
|
for i in range(32):
|
||||||
for j in range(32):
|
for j in range(32):
|
||||||
data[i*8, j*8] += .1
|
data[i*8, j*8] += .1
|
||||||
|
@ -14,7 +14,6 @@ displaying and analyzing 2D and 3D data. ImageView provides:
|
|||||||
import initExample
|
import initExample
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import scipy
|
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ win.show()
|
|||||||
win.setWindowTitle('pyqtgraph example: ImageView')
|
win.setWindowTitle('pyqtgraph example: ImageView')
|
||||||
|
|
||||||
## Create random 3D data set with noisy signals
|
## Create random 3D data set with noisy signals
|
||||||
img = scipy.ndimage.gaussian_filter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
|
img = pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
|
||||||
img = img[np.newaxis,:,:]
|
img = img[np.newaxis,:,:]
|
||||||
decay = np.exp(-np.linspace(0,0.3,100))[:,np.newaxis,np.newaxis]
|
decay = np.exp(-np.linspace(0,0.3,100))[:,np.newaxis,np.newaxis]
|
||||||
data = np.random.normal(size=(100, 200, 200))
|
data = np.random.normal(size=(100, 200, 200))
|
||||||
|
@ -13,7 +13,6 @@ import initExample ## Add path to library (just for examples; you do not need th
|
|||||||
from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
|
from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
import scipy.ndimage as ndi
|
|
||||||
import pyqtgraph.ptime as ptime
|
import pyqtgraph.ptime as ptime
|
||||||
|
|
||||||
if USE_PYSIDE:
|
if USE_PYSIDE:
|
||||||
@ -95,10 +94,11 @@ def mkData():
|
|||||||
|
|
||||||
if ui.rgbCheck.isChecked():
|
if ui.rgbCheck.isChecked():
|
||||||
data = np.random.normal(size=(frames,width,height,3), loc=loc, scale=scale)
|
data = np.random.normal(size=(frames,width,height,3), loc=loc, scale=scale)
|
||||||
data = ndi.gaussian_filter(data, (0, 6, 6, 0))
|
data = pg.gaussianFilter(data, (0, 6, 6, 0))
|
||||||
else:
|
else:
|
||||||
data = np.random.normal(size=(frames,width,height), loc=loc, scale=scale)
|
data = np.random.normal(size=(frames,width,height), loc=loc, scale=scale)
|
||||||
data = ndi.gaussian_filter(data, (0, 6, 6))
|
data = pg.gaussianFilter(data, (0, 6, 6))
|
||||||
|
pg.image(data)
|
||||||
if dtype[0] != 'float':
|
if dtype[0] != 'float':
|
||||||
data = np.clip(data, 0, mx)
|
data = np.clip(data, 0, mx)
|
||||||
data = data.astype(dt)
|
data = data.astype(dt)
|
||||||
|
@ -14,7 +14,6 @@ import initExample
|
|||||||
|
|
||||||
## This example uses a ViewBox to create a PlotWidget-like interface
|
## This example uses a ViewBox to create a PlotWidget-like interface
|
||||||
|
|
||||||
#from scipy import random
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
|
@ -7,7 +7,6 @@ the mouse.
|
|||||||
|
|
||||||
import initExample ## Add path to library (just for examples; you do not need this)
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import scipy.ndimage as ndi
|
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
from pyqtgraph.Point import Point
|
from pyqtgraph.Point import Point
|
||||||
@ -33,8 +32,8 @@ p1.setAutoVisible(y=True)
|
|||||||
|
|
||||||
#create numpy arrays
|
#create numpy arrays
|
||||||
#make the numbers large to show that the xrange shows data from 10000 to all the way 0
|
#make the numbers large to show that the xrange shows data from 10000 to all the way 0
|
||||||
data1 = 10000 + 15000 * ndi.gaussian_filter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
|
data1 = 10000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
|
||||||
data2 = 15000 + 15000 * ndi.gaussian_filter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
|
data2 = 15000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
|
||||||
|
|
||||||
p1.plot(data1, pen="r")
|
p1.plot(data1, pen="r")
|
||||||
p1.plot(data2, pen="g")
|
p1.plot(data2, pen="g")
|
||||||
|
@ -10,7 +10,6 @@ import initExample ## Add path to library (just for examples; you do not need th
|
|||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
import scipy.ndimage as ndi
|
|
||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ app = QtGui.QApplication([])
|
|||||||
frames = 200
|
frames = 200
|
||||||
data = np.random.normal(size=(frames,30,30), loc=0, scale=100)
|
data = np.random.normal(size=(frames,30,30), loc=0, scale=100)
|
||||||
data = np.concatenate([data, data], axis=0)
|
data = np.concatenate([data, data], axis=0)
|
||||||
data = ndi.gaussian_filter(data, (10, 10, 10))[frames/2:frames + frames/2]
|
data = pg.gaussianFilter(data, (10, 10, 10))[frames/2:frames + frames/2]
|
||||||
data[:, 15:16, 15:17] += 1
|
data[:, 15:16, 15:17] += 1
|
||||||
|
|
||||||
win = pg.GraphicsWindow()
|
win = pg.GraphicsWindow()
|
||||||
|
@ -4,7 +4,6 @@ from .Vector import Vector
|
|||||||
from .Transform3D import Transform3D
|
from .Transform3D import Transform3D
|
||||||
from .Vector import Vector
|
from .Vector import Vector
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import scipy.linalg
|
|
||||||
|
|
||||||
class SRTTransform3D(Transform3D):
|
class SRTTransform3D(Transform3D):
|
||||||
"""4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate
|
"""4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate
|
||||||
@ -118,11 +117,13 @@ class SRTTransform3D(Transform3D):
|
|||||||
The input matrix must be affine AND have no shear,
|
The input matrix must be affine AND have no shear,
|
||||||
otherwise the conversion will most likely fail.
|
otherwise the conversion will most likely fail.
|
||||||
"""
|
"""
|
||||||
|
import numpy.linalg
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
self.setRow(i, m.row(i))
|
self.setRow(i, m.row(i))
|
||||||
m = self.matrix().reshape(4,4)
|
m = self.matrix().reshape(4,4)
|
||||||
## translation is 4th column
|
## translation is 4th column
|
||||||
self._state['pos'] = m[:3,3]
|
self._state['pos'] = m[:3,3]
|
||||||
|
|
||||||
## scale is vector-length of first three columns
|
## scale is vector-length of first three columns
|
||||||
scale = (m[:3,:3]**2).sum(axis=0)**0.5
|
scale = (m[:3,:3]**2).sum(axis=0)**0.5
|
||||||
## see whether there is an inversion
|
## see whether there is an inversion
|
||||||
@ -132,9 +133,9 @@ class SRTTransform3D(Transform3D):
|
|||||||
self._state['scale'] = scale
|
self._state['scale'] = scale
|
||||||
|
|
||||||
## rotation axis is the eigenvector with eigenvalue=1
|
## rotation axis is the eigenvector with eigenvalue=1
|
||||||
r = m[:3, :3] / scale[:, np.newaxis]
|
r = m[:3, :3] / scale[np.newaxis, :]
|
||||||
try:
|
try:
|
||||||
evals, evecs = scipy.linalg.eig(r)
|
evals, evecs = numpy.linalg.eig(r)
|
||||||
except:
|
except:
|
||||||
print("Rotation matrix: %s" % str(r))
|
print("Rotation matrix: %s" % str(r))
|
||||||
print("Scale: %s" % str(scale))
|
print("Scale: %s" % str(scale))
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import scipy.interpolate
|
|
||||||
from .Qt import QtGui, QtCore
|
from .Qt import QtGui, QtCore
|
||||||
|
|
||||||
class ColorMap(object):
|
class ColorMap(object):
|
||||||
@ -64,8 +63,8 @@ class ColorMap(object):
|
|||||||
ignored. By default, the mode is entirely RGB.
|
ignored. By default, the mode is entirely RGB.
|
||||||
=============== ==============================================================
|
=============== ==============================================================
|
||||||
"""
|
"""
|
||||||
self.pos = pos
|
self.pos = np.array(pos)
|
||||||
self.color = color
|
self.color = np.array(color)
|
||||||
if mode is None:
|
if mode is None:
|
||||||
mode = np.ones(len(pos))
|
mode = np.ones(len(pos))
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
@ -92,15 +91,24 @@ class ColorMap(object):
|
|||||||
else:
|
else:
|
||||||
pos, color = self.getStops(mode)
|
pos, color = self.getStops(mode)
|
||||||
|
|
||||||
data = np.clip(data, pos.min(), pos.max())
|
# don't need this--np.interp takes care of it.
|
||||||
|
#data = np.clip(data, pos.min(), pos.max())
|
||||||
|
|
||||||
if not isinstance(data, np.ndarray):
|
# Interpolate
|
||||||
interp = scipy.interpolate.griddata(pos, color, np.array([data]))[0]
|
# TODO: is griddata faster?
|
||||||
|
# interp = scipy.interpolate.griddata(pos, color, data)
|
||||||
|
if np.isscalar(data):
|
||||||
|
interp = np.empty((color.shape[1],), dtype=color.dtype)
|
||||||
else:
|
else:
|
||||||
interp = scipy.interpolate.griddata(pos, color, data)
|
|
||||||
|
|
||||||
if mode == self.QCOLOR:
|
|
||||||
if not isinstance(data, np.ndarray):
|
if not isinstance(data, np.ndarray):
|
||||||
|
data = np.array(data)
|
||||||
|
interp = np.empty(data.shape + (color.shape[1],), dtype=color.dtype)
|
||||||
|
for i in range(color.shape[1]):
|
||||||
|
interp[...,i] = np.interp(data, pos, color[:,i])
|
||||||
|
|
||||||
|
# Convert to QColor if requested
|
||||||
|
if mode == self.QCOLOR:
|
||||||
|
if np.isscalar(data):
|
||||||
return QtGui.QColor(*interp)
|
return QtGui.QColor(*interp)
|
||||||
else:
|
else:
|
||||||
return [QtGui.QColor(*x) for x in interp]
|
return [QtGui.QColor(*x) for x in interp]
|
||||||
|
@ -399,7 +399,9 @@ class Profiler(object):
|
|||||||
only the initial "pyqtgraph." prefix from the module.
|
only the initial "pyqtgraph." prefix from the module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_profilers = os.environ.get("PYQTGRAPHPROFILE", "")
|
_profilers = os.environ.get("PYQTGRAPHPROFILE", None)
|
||||||
|
_profilers = _profilers.split(",") if _profilers is not None else []
|
||||||
|
|
||||||
_depth = 0
|
_depth = 0
|
||||||
_msgs = []
|
_msgs = []
|
||||||
|
|
||||||
@ -415,38 +417,36 @@ class Profiler(object):
|
|||||||
_disabledProfiler = DisabledProfiler()
|
_disabledProfiler = DisabledProfiler()
|
||||||
|
|
||||||
|
|
||||||
if _profilers:
|
def __new__(cls, msg=None, disabled='env', delayed=True):
|
||||||
_profilers = _profilers.split(",")
|
"""Optionally create a new profiler based on caller's qualname.
|
||||||
def __new__(cls, msg=None, disabled='env', delayed=True):
|
"""
|
||||||
"""Optionally create a new profiler based on caller's qualname.
|
if disabled is True or (disabled=='env' and len(cls._profilers) == 0):
|
||||||
"""
|
return cls._disabledProfiler
|
||||||
if disabled is True:
|
|
||||||
return cls._disabledProfiler
|
# determine the qualified name of the caller function
|
||||||
|
caller_frame = sys._getframe(1)
|
||||||
# determine the qualified name of the caller function
|
try:
|
||||||
caller_frame = sys._getframe(1)
|
caller_object_type = type(caller_frame.f_locals["self"])
|
||||||
try:
|
except KeyError: # we are in a regular function
|
||||||
caller_object_type = type(caller_frame.f_locals["self"])
|
qualifier = caller_frame.f_globals["__name__"].split(".", 1)[1]
|
||||||
except KeyError: # we are in a regular function
|
else: # we are in a method
|
||||||
qualifier = caller_frame.f_globals["__name__"].split(".", 1)[1]
|
qualifier = caller_object_type.__name__
|
||||||
else: # we are in a method
|
func_qualname = qualifier + "." + caller_frame.f_code.co_name
|
||||||
qualifier = caller_object_type.__name__
|
if disabled=='env' and func_qualname not in cls._profilers: # don't do anything
|
||||||
func_qualname = qualifier + "." + caller_frame.f_code.co_name
|
return cls._disabledProfiler
|
||||||
if func_qualname not in cls._profilers: # don't do anything
|
# create an actual profiling object
|
||||||
return cls._disabledProfiler
|
cls._depth += 1
|
||||||
# create an actual profiling object
|
obj = super(Profiler, cls).__new__(cls)
|
||||||
cls._depth += 1
|
obj._name = msg or func_qualname
|
||||||
obj = super(Profiler, cls).__new__(cls)
|
obj._delayed = delayed
|
||||||
obj._name = msg or func_qualname
|
obj._markCount = 0
|
||||||
obj._delayed = delayed
|
obj._finished = False
|
||||||
obj._markCount = 0
|
obj._firstTime = obj._lastTime = ptime.time()
|
||||||
obj._finished = False
|
obj._newMsg("> Entering " + obj._name)
|
||||||
obj._firstTime = obj._lastTime = ptime.time()
|
return obj
|
||||||
obj._newMsg("> Entering " + obj._name)
|
#else:
|
||||||
return obj
|
#def __new__(cls, delayed=True):
|
||||||
else:
|
#return lambda msg=None: None
|
||||||
def __new__(cls, delayed=True):
|
|
||||||
return lambda msg=None: None
|
|
||||||
|
|
||||||
def __call__(self, msg=None):
|
def __call__(self, msg=None):
|
||||||
"""Register or print a new message with timing information.
|
"""Register or print a new message with timing information.
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from ...Qt import QtCore, QtGui
|
from ...Qt import QtCore, QtGui
|
||||||
from ..Node import Node
|
from ..Node import Node
|
||||||
from scipy.signal import detrend
|
|
||||||
from scipy.ndimage import median_filter, gaussian_filter
|
|
||||||
#from ...SignalProxy import SignalProxy
|
|
||||||
from . import functions
|
from . import functions
|
||||||
|
from ... import functions as pgfn
|
||||||
from .common import *
|
from .common import *
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@ -119,7 +117,11 @@ class Median(CtrlNode):
|
|||||||
|
|
||||||
@metaArrayWrapper
|
@metaArrayWrapper
|
||||||
def processData(self, data):
|
def processData(self, data):
|
||||||
return median_filter(data, self.ctrls['n'].value())
|
try:
|
||||||
|
import scipy.ndimage
|
||||||
|
except ImportError:
|
||||||
|
raise Exception("MedianFilter node requires the package scipy.ndimage.")
|
||||||
|
return scipy.ndimage.median_filter(data, self.ctrls['n'].value())
|
||||||
|
|
||||||
class Mode(CtrlNode):
|
class Mode(CtrlNode):
|
||||||
"""Filters data by taking the mode (histogram-based) of a sliding window"""
|
"""Filters data by taking the mode (histogram-based) of a sliding window"""
|
||||||
@ -156,7 +158,11 @@ class Gaussian(CtrlNode):
|
|||||||
|
|
||||||
@metaArrayWrapper
|
@metaArrayWrapper
|
||||||
def processData(self, data):
|
def processData(self, data):
|
||||||
return gaussian_filter(data, self.ctrls['sigma'].value())
|
try:
|
||||||
|
import scipy.ndimage
|
||||||
|
except ImportError:
|
||||||
|
raise Exception("GaussianFilter node requires the package scipy.ndimage.")
|
||||||
|
return pgfn.gaussianFilter(data, self.ctrls['sigma'].value())
|
||||||
|
|
||||||
|
|
||||||
class Derivative(CtrlNode):
|
class Derivative(CtrlNode):
|
||||||
@ -189,6 +195,10 @@ class Detrend(CtrlNode):
|
|||||||
|
|
||||||
@metaArrayWrapper
|
@metaArrayWrapper
|
||||||
def processData(self, data):
|
def processData(self, data):
|
||||||
|
try:
|
||||||
|
from scipy.signal import detrend
|
||||||
|
except ImportError:
|
||||||
|
raise Exception("DetrendFilter node requires the package scipy.signal.")
|
||||||
return detrend(data)
|
return detrend(data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import scipy
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from ...metaarray import MetaArray
|
from ...metaarray import MetaArray
|
||||||
|
|
||||||
@ -47,6 +46,11 @@ def downsample(data, n, axis=0, xvals='subsample'):
|
|||||||
def applyFilter(data, b, a, padding=100, bidir=True):
|
def applyFilter(data, b, a, padding=100, bidir=True):
|
||||||
"""Apply a linear filter with coefficients a, b. Optionally pad the data before filtering
|
"""Apply a linear filter with coefficients a, b. Optionally pad the data before filtering
|
||||||
and/or run the filter in both directions."""
|
and/or run the filter in both directions."""
|
||||||
|
try:
|
||||||
|
import scipy.signal
|
||||||
|
except ImportError:
|
||||||
|
raise Exception("applyFilter() requires the package scipy.signal.")
|
||||||
|
|
||||||
d1 = data.view(np.ndarray)
|
d1 = data.view(np.ndarray)
|
||||||
|
|
||||||
if padding > 0:
|
if padding > 0:
|
||||||
@ -67,6 +71,11 @@ def applyFilter(data, b, a, padding=100, bidir=True):
|
|||||||
|
|
||||||
def besselFilter(data, cutoff, order=1, dt=None, btype='low', bidir=True):
|
def besselFilter(data, cutoff, order=1, dt=None, btype='low', bidir=True):
|
||||||
"""return data passed through bessel filter"""
|
"""return data passed through bessel filter"""
|
||||||
|
try:
|
||||||
|
import scipy.signal
|
||||||
|
except ImportError:
|
||||||
|
raise Exception("besselFilter() requires the package scipy.signal.")
|
||||||
|
|
||||||
if dt is None:
|
if dt is None:
|
||||||
try:
|
try:
|
||||||
tvals = data.xvals('Time')
|
tvals = data.xvals('Time')
|
||||||
@ -85,6 +94,11 @@ def besselFilter(data, cutoff, order=1, dt=None, btype='low', bidir=True):
|
|||||||
|
|
||||||
def butterworthFilter(data, wPass, wStop=None, gPass=2.0, gStop=20.0, order=1, dt=None, btype='low', bidir=True):
|
def butterworthFilter(data, wPass, wStop=None, gPass=2.0, gStop=20.0, order=1, dt=None, btype='low', bidir=True):
|
||||||
"""return data passed through bessel filter"""
|
"""return data passed through bessel filter"""
|
||||||
|
try:
|
||||||
|
import scipy.signal
|
||||||
|
except ImportError:
|
||||||
|
raise Exception("butterworthFilter() requires the package scipy.signal.")
|
||||||
|
|
||||||
if dt is None:
|
if dt is None:
|
||||||
try:
|
try:
|
||||||
tvals = data.xvals('Time')
|
tvals = data.xvals('Time')
|
||||||
@ -175,6 +189,11 @@ def denoise(data, radius=2, threshold=4):
|
|||||||
|
|
||||||
def adaptiveDetrend(data, x=None, threshold=3.0):
|
def adaptiveDetrend(data, x=None, threshold=3.0):
|
||||||
"""Return the signal with baseline removed. Discards outliers from baseline measurement."""
|
"""Return the signal with baseline removed. Discards outliers from baseline measurement."""
|
||||||
|
try:
|
||||||
|
import scipy.signal
|
||||||
|
except ImportError:
|
||||||
|
raise Exception("adaptiveDetrend() requires the package scipy.signal.")
|
||||||
|
|
||||||
if x is None:
|
if x is None:
|
||||||
x = data.xvals(0)
|
x = data.xvals(0)
|
||||||
|
|
||||||
|
@ -34,17 +34,6 @@ import decimal, re
|
|||||||
import ctypes
|
import ctypes
|
||||||
import sys, struct
|
import sys, struct
|
||||||
|
|
||||||
try:
|
|
||||||
import scipy.ndimage
|
|
||||||
HAVE_SCIPY = True
|
|
||||||
if getConfigOption('useWeave'):
|
|
||||||
try:
|
|
||||||
import scipy.weave
|
|
||||||
except ImportError:
|
|
||||||
setConfigOptions(useWeave=False)
|
|
||||||
except ImportError:
|
|
||||||
HAVE_SCIPY = False
|
|
||||||
|
|
||||||
from . import debug
|
from . import debug
|
||||||
|
|
||||||
def siScale(x, minVal=1e-25, allowUnicode=True):
|
def siScale(x, minVal=1e-25, allowUnicode=True):
|
||||||
@ -383,7 +372,7 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
"""
|
"""
|
||||||
Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data.
|
Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data.
|
||||||
|
|
||||||
The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. The original data is interpolated onto a new array of coordinates using scipy.ndimage.map_coordinates (see the scipy documentation for more information about this).
|
The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. The original data is interpolated onto a new array of coordinates using scipy.ndimage.map_coordinates if it is available (see the scipy documentation for more information about this). If scipy is not available, then a slower implementation of map_coordinates is used.
|
||||||
|
|
||||||
For a graphical interface to this function, see :func:`ROI.getArrayRegion <pyqtgraph.ROI.getArrayRegion>`
|
For a graphical interface to this function, see :func:`ROI.getArrayRegion <pyqtgraph.ROI.getArrayRegion>`
|
||||||
|
|
||||||
@ -422,8 +411,12 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3))
|
affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not HAVE_SCIPY:
|
try:
|
||||||
raise Exception("This function requires the scipy library, but it does not appear to be importable.")
|
import scipy.ndimage
|
||||||
|
have_scipy = True
|
||||||
|
except ImportError:
|
||||||
|
have_scipy = False
|
||||||
|
have_scipy = False
|
||||||
|
|
||||||
# sanity check
|
# sanity check
|
||||||
if len(shape) != len(vectors):
|
if len(shape) != len(vectors):
|
||||||
@ -445,7 +438,6 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
#print "tr1:", tr1
|
#print "tr1:", tr1
|
||||||
## dims are now [(slice axes), (other axes)]
|
## dims are now [(slice axes), (other axes)]
|
||||||
|
|
||||||
|
|
||||||
## make sure vectors are arrays
|
## make sure vectors are arrays
|
||||||
if not isinstance(vectors, np.ndarray):
|
if not isinstance(vectors, np.ndarray):
|
||||||
vectors = np.array(vectors)
|
vectors = np.array(vectors)
|
||||||
@ -461,12 +453,18 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
#print "X values:"
|
#print "X values:"
|
||||||
#print x
|
#print x
|
||||||
## iterate manually over unused axes since map_coordinates won't do it for us
|
## iterate manually over unused axes since map_coordinates won't do it for us
|
||||||
extraShape = data.shape[len(axes):]
|
if have_scipy:
|
||||||
output = np.empty(tuple(shape) + extraShape, dtype=data.dtype)
|
extraShape = data.shape[len(axes):]
|
||||||
for inds in np.ndindex(*extraShape):
|
output = np.empty(tuple(shape) + extraShape, dtype=data.dtype)
|
||||||
ind = (Ellipsis,) + inds
|
for inds in np.ndindex(*extraShape):
|
||||||
#print data[ind].shape, x.shape, output[ind].shape, output.shape
|
ind = (Ellipsis,) + inds
|
||||||
output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs)
|
output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs)
|
||||||
|
else:
|
||||||
|
# map_coordinates expects the indexes as the first axis, whereas
|
||||||
|
# interpolateArray expects indexes at the last axis.
|
||||||
|
tr = tuple(range(1,x.ndim)) + (0,)
|
||||||
|
output = interpolateArray(data, x.transpose(tr))
|
||||||
|
|
||||||
|
|
||||||
tr = list(range(output.ndim))
|
tr = list(range(output.ndim))
|
||||||
trb = []
|
trb = []
|
||||||
@ -483,6 +481,117 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
else:
|
else:
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def interpolateArray(data, x, default=0.0):
|
||||||
|
"""
|
||||||
|
N-dimensional interpolation similar scipy.ndimage.map_coordinates.
|
||||||
|
|
||||||
|
This function returns linearly-interpolated values sampled from a regular
|
||||||
|
grid of data.
|
||||||
|
|
||||||
|
*data* is an array of any shape containing the values to be interpolated.
|
||||||
|
*x* is an array with (shape[-1] <= data.ndim) containing the locations
|
||||||
|
within *data* to interpolate.
|
||||||
|
|
||||||
|
Returns array of shape (x.shape[:-1] + data.shape)
|
||||||
|
|
||||||
|
For example, assume we have the following 2D image data::
|
||||||
|
|
||||||
|
>>> data = np.array([[1, 2, 4 ],
|
||||||
|
[10, 20, 40 ],
|
||||||
|
[100, 200, 400]])
|
||||||
|
|
||||||
|
To compute a single interpolated point from this data::
|
||||||
|
|
||||||
|
>>> x = np.array([(0.5, 0.5)])
|
||||||
|
>>> interpolateArray(data, x)
|
||||||
|
array([ 8.25])
|
||||||
|
|
||||||
|
To compute a 1D list of interpolated locations::
|
||||||
|
|
||||||
|
>>> x = np.array([(0.5, 0.5),
|
||||||
|
(1.0, 1.0),
|
||||||
|
(1.0, 2.0),
|
||||||
|
(1.5, 0.0)])
|
||||||
|
>>> interpolateArray(data, x)
|
||||||
|
array([ 8.25, 20. , 40. , 55. ])
|
||||||
|
|
||||||
|
To compute a 2D array of interpolated locations::
|
||||||
|
|
||||||
|
>>> x = np.array([[(0.5, 0.5), (1.0, 2.0)],
|
||||||
|
[(1.0, 1.0), (1.5, 0.0)]])
|
||||||
|
>>> interpolateArray(data, x)
|
||||||
|
array([[ 8.25, 40. ],
|
||||||
|
[ 20. , 55. ]])
|
||||||
|
|
||||||
|
..and so on. The *x* argument may have any shape as long as
|
||||||
|
```x.shape[-1] <= data.ndim```. In the case that
|
||||||
|
```x.shape[-1] < data.ndim```, then the remaining axes are simply
|
||||||
|
broadcasted as usual. For example, we can interpolate one location
|
||||||
|
from an entire row of the data::
|
||||||
|
|
||||||
|
>>> x = np.array([[0.5]])
|
||||||
|
>>> interpolateArray(data, x)
|
||||||
|
array([[ 5.5, 11. , 22. ]])
|
||||||
|
|
||||||
|
This is useful for interpolating from arrays of colors, vertexes, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
prof = debug.Profiler()
|
||||||
|
|
||||||
|
result = np.empty(x.shape[:-1] + data.shape, dtype=data.dtype)
|
||||||
|
nd = data.ndim
|
||||||
|
md = x.shape[-1]
|
||||||
|
|
||||||
|
# First we generate arrays of indexes that are needed to
|
||||||
|
# extract the data surrounding each point
|
||||||
|
fields = np.mgrid[(slice(0,2),) * md]
|
||||||
|
xmin = np.floor(x).astype(int)
|
||||||
|
xmax = xmin + 1
|
||||||
|
indexes = np.concatenate([xmin[np.newaxis, ...], xmax[np.newaxis, ...]])
|
||||||
|
fieldInds = []
|
||||||
|
totalMask = np.ones(x.shape[:-1], dtype=bool) # keep track of out-of-bound indexes
|
||||||
|
for ax in range(md):
|
||||||
|
mask = (xmin[...,ax] >= 0) & (x[...,ax] <= data.shape[ax]-1)
|
||||||
|
# keep track of points that need to be set to default
|
||||||
|
totalMask &= mask
|
||||||
|
|
||||||
|
# ..and keep track of indexes that are out of bounds
|
||||||
|
# (note that when x[...,ax] == data.shape[ax], then xmax[...,ax] will be out
|
||||||
|
# of bounds, but the interpolation will work anyway)
|
||||||
|
mask &= (xmax[...,ax] < data.shape[ax])
|
||||||
|
axisIndex = indexes[...,ax][fields[ax]]
|
||||||
|
#axisMask = mask.astype(np.ubyte).reshape((1,)*(fields.ndim-1) + mask.shape)
|
||||||
|
axisIndex[axisIndex < 0] = 0
|
||||||
|
axisIndex[axisIndex >= data.shape[ax]] = 0
|
||||||
|
fieldInds.append(axisIndex)
|
||||||
|
prof()
|
||||||
|
|
||||||
|
# Get data values surrounding each requested point
|
||||||
|
# fieldData[..., i] contains all 2**nd values needed to interpolate x[i]
|
||||||
|
fieldData = data[tuple(fieldInds)]
|
||||||
|
prof()
|
||||||
|
|
||||||
|
## Interpolate
|
||||||
|
s = np.empty((md,) + fieldData.shape, dtype=float)
|
||||||
|
dx = x - xmin
|
||||||
|
# reshape fields for arithmetic against dx
|
||||||
|
for ax in range(md):
|
||||||
|
f1 = fields[ax].reshape(fields[ax].shape + (1,)*(dx.ndim-1))
|
||||||
|
sax = f1 * dx[...,ax] + (1-f1) * (1-dx[...,ax])
|
||||||
|
sax = sax.reshape(sax.shape + (1,) * (s.ndim-1-sax.ndim))
|
||||||
|
s[ax] = sax
|
||||||
|
s = np.product(s, axis=0)
|
||||||
|
result = fieldData * s
|
||||||
|
for i in range(md):
|
||||||
|
result = result.sum(axis=0)
|
||||||
|
|
||||||
|
prof()
|
||||||
|
totalMask.shape = totalMask.shape + (1,) * (nd - md)
|
||||||
|
result[~totalMask] = default
|
||||||
|
prof()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def transformToArray(tr):
|
def transformToArray(tr):
|
||||||
"""
|
"""
|
||||||
Given a QTransform, return a 3x3 numpy array.
|
Given a QTransform, return a 3x3 numpy array.
|
||||||
@ -577,17 +686,25 @@ def transformCoordinates(tr, coords, transpose=False):
|
|||||||
def solve3DTransform(points1, points2):
|
def solve3DTransform(points1, points2):
|
||||||
"""
|
"""
|
||||||
Find a 3D transformation matrix that maps points1 onto points2.
|
Find a 3D transformation matrix that maps points1 onto points2.
|
||||||
Points must be specified as a list of 4 Vectors.
|
Points must be specified as either lists of 4 Vectors or
|
||||||
|
(4, 3) arrays.
|
||||||
"""
|
"""
|
||||||
if not HAVE_SCIPY:
|
import numpy.linalg
|
||||||
raise Exception("This function depends on the scipy library, but it does not appear to be importable.")
|
pts = []
|
||||||
A = np.array([[points1[i].x(), points1[i].y(), points1[i].z(), 1] for i in range(4)])
|
for inp in (points1, points2):
|
||||||
B = np.array([[points2[i].x(), points2[i].y(), points2[i].z(), 1] for i in range(4)])
|
if isinstance(inp, np.ndarray):
|
||||||
|
A = np.empty((4,4), dtype=float)
|
||||||
|
A[:,:3] = inp[:,:3]
|
||||||
|
A[:,3] = 1.0
|
||||||
|
else:
|
||||||
|
A = np.array([[inp[i].x(), inp[i].y(), inp[i].z(), 1] for i in range(4)])
|
||||||
|
pts.append(A)
|
||||||
|
|
||||||
## solve 3 sets of linear equations to determine transformation matrix elements
|
## solve 3 sets of linear equations to determine transformation matrix elements
|
||||||
matrix = np.zeros((4,4))
|
matrix = np.zeros((4,4))
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
matrix[i] = scipy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix
|
## solve Ax = B; x is one row of the desired transformation matrix
|
||||||
|
matrix[i] = numpy.linalg.solve(pts[0], pts[1][:,i])
|
||||||
|
|
||||||
return matrix
|
return matrix
|
||||||
|
|
||||||
@ -600,8 +717,7 @@ def solveBilinearTransform(points1, points2):
|
|||||||
|
|
||||||
mapped = np.dot(matrix, [x*y, x, y, 1])
|
mapped = np.dot(matrix, [x*y, x, y, 1])
|
||||||
"""
|
"""
|
||||||
if not HAVE_SCIPY:
|
import numpy.linalg
|
||||||
raise Exception("This function depends on the scipy library, but it does not appear to be importable.")
|
|
||||||
## A is 4 rows (points) x 4 columns (xy, x, y, 1)
|
## A is 4 rows (points) x 4 columns (xy, x, y, 1)
|
||||||
## B is 4 rows (points) x 2 columns (x, y)
|
## B is 4 rows (points) x 2 columns (x, y)
|
||||||
A = np.array([[points1[i].x()*points1[i].y(), points1[i].x(), points1[i].y(), 1] for i in range(4)])
|
A = np.array([[points1[i].x()*points1[i].y(), points1[i].x(), points1[i].y(), 1] for i in range(4)])
|
||||||
@ -610,7 +726,7 @@ def solveBilinearTransform(points1, points2):
|
|||||||
## solve 2 sets of linear equations to determine transformation matrix elements
|
## solve 2 sets of linear equations to determine transformation matrix elements
|
||||||
matrix = np.zeros((2,4))
|
matrix = np.zeros((2,4))
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
matrix[i] = scipy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix
|
matrix[i] = numpy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix
|
||||||
|
|
||||||
return matrix
|
return matrix
|
||||||
|
|
||||||
@ -629,6 +745,10 @@ def rescaleData(data, scale, offset, dtype=None):
|
|||||||
try:
|
try:
|
||||||
if not getConfigOption('useWeave'):
|
if not getConfigOption('useWeave'):
|
||||||
raise Exception('Weave is disabled; falling back to slower version.')
|
raise Exception('Weave is disabled; falling back to slower version.')
|
||||||
|
try:
|
||||||
|
import scipy.weave
|
||||||
|
except ImportError:
|
||||||
|
raise Exception('scipy.weave is not importable; falling back to slower version.')
|
||||||
|
|
||||||
## require native dtype when using weave
|
## require native dtype when using weave
|
||||||
if not data.dtype.isnative:
|
if not data.dtype.isnative:
|
||||||
@ -671,68 +791,13 @@ def applyLookupTable(data, lut):
|
|||||||
Uses values in *data* as indexes to select values from *lut*.
|
Uses values in *data* as indexes to select values from *lut*.
|
||||||
The returned data has shape data.shape + lut.shape[1:]
|
The returned data has shape data.shape + lut.shape[1:]
|
||||||
|
|
||||||
Uses scipy.weave to improve performance if it is available.
|
|
||||||
Note: color gradient lookup tables can be generated using GradientWidget.
|
Note: color gradient lookup tables can be generated using GradientWidget.
|
||||||
"""
|
"""
|
||||||
if data.dtype.kind not in ('i', 'u'):
|
if data.dtype.kind not in ('i', 'u'):
|
||||||
data = data.astype(int)
|
data = data.astype(int)
|
||||||
|
|
||||||
## using np.take appears to be faster than even the scipy.weave method and takes care of clipping as well.
|
|
||||||
return np.take(lut, data, axis=0, mode='clip')
|
return np.take(lut, data, axis=0, mode='clip')
|
||||||
|
|
||||||
### old methods:
|
|
||||||
#data = np.clip(data, 0, lut.shape[0]-1)
|
|
||||||
|
|
||||||
#try:
|
|
||||||
#if not USE_WEAVE:
|
|
||||||
#raise Exception('Weave is disabled; falling back to slower version.')
|
|
||||||
|
|
||||||
### number of values to copy for each LUT lookup
|
|
||||||
#if lut.ndim == 1:
|
|
||||||
#ncol = 1
|
|
||||||
#else:
|
|
||||||
#ncol = sum(lut.shape[1:])
|
|
||||||
|
|
||||||
### output array
|
|
||||||
#newData = np.empty((data.size, ncol), dtype=lut.dtype)
|
|
||||||
|
|
||||||
### flattened input arrays
|
|
||||||
#flatData = data.flatten()
|
|
||||||
#flatLut = lut.reshape((lut.shape[0], ncol))
|
|
||||||
|
|
||||||
#dataSize = data.size
|
|
||||||
|
|
||||||
### strides for accessing each item
|
|
||||||
#newStride = newData.strides[0] / newData.dtype.itemsize
|
|
||||||
#lutStride = flatLut.strides[0] / flatLut.dtype.itemsize
|
|
||||||
#dataStride = flatData.strides[0] / flatData.dtype.itemsize
|
|
||||||
|
|
||||||
### strides for accessing individual values within a single LUT lookup
|
|
||||||
#newColStride = newData.strides[1] / newData.dtype.itemsize
|
|
||||||
#lutColStride = flatLut.strides[1] / flatLut.dtype.itemsize
|
|
||||||
|
|
||||||
#code = """
|
|
||||||
|
|
||||||
#for( int i=0; i<dataSize; i++ ) {
|
|
||||||
#for( int j=0; j<ncol; j++ ) {
|
|
||||||
#newData[i*newStride + j*newColStride] = flatLut[flatData[i*dataStride]*lutStride + j*lutColStride];
|
|
||||||
#}
|
|
||||||
#}
|
|
||||||
#"""
|
|
||||||
#scipy.weave.inline(code, ['flatData', 'flatLut', 'newData', 'dataSize', 'ncol', 'newStride', 'lutStride', 'dataStride', 'newColStride', 'lutColStride'])
|
|
||||||
#newData = newData.reshape(data.shape + lut.shape[1:])
|
|
||||||
##if np.any(newData != lut[data]):
|
|
||||||
##print "mismatch!"
|
|
||||||
|
|
||||||
#data = newData
|
|
||||||
#except:
|
|
||||||
#if USE_WEAVE:
|
|
||||||
#debug.printExc("Error; disabling weave.")
|
|
||||||
#USE_WEAVE = False
|
|
||||||
#data = lut[data]
|
|
||||||
|
|
||||||
#return data
|
|
||||||
|
|
||||||
|
|
||||||
def makeRGBA(*args, **kwds):
|
def makeRGBA(*args, **kwds):
|
||||||
"""Equivalent to makeARGB(..., useRGBA=True)"""
|
"""Equivalent to makeARGB(..., useRGBA=True)"""
|
||||||
@ -1055,6 +1120,45 @@ def colorToAlpha(data, color):
|
|||||||
|
|
||||||
#raise Exception()
|
#raise Exception()
|
||||||
return np.clip(output, 0, 255).astype(np.ubyte)
|
return np.clip(output, 0, 255).astype(np.ubyte)
|
||||||
|
|
||||||
|
def gaussianFilter(data, sigma):
|
||||||
|
"""
|
||||||
|
Drop-in replacement for scipy.ndimage.gaussian_filter.
|
||||||
|
|
||||||
|
(note: results are only approximately equal to the output of
|
||||||
|
gaussian_filter)
|
||||||
|
"""
|
||||||
|
if np.isscalar(sigma):
|
||||||
|
sigma = (sigma,) * data.ndim
|
||||||
|
|
||||||
|
baseline = data.mean()
|
||||||
|
filtered = data - baseline
|
||||||
|
for ax in range(data.ndim):
|
||||||
|
s = sigma[ax]
|
||||||
|
if s == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# generate 1D gaussian kernel
|
||||||
|
ksize = int(s * 6)
|
||||||
|
x = np.arange(-ksize, ksize)
|
||||||
|
kernel = np.exp(-x**2 / (2*s**2))
|
||||||
|
kshape = [1,] * data.ndim
|
||||||
|
kshape[ax] = len(kernel)
|
||||||
|
kernel = kernel.reshape(kshape)
|
||||||
|
|
||||||
|
# convolve as product of FFTs
|
||||||
|
shape = data.shape[ax] + ksize
|
||||||
|
scale = 1.0 / (abs(s) * (2*np.pi)**0.5)
|
||||||
|
filtered = scale * np.fft.irfft(np.fft.rfft(filtered, shape, axis=ax) *
|
||||||
|
np.fft.rfft(kernel, shape, axis=ax),
|
||||||
|
axis=ax)
|
||||||
|
|
||||||
|
# clip off extra data
|
||||||
|
sl = [slice(None)] * data.ndim
|
||||||
|
sl[ax] = slice(filtered.shape[ax]-data.shape[ax],None,None)
|
||||||
|
filtered = filtered[sl]
|
||||||
|
return filtered + baseline
|
||||||
|
|
||||||
|
|
||||||
def downsample(data, n, axis=0, xvals='subsample'):
|
def downsample(data, n, axis=0, xvals='subsample'):
|
||||||
"""Downsample by averaging points together across axis.
|
"""Downsample by averaging points together across axis.
|
||||||
@ -1473,7 +1577,11 @@ def traceImage(image, values, smooth=0.5):
|
|||||||
If image is RGB or RGBA, then the shape of values should be (nvals, 3/4)
|
If image is RGB or RGBA, then the shape of values should be (nvals, 3/4)
|
||||||
The parameter *smooth* is expressed in pixels.
|
The parameter *smooth* is expressed in pixels.
|
||||||
"""
|
"""
|
||||||
import scipy.ndimage as ndi
|
try:
|
||||||
|
import scipy.ndimage as ndi
|
||||||
|
except ImportError:
|
||||||
|
raise Exception("traceImage() requires the package scipy.ndimage, but it is not importable.")
|
||||||
|
|
||||||
if values.ndim == 2:
|
if values.ndim == 2:
|
||||||
values = values.T
|
values = values.T
|
||||||
values = values[np.newaxis, np.newaxis, ...].astype(float)
|
values = values[np.newaxis, np.newaxis, ...].astype(float)
|
||||||
@ -1487,7 +1595,7 @@ def traceImage(image, values, smooth=0.5):
|
|||||||
paths = []
|
paths = []
|
||||||
for i in range(diff.shape[-1]):
|
for i in range(diff.shape[-1]):
|
||||||
d = (labels==i).astype(float)
|
d = (labels==i).astype(float)
|
||||||
d = ndi.gaussian_filter(d, (smooth, smooth))
|
d = gaussianFilter(d, (smooth, smooth))
|
||||||
lines = isocurve(d, 0.5, connected=True, extendToEdge=True)
|
lines = isocurve(d, 0.5, connected=True, extendToEdge=True)
|
||||||
path = QtGui.QPainterPath()
|
path = QtGui.QPainterPath()
|
||||||
for line in lines:
|
for line in lines:
|
||||||
@ -1967,14 +2075,16 @@ def invertQTransform(tr):
|
|||||||
bugs in that method. (specifically, Qt has floating-point precision issues
|
bugs in that method. (specifically, Qt has floating-point precision issues
|
||||||
when determining whether a matrix is invertible)
|
when determining whether a matrix is invertible)
|
||||||
"""
|
"""
|
||||||
if not HAVE_SCIPY:
|
try:
|
||||||
|
import numpy.linalg
|
||||||
|
arr = np.array([[tr.m11(), tr.m12(), tr.m13()], [tr.m21(), tr.m22(), tr.m23()], [tr.m31(), tr.m32(), tr.m33()]])
|
||||||
|
inv = numpy.linalg.inv(arr)
|
||||||
|
return QtGui.QTransform(inv[0,0], inv[0,1], inv[0,2], inv[1,0], inv[1,1], inv[1,2], inv[2,0], inv[2,1])
|
||||||
|
except ImportError:
|
||||||
inv = tr.inverted()
|
inv = tr.inverted()
|
||||||
if inv[1] is False:
|
if inv[1] is False:
|
||||||
raise Exception("Transform is not invertible.")
|
raise Exception("Transform is not invertible.")
|
||||||
return inv[0]
|
return inv[0]
|
||||||
arr = np.array([[tr.m11(), tr.m12(), tr.m13()], [tr.m21(), tr.m22(), tr.m23()], [tr.m31(), tr.m32(), tr.m33()]])
|
|
||||||
inv = scipy.linalg.inv(arr)
|
|
||||||
return QtGui.QTransform(inv[0,0], inv[0,1], inv[0,2], inv[1,0], inv[1,1], inv[1,2], inv[2,0], inv[2,1])
|
|
||||||
|
|
||||||
|
|
||||||
def pseudoScatter(data, spacing=None, shuffle=True, bidir=False):
|
def pseudoScatter(data, spacing=None, shuffle=True, bidir=False):
|
||||||
|
@ -651,13 +651,12 @@ class PlotDataItem(GraphicsObject):
|
|||||||
|
|
||||||
def _fourierTransform(self, x, y):
|
def _fourierTransform(self, x, y):
|
||||||
## Perform fourier transform. If x values are not sampled uniformly,
|
## Perform fourier transform. If x values are not sampled uniformly,
|
||||||
## then use interpolate.griddata to resample before taking fft.
|
## then use np.interp to resample before taking fft.
|
||||||
dx = np.diff(x)
|
dx = np.diff(x)
|
||||||
uniform = not np.any(np.abs(dx-dx[0]) > (abs(dx[0]) / 1000.))
|
uniform = not np.any(np.abs(dx-dx[0]) > (abs(dx[0]) / 1000.))
|
||||||
if not uniform:
|
if not uniform:
|
||||||
import scipy.interpolate as interp
|
|
||||||
x2 = np.linspace(x[0], x[-1], len(x))
|
x2 = np.linspace(x[0], x[-1], len(x))
|
||||||
y = interp.griddata(x, y, x2, method='linear')
|
y = np.interp(x2, x, y)
|
||||||
x = x2
|
x = x2
|
||||||
f = np.fft.fft(y) / len(y)
|
f = np.fft.fft(y) / len(y)
|
||||||
y = abs(f[1:len(f)/2])
|
y = abs(f[1:len(f)/2])
|
||||||
|
@ -13,11 +13,8 @@ of how to build an ROI at the bottom of the file.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from ..Qt import QtCore, QtGui
|
from ..Qt import QtCore, QtGui
|
||||||
#if not hasattr(QtCore, 'Signal'):
|
|
||||||
#QtCore.Signal = QtCore.pyqtSignal
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from numpy.linalg import norm
|
#from numpy.linalg import norm
|
||||||
import scipy.ndimage as ndimage
|
|
||||||
from ..Point import *
|
from ..Point import *
|
||||||
from ..SRTTransform import SRTTransform
|
from ..SRTTransform import SRTTransform
|
||||||
from math import cos, sin
|
from math import cos, sin
|
||||||
@ -1085,105 +1082,6 @@ class ROI(GraphicsObject):
|
|||||||
#mapped += translate.reshape((2,1,1))
|
#mapped += translate.reshape((2,1,1))
|
||||||
mapped = fn.transformCoordinates(img.transform(), coords)
|
mapped = fn.transformCoordinates(img.transform(), coords)
|
||||||
return result, mapped
|
return result, mapped
|
||||||
|
|
||||||
|
|
||||||
### transpose data so x and y are the first 2 axes
|
|
||||||
#trAx = range(0, data.ndim)
|
|
||||||
#trAx.remove(axes[0])
|
|
||||||
#trAx.remove(axes[1])
|
|
||||||
#tr1 = tuple(axes) + tuple(trAx)
|
|
||||||
#arr = data.transpose(tr1)
|
|
||||||
|
|
||||||
### Determine the minimal area of the data we will need
|
|
||||||
#(dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes)
|
|
||||||
|
|
||||||
### Pad data boundaries by 1px if possible
|
|
||||||
#dataBounds = (
|
|
||||||
#(max(dataBounds[0][0]-1, 0), min(dataBounds[0][1]+1, arr.shape[0])),
|
|
||||||
#(max(dataBounds[1][0]-1, 0), min(dataBounds[1][1]+1, arr.shape[1]))
|
|
||||||
#)
|
|
||||||
|
|
||||||
### Extract minimal data from array
|
|
||||||
#arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]]
|
|
||||||
|
|
||||||
### Update roiDataTransform to reflect this extraction
|
|
||||||
#roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0])
|
|
||||||
#### (roiDataTransform now maps from ROI coords to extracted data coords)
|
|
||||||
|
|
||||||
|
|
||||||
### Rotate array
|
|
||||||
#if abs(self.state['angle']) > 1e-5:
|
|
||||||
#arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1)
|
|
||||||
|
|
||||||
### update data transforms to reflect this rotation
|
|
||||||
#rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi)
|
|
||||||
#roiDataTransform *= rot
|
|
||||||
|
|
||||||
### The rotation also causes a shift which must be accounted for:
|
|
||||||
#dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1])
|
|
||||||
#rotBound = rot.mapRect(dataBound)
|
|
||||||
#roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top())
|
|
||||||
|
|
||||||
#else:
|
|
||||||
#arr2 = arr1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Shift off partial pixels
|
|
||||||
## 1. map ROI into current data space
|
|
||||||
#roiBounds = roiDataTransform.mapRect(self.boundingRect())
|
|
||||||
|
|
||||||
## 2. Determine amount to shift data
|
|
||||||
#shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom())
|
|
||||||
#if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6:
|
|
||||||
## 3. pad array with 0s before shifting
|
|
||||||
#arr2a = np.zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype)
|
|
||||||
#arr2a[1:-1, 1:-1] = arr2
|
|
||||||
|
|
||||||
## 4. shift array and udpate transforms
|
|
||||||
#arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1)
|
|
||||||
#roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1])
|
|
||||||
#else:
|
|
||||||
#arr3 = arr2
|
|
||||||
|
|
||||||
|
|
||||||
#### Extract needed region from rotated/shifted array
|
|
||||||
## 1. map ROI into current data space (round these values off--they should be exact integer values at this point)
|
|
||||||
#roiBounds = roiDataTransform.mapRect(self.boundingRect())
|
|
||||||
##print self, roiBounds.height()
|
|
||||||
##import traceback
|
|
||||||
##traceback.print_stack()
|
|
||||||
|
|
||||||
#roiBounds = QtCore.QRect(round(roiBounds.left()), round(roiBounds.top()), round(roiBounds.width()), round(roiBounds.height()))
|
|
||||||
|
|
||||||
##2. intersect ROI with data bounds
|
|
||||||
#dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1]))
|
|
||||||
|
|
||||||
##3. Extract data from array
|
|
||||||
#db = dataBounds
|
|
||||||
#bounds = (
|
|
||||||
#(db.left(), db.right()+1),
|
|
||||||
#(db.top(), db.bottom()+1)
|
|
||||||
#)
|
|
||||||
#arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]]
|
|
||||||
|
|
||||||
#### Create zero array in size of ROI
|
|
||||||
#arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype)
|
|
||||||
|
|
||||||
### Fill array with ROI data
|
|
||||||
#orig = Point(dataBounds.topLeft() - roiBounds.topLeft())
|
|
||||||
#subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]]
|
|
||||||
#subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]]
|
|
||||||
|
|
||||||
|
|
||||||
### figure out the reverse transpose order
|
|
||||||
#tr2 = np.array(tr1)
|
|
||||||
#for i in range(0, len(tr2)):
|
|
||||||
#tr2[tr1[i]] = i
|
|
||||||
#tr2 = tuple(tr2)
|
|
||||||
|
|
||||||
### Untranspose array before returning
|
|
||||||
#return arr5.transpose(tr2)
|
|
||||||
|
|
||||||
def getAffineSliceParams(self, data, img, axes=(0,1)):
|
def getAffineSliceParams(self, data, img, axes=(0,1)):
|
||||||
"""
|
"""
|
||||||
|
64
pyqtgraph/tests/test_functions.py
Normal file
64
pyqtgraph/tests/test_functions.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import pyqtgraph as pg
|
||||||
|
import numpy as np
|
||||||
|
from numpy.testing import assert_array_almost_equal, assert_almost_equal
|
||||||
|
|
||||||
|
np.random.seed(12345)
|
||||||
|
|
||||||
|
def testSolve3D():
|
||||||
|
p1 = np.array([[0,0,0,1],
|
||||||
|
[1,0,0,1],
|
||||||
|
[0,1,0,1],
|
||||||
|
[0,0,1,1]], dtype=float)
|
||||||
|
|
||||||
|
# transform points through random matrix
|
||||||
|
tr = np.random.normal(size=(4, 4))
|
||||||
|
tr[3] = (0,0,0,1)
|
||||||
|
p2 = np.dot(tr, p1.T).T[:,:3]
|
||||||
|
|
||||||
|
# solve to see if we can recover the transformation matrix.
|
||||||
|
tr2 = pg.solve3DTransform(p1, p2)
|
||||||
|
|
||||||
|
assert_array_almost_equal(tr[:3], tr2[:3])
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolateArray():
|
||||||
|
data = np.array([[ 1., 2., 4. ],
|
||||||
|
[ 10., 20., 40. ],
|
||||||
|
[ 100., 200., 400.]])
|
||||||
|
|
||||||
|
x = np.array([[ 0.3, 0.6],
|
||||||
|
[ 1. , 1. ],
|
||||||
|
[ 0.5, 1. ],
|
||||||
|
[ 0.5, 2.5],
|
||||||
|
[ 10. , 10. ]])
|
||||||
|
|
||||||
|
result = pg.interpolateArray(data, x)
|
||||||
|
|
||||||
|
import scipy.ndimage
|
||||||
|
spresult = scipy.ndimage.map_coordinates(data, x.T, order=1)
|
||||||
|
|
||||||
|
assert_array_almost_equal(result, spresult)
|
||||||
|
|
||||||
|
# test mapping when x.shape[-1] < data.ndim
|
||||||
|
x = np.array([[ 0.3, 0],
|
||||||
|
[ 0.3, 1],
|
||||||
|
[ 0.3, 2]])
|
||||||
|
|
||||||
|
r1 = pg.interpolateArray(data, x)
|
||||||
|
r2 = pg.interpolateArray(data, x[0,:1])
|
||||||
|
assert_array_almost_equal(r1, r2)
|
||||||
|
|
||||||
|
|
||||||
|
# test mapping 2D array of locations
|
||||||
|
x = np.array([[[0.5, 0.5], [0.5, 1.0], [0.5, 1.5]],
|
||||||
|
[[1.5, 0.5], [1.5, 1.0], [1.5, 1.5]]])
|
||||||
|
|
||||||
|
r1 = pg.interpolateArray(data, x)
|
||||||
|
r2 = scipy.ndimage.map_coordinates(data, x.transpose(2,0,1), order=1)
|
||||||
|
assert_array_almost_equal(r1, r2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_interpolateArray()
|
39
pyqtgraph/tests/test_srttransform3d.py
Normal file
39
pyqtgraph/tests/test_srttransform3d.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import pyqtgraph as pg
|
||||||
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
import numpy as np
|
||||||
|
from numpy.testing import assert_array_almost_equal, assert_almost_equal
|
||||||
|
|
||||||
|
testPoints = np.array([
|
||||||
|
[0, 0, 0],
|
||||||
|
[1, 0, 0],
|
||||||
|
[0, 1, 0],
|
||||||
|
[0, 0, 1],
|
||||||
|
[-1, -1, 0],
|
||||||
|
[0, -1, -1]])
|
||||||
|
|
||||||
|
|
||||||
|
def testMatrix():
|
||||||
|
"""
|
||||||
|
SRTTransform3D => Transform3D => SRTTransform3D
|
||||||
|
"""
|
||||||
|
tr = pg.SRTTransform3D()
|
||||||
|
tr.setRotate(45, (0, 0, 1))
|
||||||
|
tr.setScale(0.2, 0.4, 1)
|
||||||
|
tr.setTranslate(10, 20, 40)
|
||||||
|
assert tr.getRotation() == (45, QtGui.QVector3D(0, 0, 1))
|
||||||
|
assert tr.getScale() == QtGui.QVector3D(0.2, 0.4, 1)
|
||||||
|
assert tr.getTranslation() == QtGui.QVector3D(10, 20, 40)
|
||||||
|
|
||||||
|
tr2 = pg.Transform3D(tr)
|
||||||
|
assert np.all(tr.matrix() == tr2.matrix())
|
||||||
|
|
||||||
|
# This is the most important test:
|
||||||
|
# The transition from Transform3D to SRTTransform3D is a tricky one.
|
||||||
|
tr3 = pg.SRTTransform3D(tr2)
|
||||||
|
assert_array_almost_equal(tr.matrix(), tr3.matrix())
|
||||||
|
assert_almost_equal(tr3.getRotation()[0], tr.getRotation()[0])
|
||||||
|
assert_array_almost_equal(tr3.getRotation()[1], tr.getRotation()[1])
|
||||||
|
assert_array_almost_equal(tr3.getScale(), tr.getScale())
|
||||||
|
assert_array_almost_equal(tr3.getTranslation(), tr.getTranslation())
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user