How to create julia color scheme for displaying Ct scan Makie.jl - plot

I use makie.jl with slicesNumb for visualization of PET/CT scans, I have 3d array of attenuation values and I display heatmap with changing slices using slider - this works well I have two problems
I do not know how to be able to define custom colormaps (basically I need to be able to specify that all above some threshold value will be black and all below white and values between will have grey values proportional to attenuation value).
2)I would like to be able to display to display over my image (tachnically heatmap) another ones where I would be able to controll transparency - alpha value of pixels - in order to display some annotations/ PET ...
code that works but without those 2 functionalities and how it looks
using GLMakie
```#doc
simple display of single image - only in transverse plane
```
function singleCtScanDisplay(arr ::Array{Number, 3})
fig = Figure()
sl_x = Slider(fig[2, 1], range = 1:1:size(arr)[3], startvalue = 40)
ax = Axis(fig[1, 1])
hm = heatmap!(ax, lift(idx-> arr[:,:, floor(idx)], sl_x.value) ,colormap = :grays)
Colorbar(fig[1, 2], hm)
fig
end
Thanks for help !

You can use Colors and ColorSchemeTools, but you will need to add the top and bottom of the scheme according to your thresholds.
using Colors, ColorSchemeTools
truemin = 0
truemax = 600
max_shown_black = 20
min_shown_white = 500
data = rand(truemin:truemax, (500, 500, 20))
grayscheme = [fill(colorant"black", max_shown_black - truemin + 1);
collect(make_colorscheme(identity, identity, identity,
length = min_shown_white - max_shown_black - 1));
fill(colorant"white", truemax - min_shown_white + 1)]
For controlling alpha, I would add a popup window with an alpha slider. Take a look at some of the distributable DICOM tools for examples.

