Commit 11fdf4ed authored by Josef Brandt's avatar Josef Brandt

new simulated raman

parent c5a3864a
......@@ -72,6 +72,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
def closeEvent(self, event):
self.view.closeEvent(event)
closeAll()
@QtCore.pyqtSlot(float)
def scalingChanged(self, scale):
......@@ -336,7 +337,17 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
if __name__ == '__main__':
import sys
from time import localtime, strftime
def closeAll() -> None:
"""
Closes the app and, with that, all windows.
Josef: I implemented this, as with the simulated microscope stage it was difficult to find a proper way to
ONLY close it at the end of running the program. Closing it on disconnect of the ramanctrl is not suitable,
as it should be opened also in disconnected stage (e.g., when another instance is running in optical or raman
scan, but the UI (disconnected) should still update what's going on.
"""
app.deleteLater()
def excepthook(excType, excValue, tracebackobj):
"""
Global function to catch unhandled exceptions.
......
......@@ -43,7 +43,7 @@ except KeyError:
pass
if interface == "SIMULATED_RAMAN_CONTROL":
from .simulatedraman import SimulatedRaman
from .simulated.simulatedraman import SimulatedRaman
RamanControl = SimulatedRaman
print("WARNING: using only simulated raman control!")
simulatedRaman = True
......
# -*- coding: utf-8 -*-
"""
GEPARD - Gepard-Enabled PARticle Detection
Copyright (C) 2018 Lars Bittrich and Josef Brandt, Leibniz-Institut für
Polymerforschung Dresden e. V. <bittrich-lars@ipfdd.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program, see COPYING.
If not, see <https://www.gnu.org/licenses/>.
Image Generator for fake images for testing with simulated instrument interfaces
"""
import cv2
import numpy as np
import matplotlib.pyplot as plt
import time
import noise
from copy import deepcopy
from typing import Tuple, List
def particleIsVisible(particle: np.ndarray, full: np.ndarray, pos: Tuple[int, int]) -> bool:
"""
:param full: image of the current particle
:param particle: image with all particles
:param pos: x, y of the particle center in pixels
"""
isVisible: bool = False
height, width = particle.shape[:2]
if pos[0] + width/2 >= 0 and pos[0] - width/2 < full.shape[1]:
if pos[1] + height/2 >= 0 and pos[1] - height/2 < full.shape[0]:
isVisible = True
return isVisible
def addTexture(partImage: np.ndarray) -> np.ndarray:
shape = partImage.shape
startX = np.random.randint(0, 100)
startY = np.random.randint(0, 100)
for i in range(shape[0]):
for j in range(shape[1]):
noiseVal: float = noise.pnoise2((startY + i)/25, (startX + j)/25, octaves=4, persistence=0.5)
noiseVal = np.clip(noiseVal + 0.5, 0.0, 1.0) # bring to 0...1 range
partImage[i, j, :] *= (noiseVal**0.3)
return partImage
class OverlapRange:
fullStartX: int = np.nan
fullEndX: int = np.nan
fullStartY: int = np.nan
fullEndY: int = np.nan
subStartX: int = np.nan
subEndX: int = np.nan
subStartY: int = np.nan
subEndY: int = np.nan
def getParticleOverlapRanges(full: np.ndarray, sub: np.ndarray, centerXY: Tuple[int, int]) -> Tuple[bool, OverlapRange]:
ranges: OverlapRange = OverlapRange()
ranges.fullStartX = int(round(centerXY[0] - sub.shape[1]/2))
ranges.fullEndX = ranges.fullStartX + sub.shape[1]
ranges.fullStartY = int(round(centerXY[1] - sub.shape[0]/2))
ranges.fullEndY = ranges.fullStartY + sub.shape[0]
origRanges = deepcopy(ranges)
if ranges.fullEndX < 0 or ranges.fullEndY < 0 or ranges.fullStartX >= full.shape[1] or ranges.fullStartY >= full.shape[0]:
success = False
else:
success = True
if origRanges.fullStartX >= 0:
ranges.subStartX = 0
else:
ranges.subStartX = -origRanges.fullStartX
ranges.fullStartX = 0
if origRanges.fullEndX <= full.shape[1]:
ranges.subEndX = sub.shape[1]
else:
diff: int = origRanges.fullEndX - full.shape[1]
ranges.subEndX = sub.shape[1] - diff
ranges.fullEndX = full.shape[1]
if origRanges.fullStartY >= 0:
ranges.subStartY = 0
else:
ranges.subStartY = -origRanges.fullStartY
ranges.fullStartY = 0
if origRanges.fullEndY <= full.shape[0]:
ranges.subEndY = sub.shape[0]
else:
diff: int = origRanges.fullEndY - full.shape[0]
ranges.subEndY = sub.shape[0] - diff
ranges.fullEndY = full.shape[0]
return success, ranges
class FakeParticle:
""" A fake spherical particle"""
x: float = np.nan # in µm
y: float = np.nan # in µm
radius: float = np.nan # in µm
randImageIndex: int = np.nan
class FakeCamera:
# TODO: Implement a small angle to simulate a tilted camera!
def __init__(self):
self.imgDims: Tuple[int, int] = (500, 250) # width, height of camera imgage
self.pixelscale: float = 1.0 # µm/px
self.sizeScale: float = 100.0 # smaller values make particles rendered smaller and vice versa
self.threshold: float = 0.7 # threshold for determining particles. Larger values -> smaller particles
self.numZLevels: int = 7 # number of z-levels cached for faking depth of field
self.maxZDiff: float = 100 # max difference in z that is simulated.
self.currentImage: np.ndarray = np.zeros((self.imgDims[1], self.imgDims[0], 3))
self.fakeFilter: FakeFilter = FakeFilter()
def updateImageAtPosition(self, position: List[float]) -> None:
"""
Centers the camera at the given µm coordinates and returns the camera image centered at this position.
:param position: tuple (x in µm, y in µm, z in µm)
"""
particles: np.ndarray = np.zeros((self.imgDims[1], self.imgDims[0], 3), dtype=np.uint8)
for particle in self.fakeFilter.particles:
pixelX, pixelY = self._getParticlePixelCoords((position[0], position[1]), particle)
blurRadius: int = int(round(abs(position[2] - particle.radius) / 2)) # we just use radius as height..
if blurRadius % 2 == 0:
blurRadius += 1
particleImg: np.ndarray = self.fakeFilter.getParticleImage(particle, blurRadius=blurRadius)
particles = self._addParticleImg(particles, particleImg, (pixelX, pixelY))
for i in range(3):
particles[:, :, i] = np.flipud(particles[:, :, i])
self.currentImage = particles
def _addParticleImg(self, allParts: np.ndarray, curPart: np.ndarray, position: Tuple[int, int]) -> np.ndarray:
"""
Adds the current particle image to the image with all particles.
:param allParts: image with all particles
:param curPart: image of the current particle
:param position: x, y of the particle center
"""
def overlayImages(img1: np.ndarray, img2: np.ndarray) -> np.ndarray:
assert img1.shape == img2.shape
merge = np.zeros_like(img1)
mask = np.sum(img1, axis=2) > 128
merge[mask] = img1[mask]
merge[~mask] = img2[~mask]
return np.uint8(merge)
particleFits, ranges = getParticleOverlapRanges(allParts, curPart, position)
if particleFits:
img1 = allParts[ranges.fullStartY:ranges.fullEndY, ranges.fullStartX:ranges.fullEndX, :]
img2 = curPart[ranges.subStartY:ranges.subEndY, ranges.subStartX:ranges.subEndX, :]
assert img1.shape == img2.shape
blend = overlayImages(img1, img2)
allParts[ranges.fullStartY:ranges.fullEndY, ranges.fullStartX:ranges.fullEndX, :] = blend
return allParts
def _getParticlePixelCoords(self, position: Tuple[float, float], particle: FakeParticle) -> Tuple[int, int]:
"""Converts the absolute particle x,y coordinates into image pixel coordinates
:param position: x and y coordinates of stage center (in µm)
:param particle: the particle in consideration
:return (x, y): pixel Coordinates of x and y of particle in image
"""
imgHeightMicrons, imgWidthMicrons = self.imgDims[1] * self.pixelscale, self.imgDims[0] * self.pixelscale
upperLeftX, upperLeftY = position[0] - imgWidthMicrons/2, position[1] - imgHeightMicrons/2
particlePixelX: int = int(round((particle.x - upperLeftX) / self.pixelscale))
particlePixelY: int = int(round((particle.y - upperLeftY) / self.pixelscale))
return particlePixelX, particlePixelY
class FakeFilter:
def __init__(self):
self.numParticles: int = 500
self.xRange: Tuple[float, float] = (-3000, 3000) # min and max of x Dimensions (in µm)
self.yRange: Tuple[float, float] = (-3000, 3000) # min and max of x Dimensions (in µm)
self.particleSizeRange: Tuple[float, float] = (5, 50) # min and max of particle radius (in µm)
self.numIndParticles: int = 10 # number of individual particles presets to generate
self.numBlurSteps: int = 7 # number of blur steps to simulate (additionally to image without any blur)
self.maxBlurSize: int = 51 # highest blur radius
self.baseImageSize: int = 100
self.presetParticles: List[dict] = self._generagePresetParticles()
self.particles: List[FakeParticle] = []
self._generateParticles()
def getParticleImage(self, particle: FakeParticle, blurRadius: int = 0) -> np.ndarray:
def getSizeCorrectedBlurRadius(blurRad: int, scaling: float) -> int:
"""If the images are scaled down later, we want to pick a stronger blurred image at this point"""
newRad: int = int(round(blurRad / scaling))
if newRad > 0 and newRad % 2 == 0:
newRad += 1
return newRad
blurredImages: dict = self.presetParticles[particle.randImageIndex]
scaleFac: float = particle.radius / (self.baseImageSize / 2)
blurRadius = getSizeCorrectedBlurRadius(blurRadius, scaleFac)
if blurRadius == 0:
img: np.ndarray = blurredImages[0]
else:
availableRadii: np.ndarray = np.array([i for i in blurredImages.keys()])
closestRadius = availableRadii[np.argmin(abs(availableRadii-blurRadius))]
img: np.ndarray = blurredImages[closestRadius]
return cv2.resize(img, None, fx=scaleFac, fy=scaleFac)
def _generagePresetParticles(self) -> List[dict]:
"""
The list contains a dict for each particle variation, containing the image in different blur stages.
The key of that dicts is the used blur radius
"""
# TODO: Consider moving that into a separate thread.. it takes quite some seconds at startup..
def getBlurLevels() -> np.ndarray:
levels: np.ndarray = np.linspace(1, self.maxBlurSize, self.numBlurSteps)
for j in range(len(levels)):
levels[j] = int(round(levels[j]))
if levels[j] % 2 == 0:
levels[j] += 1
return levels
baseSize = self.baseImageSize
presetParticles: List[dict] = []
np.random.seed(42)
for i in range(self.numIndParticles):
baseImg: np.ndarray = np.zeros((baseSize + self.maxBlurSize, baseSize + self.maxBlurSize, 3))
centerX = centerY = int(round(baseImg.shape[0] / 2))
radius: int = int(round(baseSize/2))
rgb: np.ndarray = 0.7 + np.random.rand(3) * 0.3 # desaturate a bit..
randColor = (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255))
cv2.circle(baseImg, (centerX, centerY), radius, randColor, thickness=-1)
baseImg = np.uint8(addTexture(baseImg))
newParticleDict: dict = {0: baseImg}
for radius in getBlurLevels():
radius = int(radius)
newParticleDict[radius] = cv2.GaussianBlur(baseImg, (radius, radius), sigmaX=0)
presetParticles.append(newParticleDict)
return presetParticles
def _generateParticles(self) -> None:
self.particles = []
np.random.seed(42)
for i in range(self.numParticles):
rands: np.ndarray = np.random.rand(3)
newParticle: FakeParticle = FakeParticle()
newParticle.x = self.xRange[0] + rands[0]*(self.xRange[1]-self.xRange[0])
newParticle.y = self.yRange[0] + rands[1] * (self.yRange[1] - self.yRange[0])
newParticle.radius = self.particleSizeRange[0] + rands[2] * (
self.particleSizeRange[1] - self.particleSizeRange[0])
newParticle.randImageIndex = np.random.randint(0, self.numIndParticles)
self.particles.append(newParticle)
# -*- coding: utf-8 -*-
"""
GEPARD - Gepard-Enabled PARticle Detection
Copyright (C) 2018 Lars Bittrich and Josef Brandt, Leibniz-Institut für
Polymerforschung Dresden e. V. <bittrich-lars@ipfdd.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program, see COPYING.
If not, see <https://www.gnu.org/licenses/>.
Simulates a Microscope Stage with Camera.
"""
import os
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
import cv2
import json
import numpy as np
from typing import List
try:
from .imageGenerator import FakeCamera
except ImportError:
from imageGenerator import FakeCamera
class SimulatedStage(object):
def __init__(self, ui: bool = True):
super(SimulatedStage, self).__init__()
self.currentpos: List[float] = [0.0, 0.0, 0.0] # µm pos in x, y and z
self.offset: List[float] = [0.0, 0.0, 0.0] # µm offset in x, y and z
self.rotMatrix: np.ndarray = np.zeros((3, 3))
self.camera: FakeCamera = FakeCamera()
self.camera.updateImageAtPosition(self.currentpos)
self.filepath: str = self._getFilePath()
self.uiEnabled = ui
if self.uiEnabled:
self.ui: SimulatedStageUI = SimulatedStageUI(self)
self.ui.show()
def getCurrentCameraImage(self) -> np.ndarray:
return self.camera.currentImage
def moveToPosition(self, x: float, y: float, z: float):
self.currentpos = [x, y, z]
self.saveConfigToFile()
self.camera.updateImageAtPosition(self.currentpos)
if self.uiEnabled:
self.ui.updateCameraImage()
self.ui.updateStageCoords()
def connect(self) -> None:
self.updateConfigFromFile()
if self.uiEnabled:
self.ui.setEnabled(True)
def disconnect(self) -> None:
self.saveConfigToFile()
if self.uiEnabled:
self.ui.setEnabled(False)
def updateConfigFromFile(self) -> None:
if os.path.exists(self.filepath):
with open(self.filepath, 'r') as fp:
try:
configHasChanged: bool = self._dictToConfig(json.load(fp))
except json.JSONDecodeError:
# TODO: get reference to logger and log info that loading config failed..
print('failed updating simulated stage state from file..')
return
if configHasChanged:
self.camera.updateImageAtPosition(self.currentpos)
if self.uiEnabled:
self.ui.updateCameraImage()
self.ui.updateStageCoords()
def saveConfigToFile(self) -> None:
if self.filepath != '':
with open(self.filepath, 'w') as fp:
json.dump(self._configToDict(), fp)
def _getFilePath(self) -> str:
self.app: QtWidgets.QApplication = QtWidgets.QApplication(sys.argv) # has to be an instance attribute :/
self.app.setApplicationName("GEPARD")
path: str = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppLocalDataLocation)
return os.path.join(path, 'simulatedStageConfig.txt')
def _configToDict(self) -> dict:
return {'currentPos': self.currentpos}
def _dictToConfig(self, inputDict: dict) -> bool:
configChanged: bool = False
if self.currentpos != inputDict['currentPos']:
self.currentpos = inputDict['currentPos']
configChanged = True
return configChanged
class SimulatedStageUI(QtWidgets.QWidget):
def __init__(self, stageParent: SimulatedStage):
super(SimulatedStageUI, self).__init__()
self.stageParent: SimulatedStage = stageParent
self.updateTimer: QtCore.QTimer = QtCore.QTimer()
self.updateTimer.setSingleShot(False)
self.updateTimer.timeout.connect(self.stageParent.updateConfigFromFile)
self.updateTimer.start(500)
self.setWindowTitle('Simulated Microscope Stage')
coordsGroup: QtWidgets.QGroupBox = QtWidgets.QGroupBox('Stage Coordinates')
coordsLayout: QtWidgets.QHBoxLayout = QtWidgets.QHBoxLayout()
coordsGroup.setLayout(coordsLayout)
self.labelX: QtWidgets.QLabel = QtWidgets.QLabel()
self.labelY: QtWidgets.QLabel = QtWidgets.QLabel()
self.labelZ: QtWidgets.QLabel = QtWidgets.QLabel()
for label in [self.labelX, self.labelY, self.labelZ]:
coordsLayout.addWidget(label)
self.camView: QtWidgets.QGraphicsView = self._createCamView()
self.stepSizeSpinbox: QtWidgets.QSpinBox = QtWidgets.QSpinBox()
self.stageControls: QtWidgets.QGroupBox = self._createControls()
layout: QtWidgets.QHBoxLayout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
leftColumn: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout()
leftColumn.addWidget(coordsGroup)
leftColumn.addWidget(QtWidgets.QLabel('Camera Image'))
leftColumn.addWidget(self.camView)
leftColumn.addWidget(self.stageControls)
layout.addLayout(leftColumn)
rightColumn: QtWidgets.QGroupBox = self._getExtrasColumn()
layout.addWidget(rightColumn)
self.updateCameraImage()
self.updateStageCoords()
def _createCamView(self) -> QtWidgets.QGraphicsView:
camView: QtWidgets.QGraphicsView = QtWidgets.QGraphicsView()
camView.item = QtWidgets.QGraphicsPixmapItem()
scene = QtWidgets.QGraphicsScene(camView)
scene.addItem(camView.item)
camView.setScene(scene)
camView.scale(1.0, 1.0)
return camView
def _createControls(self) -> QtWidgets.QGroupBox:
def makeBtnLambda(_btn: QtWidgets.QPushButton):
return lambda: self._moveBtnPressed(_btn)
controls: QtWidgets.QGroupBox = QtWidgets.QGroupBox('Stage Controls')
layout: QtWidgets.QGridLayout = QtWidgets.QGridLayout()
controls.setLayout(layout)
stepSizeLayout: QtWidgets.QHBoxLayout = QtWidgets.QHBoxLayout()
stepSizeLayout.addStretch()
stepSizeLayout.addWidget(QtWidgets.QLabel('Set Step Size (µm): '))
stepSizeLayout.addWidget(self.stepSizeSpinbox)
stepSizeLayout.addStretch()
self.stepSizeSpinbox.setMinimum(0)
self.stepSizeSpinbox.setValue(10)
self.stepSizeSpinbox.setMaximum(100000)
self.stepSizeSpinbox.setSingleStep(50)
self.stepSizeSpinbox.setMaximumWidth(75)
xyGroup: QtWidgets.QGroupBox = QtWidgets.QGroupBox('XY Controls')
xyLayout: QtWidgets.QGridLayout = QtWidgets.QGridLayout()
xyGroup.setLayout(xyLayout)
upBtn: QtWidgets.QPushButton = QtWidgets.QPushButton('UP')
downBtn: QtWidgets.QPushButton = QtWidgets.QPushButton('DOWN')
leftBtn: QtWidgets.QPushButton = QtWidgets.QPushButton('LEFT')
rightBtn: QtWidgets.QPushButton = QtWidgets.QPushButton('RIGHT')
for btn in [upBtn, downBtn, leftBtn, rightBtn]:
btn.clicked.connect(makeBtnLambda(btn))
xyLayout.addWidget(upBtn, 0, 1)
xyLayout.addWidget(leftBtn, 1, 0)
xyLayout.addWidget(rightBtn, 1, 2)
xyLayout.addWidget(downBtn, 2, 1)
zGroup: QtWidgets.QGroupBox = QtWidgets.QGroupBox('Z Controls')
zLayout: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout()
zGroup.setLayout(zLayout)
zUpBtn: QtWidgets.QPushButton = QtWidgets.QPushButton('HIGHER')
zDownBtn: QtWidgets.QPushButton = QtWidgets.QPushButton('LOWER')
for btn in [zUpBtn, zDownBtn]:
btn.clicked.connect(makeBtnLambda(btn))
zLayout.addWidget(btn)
layout.addLayout(stepSizeLayout, 0, 0, 1, 2)
layout.addWidget(xyGroup, 1, 0)
layout.addWidget(zGroup, 1, 1)
return controls
def _getExtrasColumn(self) -> QtWidgets.QGroupBox:
def makePresetBtnLambda(pressedBtn: QtWidgets.QPushButton):
return lambda: self._moveToPresetPosition(pressedBtn)
extrasGroup: QtWidgets.QGroupBox = QtWidgets.QGroupBox('Extra Operations')
extrasLayout: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout()
extrasGroup.setLayout(extrasLayout)
presetPositionsGroup: QtWidgets.QGroupBox = QtWidgets.QGroupBox('Move to preset positions')
presetPositionsLayout: QtWidgets.QGridLayout = QtWidgets.QGridLayout()
presetPositionsGroup.setLayout(presetPositionsLayout)
upperLeftBtn: QtWidgets.QPushButton = QtWidgets.QPushButton('UpperLeft')
lowerLeftBtn: QtWidgets.QPushButton = QtWidgets.QPushButton('LowerLeft')
upperRightBtn: QtWidgets.QPushButton = QtWidgets.QPushButton('UpperRight')
lowerRightBtn: QtWidgets.QPushButton = QtWidgets.QPushButton('LowerRight')
centerBtn: QtWidgets.QPushButton = QtWidgets.QPushButton('Center')
for btn in [upperLeftBtn, upperRightBtn, lowerLeftBtn, lowerRightBtn, centerBtn]:
btn.clicked.connect(makePresetBtnLambda(btn))
presetPositionsLayout.addWidget(upperLeftBtn, 0, 0)
presetPositionsLayout.addWidget(upperRightBtn, 0, 2)
presetPositionsLayout.addWidget(centerBtn, 1, 1)
presetPositionsLayout.addWidget(lowerLeftBtn, 2, 0)
presetPositionsLayout.addWidget(lowerRightBtn, 2, 2)
coordGroup: QtWidgets.QGroupBox = QtWidgets.QGroupBox('Modify Coordinate System')
coordLayout: QtWidgets.QFormLayout = QtWidgets.QFormLayout()
coordGroup.setLayout(coordLayout)
extrasLayout.addWidget(presetPositionsGroup)
extrasLayout.addWidget(coordGroup)
return extrasGroup
def _moveBtnPressed(self, btn: QtWidgets.QPushButton) -> None:
label: str = btn.text()
newPos: List[float] = self.stageParent.currentpos
if label == 'UP':
newPos[1] += self.stepSizeSpinbox.value()
elif label == 'DOWN':
newPos[1] -= self.stepSizeSpinbox.value()
elif label == 'LEFT':
newPos[0] -= self.stepSizeSpinbox.value()
elif label == 'RIGHT':
newPos[0] += self.stepSizeSpinbox.value()
elif label == 'HIGHER':
newPos[2] += self.stepSizeSpinbox.value()
elif label == 'LOWER':
newPos[2] -= self.stepSizeSpinbox.value()
self.stageParent.moveToPosition(newPos[0], newPos[1], newPos[2])
def _moveToPresetPosition(self, btn: QtWidgets.QPushButton, stepSize: float = 1000) -> None:
label: str = btn.text()
newPos: List[float] = [0.0, 0.0, 0.0]
if label == 'UpperLeft':
newPos[0] -= stepSize
newPos[1] += stepSize
elif label == 'UpperRight':
newPos[0] += stepSize
newPos[1] += stepSize
elif label == 'LowerLeft':
newPos[0] -= stepSize
newPos[1] -= stepSize
elif label == 'LowerRight':
newPos[0] += stepSize
newPos[1] -= stepSize
elif label == 'Center':
pass