I finally managed it basically I load 3 dimensional data stored in hdf5 (I loaded it into hdf5 from raw using python)
It enables viewing transverse slices and annotate 3d pathes in a mask that will be displayed over main image
exmpleH = #spawnat persistenceWorker Main.h5manag.getExample()
minimumm = -1000
maximumm = 2000
arrr= fetch(exmpleH)
imageDim = size(arrr)
using GLMakie
maskArr = Observable(BitArray(undef, imageDim))
MyImgeViewer.singleCtScanDisplay(arrr, maskArr,minimumm, maximumm)
Now definition of the required modules
```#doc
functions responsible for displaying medical image Data
```
using DrWatson
#quickactivate "Probabilistic medical segmentation"
module MyImgeViewer
using GLMakie
using Makie
#using GeometryBasics
using GeometricalPredicates
using ColorTypes
using Distributed
using GLMakie
using Main.imageViewerHelper
using Main.workerNumbers
## getting id of workers
```#doc
simple display of single image - only in transverse plane we are adding also a mask that
arrr - main 3 dimensional data representing medical image for example in case of CT each voxel represents value of X ray attenuation
minimumm, maximumm - approximately minimum and maximum values we can have in our image
```
function singleCtScanDisplay(arrr ::Array{Number, 3}, maskArr , minimumm, maximumm)
#we modify 2 pixels just in order to make the color range constant so slices will be displayed in the same windows
arrr[1,1,:].= minimumm
arrr[2,1,:].= maximumm
imageDim = size(arrr) # dimenstion of the primary image for example CT scan
slicesNumb =imageDim[3] # number of slices
#defining layout variables
scene, layout = GLMakie.layoutscene(resolution = (600, 400))
ax1 = layout[1, 1] = GLMakie.Axis(scene, backgroundcolor = :transparent)
ax2 = layout[1, 1] = GLMakie.Axis(scene, backgroundcolor = :transparent)
#control widgets
sl_x =layout[2, 1]= GLMakie.Slider(scene, range = 1:1: slicesNumb , startvalue = slicesNumb/2 )
sliderXVal = sl_x.value
#color maps
cmwhite = cgrad(range(RGBA(10,10,10,0.01), stop=RGBA(0,0,255,0.4), length=10000));
greyss = createMedicalImageColorSchemeB(200,-200,maximumm, minimumm )
####heatmaps
#main heatmap that holds for example Ct scan
currentSliceMain = GLMakie.#lift(arrr[:,:, convert(Int32,$sliderXVal)])
hm = GLMakie.heatmap!(ax1, currentSliceMain ,colormap = greyss)
#helper heatmap designed to respond to both changes in slider and changes in the bit matrix
currentSliceMask = GLMakie.#lift($maskArr[:,:, convert(Int32,$sliderXVal)])
hmB = GLMakie.heatmap!(ax1, currentSliceMask ,colormap = cmwhite)
#adding ability to be able to add information to mask where we clicked so in casse of mit matrix we will set the point where we clicked to 1
indicatorC(ax1,imageDim,scene,maskArr,sliderXVal)
#displaying
colorB = layout[1,2]= Colorbar(scene, hm)
GLMakie.translate!(hmB, Vec3f0(0,0,5))
scene
end
```#doc
inspired by https://github.com/JuliaPlots/Makie.jl/issues/810
Generaly thanks to this function the viewer is able to respond to clicking on the slices and records it in the supplied 3 dimensional AbstractArray
ax - Axis which store our heatmap slices which we want to observe wheather user clicked on them and where
dims - dimensions of main image for example CT
sc - Scene where our axis is
maskArr - the 3 dimensional bit array that has exactly the same dimensions as main Array storing image
sliceNumb - represents on what slide we are on currently on - ussually it just give information from slider
```
function indicatorC(ax::Axis,dims::Tuple{Int64, Int64, Int64},sc::Scene,maskArr,sliceNumb::Observable{Any})
register_interaction!(ax, :indicator) do event::GLMakie.MouseEvent, axis
if event.type === MouseEventTypes.leftclick
println("clicked")
##async begin
#appropriately modyfing wanted pixels in mask array
#async calculateMouseAndSetmaskWrap(maskArr, event,sc,dims,sliceNumb)
#
#
# println("fetched" + fetch(maskA))
# finalize(maskA)
#end
return true
#print("xMouse: $(xMouse) yMouse: $(yMouse) compBoxWidth: $(compBoxWidth) compBoxHeight: $(compBoxHeight) calculatedXpixel: $(calculatedXpixel) calculatedYpixel: $(calculatedYpixel) pixelsNumbInX $(pixelsNumbInX) ")
end
end
end
```#doc
wrapper for calculateMouseAndSetmask - from imageViewerHelper module
given mouse event modifies mask accordingly
maskArr - the 3 dimensional bit array that has exactly the same dimensions as main Array storing image
event - mouse event passed from Makie
sc - scene we are using in Makie
```
function calculateMouseAndSetmaskWrap(maskArr, event,sc,dims,sliceNumb)
maskArr[] = calculateMouseAndSetmask(maskArr, event,sc,dims,sliceNumb)
end
end #module
and helper methods
```#doc
functions responsible for helping in image viewer - those functions are meant to be invoked on separate process
- in parallel
```
using DrWatson
#quickactivate "Probabilistic medical segmentation"
module imageViewerHelper
using Documenter
using ColorTypes
using Colors, ColorSchemeTools
using Makie
export calculateMouseAndSetmask
export createMedicalImageColorSchemeB
# using AbstractPlotting
```#doc
given mouse event modifies mask accordingly
maskArr - the 3 dimensional bit array that has exactly the same dimensions as main Array storing image
event - mouse event passed from Makie
sc - scene we are using in Makie
```
function calculateMouseAndSetmask(maskArr, event,sc,dims,sliceNumb)
#position from top left corner
xMouse= Makie.to_world(sc,event.data)[1]
yMouse= Makie.to_world(sc,event.data)[2]
#data about height and width in layout
compBoxWidth = 510
compBoxHeight = 510
#image dimensions - number of pixels from medical image for example ct scan
pixelsNumbInX =dims[1]
pixelsNumbInY =dims[2]
#calculating over which image pixel we are
calculatedXpixel =convert(Int32, round( (xMouse/compBoxWidth)*pixelsNumbInX) )
calculatedYpixel = convert(Int32,round( (yMouse/compBoxHeight)*pixelsNumbInY ))
sliceNumbConv =convert(Int32,round( sliceNumb[] ))
#appropriately modyfing wanted pixels in mask array
return markMaskArrayPatch( maskArr ,CartesianIndex(calculatedXpixel, calculatedYpixel, sliceNumbConv ),2)
end
```#doc
maskArr - the 3 dimensional bit array that has exactly the same dimensions as main Array storing image
point - cartesian coordinates of point around which we want to modify the 3 dimensional array from 0 to 1
```
function markMaskArrayPatch(maskArr, pointCart::CartesianIndex{3}, patchSize ::Int64)
ones = CartesianIndex(patchSize,patchSize,patchSize) # cartesian 3 dimensional index used for calculations to get range of the cartesian indicis to analyze
maskArrB = maskArr[]
for J in (pointCart-ones):(pointCart+ones)
diff = J - pointCart # diffrence between dimensions relative to point of origin
if cartesianTolinear(diff) <= patchSize
maskArrB[J]=1
end
end
return maskArrB
end
```#doc
works only for 3d cartesian coordinates
cart - cartesian coordinates of point where we will add the dimensions ...
```
function cartesianTolinear(pointCart::CartesianIndex{3}) :: Int16
abs(pointCart[1])+ abs(pointCart[2])+abs(pointCart[3])
end
```#doc
creating grey scheme colors for proper display of medical image mainly CT scan
min_shown_white - max_shown_black range over which the gradint of greys will be shown
truemax - truemin the range of values in the image for which we are creating the scale
```
#taken from https://stackoverflow.com/questions/67727977/how-to-create-julia-color-scheme-for-displaying-ct-scan-makie-jl/67756158#67756158
function createMedicalImageColorSchemeB(min_shown_white,max_shown_black,truemax,truemin ) ::Vector{Any}
# println("max_shown_black - truemin + 1")
# println(max_shown_black - truemin + 1)
# println(" min_shown_white - max_shown_black - 1")
# println( min_shown_white - max_shown_black - 1)
# println("truemax - min_shown_white + 1")
# println(truemax - min_shown_white + 1)
return [fill(colorant"black", max_shown_black - truemin + 1);
collect(make_colorscheme(identity, identity, identity,
length = min_shown_white - max_shown_black - 1));
fill(colorant"white", truemax - min_shown_white + 1)]
end
end #module

Related

QGraphicsPolygonItem - How to change Points coordinates

I'm working on QT since last week and I'm new to graphics management.
I have a polygon that represents 4 pressure graphically: How can I refresh the polygon with the new pos position?
double deltaPos_A = vshp->cpu_v.paramCpuToVisu.liftData[0].pos - vshp->cpu_v.paramCpuToVisu.lift.hMin;
double deltaPos_B = vshp->cpu_v.paramCpuToVisu.liftData[1].pos - vshp->cpu_v.paramCpuToVisu.lift.hMin;
double deltaPos_C = vshp->cpu_v.paramCpuToVisu.liftData[2].pos - vshp->cpu_v.paramCpuToVisu.lift.hMin;
double deltaPos_D = vshp->cpu_v.paramCpuToVisu.liftData[3].pos - vshp->cpu_v.paramCpuToVisu.lift.hMin;
double kPos_X = 180 / (vshp->cpu_v.paramCpuToVisu.lift.hMax - vshp->cpu_v.paramCpuToVisu.lift.hMin);
double kPos_Y = 295 / (vshp->cpu_v.paramCpuToVisu.lift.hMax - vshp->cpu_v.paramCpuToVisu.lift.hMin);
graphic_Press->polygon()[0].setX(-deltaPos_A*kPos_X);
graphic_Press->polygon()[0].setY(-deltaPos_A*kPos_Y);
graphic_Press->polygon()[1].setX(deltaPos_B*kPos_X);
graphic_Press->polygon()[1].setY(-deltaPos_B*kPos_Y);
graphic_Press->polygon()[2].setX(deltaPos_C*kPos_X);
graphic_Press->polygon()[2].setY(deltaPos_C*kPos_Y);
graphic_Press->polygon()[3].setX(-deltaPos_D*kPos_X);
graphic_Press->polygon()[3].setY(deltaPos_D*kPos_Y);
graphic_Press->polygon()[4].setX(-deltaPos_A*kPos_X);
graphic_Press->polygon()[4].setY(-deltaPos_A*kPos_Y);
Here are the declarations ("graphic_Press_Polygon" is a QPolygonF with 5 points):
QGraphicsPolygonItem* graphic_Press;
graphic_Press = mSceneMSPE->addPolygon(graphic_Press_Polygon,greenPen,trasparentBrush);
The new polygon doesn't refresh into the scene. How can I do?
To set a new polygon for your QGraphicsPolygonItem you need to call setPolygon().
Note that calling polygon gives you a copy of the polygon used by the QGraphicsPolygonItem. Modifying this copy does not have any effect on the QGraphicsPolygonItem. After your modifications you need to call setPolygon().

How compare two images using sikuli in robotframework

am totaly new to the sikuli with robotframework. I have some want idea of click and action of the window based but i dont have the concept of compare the image using sikuli with robotframework. Can any one help me?
This is RaiMan from SikuliX.
I recommend to use
https://github.com/rainmanwy/robotframework-SikuliLibrary
I have developed a python script for image comparison, which gets two images to compare the images and write the difference.
You can use this as robot keyword for image comparison. Challenge is that you have to crop the image from desktop application. you can able to do this using robot framework sikuli library keyword crop region by providing x,y, width and height values
compare images.py
# importing the necessary packages
#from skimage.measure import compare_ssim
from skimage.metrics import structural_similarity
import argparse
import imutils
import cv2
import time
import logging
def Compare_Image(image1,image2,Original_image_path,Modified_image_path):
# Reading images
imageA = cv2.imread(image1)
imageB = cv2.imread(image2)
# convert the images to grayscale
grayA = cv2.cvtColor(imageA, cv2.COLOR_BGR2GRAY)
grayB = cv2.cvtColor(imageB, cv2.COLOR_BGR2GRAY)
#getting the structural similarity between the images
(score, diff) = structural_similarity(grayA, grayB, full=True)
diff = (diff * 255).astype("uint8")
print("SSIM: {}".format(score))
#Threshold differences
thresh = cv2.threshold(diff, 0, 255,
cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# loop over the contours
for c in cnts:
#Displaying Rectangles on image difference
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(imageA, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv2.rectangle(imageB, (x, y), (x + w, y + h), (0, 0, 255), 2)
# show the output images
image1=cv2.imshow("Original", imageA)
image2=cv2.imshow("Modified", imageB)
cv2.imwrite(Original_image_path, imageA)
cv2.imwrite(Modified_image_path, imageB)
# cv2.imshow("Diff", diff)
# cv2.imshow("Thresh", thresh)
#cv2.waitKey(0)
#cv2.destroyAllWindows()
#Validating and Logging Image Results
if score==1.0:
print("Reference and Test images are same")
else:
print("Reference and Test images are different")
Sample.robot
* Setting *
Library compare_Images.py
* Test Cases *
Check_Image_Compare
Compare Image path_to_image1\\img1.png path_to_image2\\image2.png
path_to_saveorginalimage1\\orgimage.png path_to_savemodifiedimage\\modifiedimage.png
Log <img src="path_to_saveorginalimage1\\orgimage.png"> HTML
Log <img src="path_to_savemodifiedimage\\modifiedimage.png"> HTML
On executing the python script it will give two images (original image- image1 to compare Modified image- image2 to compare) which will be saved in specified location and logged as in robot file above.

How to create an rectangle in a 3d area via vector&angle

I have a 3d game where I will create an rectangle which is working as screen and the game itself works with vectors to positions. so I will create an rectangle and have only these parameters aviable:
start position ->vector (x,y,z).
Angle(rotation) of object(x,y,z).
size of rectangle.
now also the object need to be roatet to the right side so they are using angels also (x,y,z).
example:
position:-381.968750 -28.653845 -12702.185547
angle: -0.000 90.000 90.000
What I will create is an little bit hard but as idea simple.
I choose 2 complete different positions and angles and will create from the first vector to the second an rectangle.
I can only create an rectangle with the start point and angle.
and I can set the size so (x,y)
So I will now insert 2 positions(vectors) with 2 different angles
The rectangle will have the middle value between the first and second angle so like (90 and 0) -> 45
And the rectangle will start at the start vector and will end with his own size so I don't have a chance to use the end vector directly.
Legendary on photo:
Green=>start and end positions(vectors).
red => the marked zone.
Blue => how I will have the rectangle.
aem_point = vgui.Create( "AEM.Main.Panel" )
if IsValid(aem_point) then
aem_point:SetSize( 2,2 ) -- <-the size that i can set
aem_point:SetPos( 0, 0 )
aem_ph = vgui.Create( "DHTML", aem_point )
aem_ph:SetSize( aem_point:GetSize() )
aem_ph:SetPos(aem_point:GetPos())
aem_ph:SetVisible( true )
aem_ph:SetHTML([[
<html>
<body style="margin:0px;padding:0px;font-size:20px;color:red;border-style: solid;border-color: #ff0000;background-color:rgba(255,0,0,0.1);">
</body>
</html>
]] )
end
hook.Add( "PostDrawOpaqueRenderables", "DrawSample3D2DFrame" .. math.random(), function()
if first and dat_finish then
vgui.Start3D2D( input_position, input_angle, 1 ) -- <-and position&vec
aem_point:Paint3D2D()
vgui.End3D2D()
end
end )
Oh so you want to create a 3d2d plane from 2 vector positions?
Vec1 = A
Vec2 = B
input_position = ( Vec1 + Vec2 ) / 2
A problem you will run into is you need 3 points to generate a plane, so while you can get the position of the screen to get the angle of it you will need another point.
If these screens of yours are staticly set, as in you put their position into the lua code manually and dont intend to have it move or anything, just manually putting in the angle is by far the most simple approch.
As you can see, both of these planes are on the same two points, but they have diffrent angles.
I wrote the demo in expression 2, this should make it clear as to how this works, if you have any other questions just ask.
A = entity(73):pos()
B = entity(83):pos()
if(first())
{
holoCreate(1)
holoCreate(2)
}
holoPos(1,(A+B)/2)
holoScaleUnits(1,vec( abs(B:y() - A:y()) , 1 , abs(sqrt( ( B:z() - A:z() ) ^ 2 + ( B:x() - A:x() ) ^ 2 ))))
holoAng(1,ang(0,90,45))
holoPos(2,(A+B)/2)
holoScaleUnits(2,vec( abs(sqrt( ( B:x() - A:x() ) ^ 2 + ( B:y() - A:y() ) ^ 2 )) , 1 , abs(B:z()-A:z())))
holoAng(2,ang(0,45,0))

ImageView.setImage axes parameter does not switch X-Y dimensions

I have modified the ImageView example by adding the statement data[:, ::10, :] = 0, which sets every tenth element of the middle dimension to 0. The program now shows horizontal lines. This is consistent with the documentation of the ImageView.setImage function: the default axes dictionary is {'t':0, 'x':1, 'y':2, 'c':3}. However, when I change this to {'t':0, 'x':2, 'y':1, 'c':3}, nothing changes where I would expect to get vertical rows.
So my question is: how can I give the row dimension a higher precedence in PyQtGraph? Of course I can transpose all my arrays myself before passing them to the setImage function but I prefer not to. Especially since both Numpy and Qt use the row/column convention and not X before Y. I don't see why PyQtGraph chooses the latter.
For completeness, find my modified ImageView example below.
import numpy as np
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
app = QtGui.QApplication([])
## Create window with ImageView widget
win = QtGui.QMainWindow()
win.resize(800,800)
imv = pg.ImageView()
win.setCentralWidget(imv)
win.show()
win.setWindowTitle('pyqtgraph example: ImageView')
## Create random 3D data set with noisy signals
img = pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
img = img[np.newaxis,:,:]
decay = np.exp(-np.linspace(0,0.3,100))[:,np.newaxis,np.newaxis]
data = np.random.normal(size=(100, 200, 200))
data += img * decay
data += 2
## Add time-varying signal
sig = np.zeros(data.shape[0])
sig[30:] += np.exp(-np.linspace(1,10, 70))
sig[40:] += np.exp(-np.linspace(1,10, 60))
sig[70:] += np.exp(-np.linspace(1,10, 30))
sig = sig[:,np.newaxis,np.newaxis] * 3
data[:,50:60,50:60] += sig
data[:, ::10, :] = 0 # Make image a-symmetrical
## Display the data and assign each frame a time value from 1.0 to 3.0
imv.setImage(data, xvals=np.linspace(1., 3., data.shape[0]),
axes={'t':0, 'x':2, 'y':1, 'c':3}) # doesn't help
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Looking through ImageView.py, setImage() parses the axes dictionary and based on presence of 't' it builds the z-axis/frame slider, and that's it. Rearranging the axes seems unimplemented yet.

PyQtGraph sliding window acquires y offset

I'm trying to create an scrolling plot window with PyQtGraph. The plot itself has multiple axes. I used this as the basis of the multiple axes. I used this one as the base to do the scrolling bit.
My problem is that for my data, the scrolling plot seems to acquire an y-offset, increasing as time goes on. I also tried using the same data to display in an accumulating plot (though I really would rather to do a scrolling view) and it didn't acquire any y-offset.
This is what it looks at the end of my test sample - missing the effect of y-offset gradually increasing -
Of course, I would like for the y-offset not to appear, the top plot should be identical to the last 50 samples of the bottom plot
Both plots have identical data sets.
The code that I'm using to generate this is:
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
from time import sleep
win = pg.GraphicsWindow()
win.setWindowTitle('Sliding Window Test')
p1 = win.addPlot()
p1.setLabels(left='Large Range')
## create third ViewBox.
## this time we need to create a new axis as well.
p3 = pg.ViewBox()
ax3 = pg.AxisItem('right')
p1.layout.addItem(ax3, 2, 3)
p1.scene().addItem(p3)
ax3.linkToView(p3)
p3.setXLink(p1)
ax3.setZValue(-10000)
ax3.setLabel('Small Range', color='#ff0000')
win.nextRow()
p5 = win.addPlot()
p5.setLabels(left='Large Range')
## create third ViewBox.
## this time we need to create a new axis as well.
p7 = pg.ViewBox()
ax7 = pg.AxisItem('right')
p5.layout.addItem(ax7, 2, 3)
p5.scene().addItem(p7)
ax7.linkToView(p7)
p7.setXLink(p5)
ax7.setZValue(-10000)
ax7.setLabel('Small Range', color='#ff0000')
## Handle view resizing
def updateViews():
## view has resized; update auxiliary views to match
global p1, p3, p5, p7
p3.setGeometry(p1.vb.sceneBoundingRect())
p7.setGeometry(p5.vb.sceneBoundingRect())
## need to re-update linked axes since this was called
## incorrectly while views had different shapes.
## (probably this should be handled in ViewBox.resizeEvent)
p3.linkedViewChanged(p1.vb, p3.XAxis)
p7.linkedViewChanged(p5.vb, p7.XAxis)
updateViews()
p1.vb.sigResized.connect(updateViews)
p5.vb.sigResized.connect(updateViews)
data1 = []
data3 = []
curve1 = p1.plot()
curve3 = pg.PlotCurveItem(pen='r')
p3.addItem(curve3)
curve5 = p5.plot()
curve7 = pg.PlotCurveItem(pen='r')
p7.addItem(curve7)
data1 = [1000.0*r - 400 for r in np.random.random(size=600)]
data3 = [1.5*r for r in np.random.random(size=600)]
p1.setRange(yRange=(-400, 600))
p3.setRange(yRange=(0, 1.5))
p5.setRange(yRange=(-400, 600))
p7.setRange(yRange=(0, 1.5))
timer = pg.QtCore.QTimer()
r = 0
def update():
global timer
global r
if r > 50:
curve1.setData(data1[r-50:r])
curve3.setData(data3[r-50:r])
curve1.setPos(r - 50, r)
curve3.setPos(r - 50, r)
else:
curve1.setData(data1[:r])
curve3.setData(data3[:r])
curve5.setData(data1[:r])
curve7.setData(data3[:r])
r +=1
if r >= 600:
timer.stop()
timer.timeout.connect(update)
timer.start(100)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Simple error: both curves in the top plot are shifted in the y-direction because of these lines:
curve1.setPos(r - 50, r)
curve3.setPos(r - 50, r)
They should instead look like:
curve1.setPos(r - 50, 0)
curve3.setPos(r - 50, 0)

Resources