Commit 819efb02 authored by Robert's avatar Robert Committed by Robert Ohmacht

-initial integration of ScenePyramid from GepardEvaluation into Gepard

parent 1fff5fef
......@@ -135,6 +135,9 @@ class DataSet(object):
self.coordinatetransform = None # if imported form extern source coordinate system may be rotated
self.signx = 1.
self.signy = -1.
# tiling parameters
self.pyramidParams = None
# parameters specifically for raman scan
self.pshift = None # shift of raman scan position relative to image center
......@@ -169,6 +172,12 @@ class DataSet(object):
def __eq__(self, other):
return recursiveDictCompare(self.__dict__, other.__dict__)
def getPyramidParams(self):
return self.pyramidParams
def setPyramidParams(self, pyramid_params):
self.pyramidParams = pyramid_params
def getPixelScale(self, mode=None):
if mode is None:
......@@ -275,6 +284,12 @@ class DataSet(object):
self.__dict__.update(loadData(fname).__dict__)
return fname
def getTilePath(self):
scandir = os.path.join(self.path, "tiles")
if not os.path.exists(scandir):
os.mkdir(scandir)
return scandir
def getScanPath(self):
scandir = os.path.join(self.path, "scanimages")
if not os.path.exists(scandir):
......
......@@ -27,6 +27,7 @@ from threading import Thread
from .segmentation import Segmentation
from .analysis.particleCharacterization import getParticleStatsWithPixelScale, loadZValImageFromDataset
from .errors import InvalidParticleError
from .scenePyramid import ScenePyramid
Nscreen = 1000
......@@ -269,14 +270,15 @@ class ParticleDetectionView(QtWidgets.QWidget):
imageUpdate = QtCore.pyqtSignal(str, name='imageUpdate') #str = 'df' (= darkfield) or 'bf' (=bright field)
detectionFinished = QtCore.pyqtSignal(name='detectionFinished')
def __init__(self, img, dataset, parent=None):
def __init__(self, pyramid: ScenePyramid, dataset, parent=None):
super().__init__(parent, QtCore.Qt.Window)
self.dataset = self.verifySeedpoints(dataset)
self.img = img
self.imgclip = 0,0,0,0
self.pyramid = pyramid
self.img = pyramid.getFullImage(.5)
self.imgclip = 0, 0, 0, 0
self.seg = Segmentation(self.dataset, self)
self.thread = None
self.view = parent
self.view : QtWidgets.QGraphicsView = parent
vbox = QtWidgets.QVBoxLayout()
hbox = QtWidgets.QHBoxLayout()
......@@ -529,33 +531,69 @@ class ParticleDetectionView(QtWidgets.QWidget):
self.updateImageSeeds()
def setImageCenter(self, center=None):
'''
as more than the tiles are part of the scene
bounding rect may be bigger than just a rect around the tiles
tiles should be grouped
'''
width, height = self.pyramid.getBoundingRectDim()
if center is None:
centerx = self.img.shape[1]//2
centery = self.img.shape[0]//2
centerx = width//2
centery = height//2
else:
centerx, centery = center
if self.img.shape[0]<Nscreen:
centery = self.img.shape[0]//2
if self.img.shape[1]<Nscreen:
centerx = self.img.shape[1]//2
if height < Nscreen:
centery = height//2
if width < Nscreen:
centerx = width//2
if not self.drag:
self.lastcenter = centerx, centery
n1, n2 = int(centery-Nscreen//2),int(centery+Nscreen//2+1)
m1, m2 = int(centerx-Nscreen//2),int(centerx+Nscreen//2+1)
if n1<0:
n2 -= n1
n1 = 0
if n2>self.img.shape[0]:
n1 -= n2-self.img.shape[0]
if n1<0: n1 = 0
n2 = self.img.shape[0]
if m1<0:
m2 -= m1
m1 = 0
if m2>self.img.shape[1]:
m1 -= m2-self.img.shape[1]
if m1<0: m1 = 0
m2 = self.img.shape[1]
y1, y2 = centery-Nscreen//2, centery+Nscreen//2+1
x1, x2 = centerx-Nscreen//2, centerx+Nscreen//2+1
if y1 < 0:
y2 -= y1
y1 = 0
if y2 > height:
y1 -= y2-height
if y1 < 0:
y1 = 0
y2 = height
if x1 < 0:
x2 -= x1
x1 = 0
if x2 > width:
x1 -= x2-width
if x1 < 0:
x1 = 0
x2 = width
self.imgclip = int(y1), int(y2), int(x1), int(x2)
self.subimg, pix = self.pyramid.getSubImage(self.imgclip)
'''
img = QtGui.QImage(x2 - x1, y2 - y1, QtGui.QImage.Format_RGB888)
self.view.scene().render(
QtGui.QPainter(img),
QtCore.QRectF(0, 0, img.width(), img.height()),
#QtCore.QRectF(x1 + x0, y1 + y0, x2 - x1, y2 - y1)
QtCore.QRectF(x1, y1, x2 - x1, y2 - y1)
)
#pix = self.view.grab(QtCore.QRect(x1 + x0, y1 + y0, x2 - x1, y2 - y1))
# @see https://stackoverflow.com/a/11399959
#img = pix.toImage().convertToFormat(QtGui.QImage.Format_RGB888)
ptr = img.bits()
#ptr.setsize(img.byteCount())
ptr.setsize(img.height() * img.width() * 3)
self.subimg = np.asarray(ptr).reshape(img.height(), img.width(), 3)
pix = QtGui.QPixmap()
pix.convertFromImage(img)
'''
'''
sub = self.img[n1:n2,m1:m2,:].copy()
self.imgclip = n1,n2,m1,m2
self.subimg = sub
......@@ -564,6 +602,7 @@ class ParticleDetectionView(QtWidgets.QWidget):
pix = QtGui.QPixmap()
pix.convertFromImage(QtGui.QImage(sub.data, width, height,
bytesPerLine, QtGui.QImage.Format_RGB888))
'''
self.imglabel.clearData()
self.imglabel.setPixmap(pix)
self.updateImageSeeds()
......
......@@ -27,8 +27,9 @@ from .helperfunctions import cv2imread_fix, cv2imwrite_fix
from .analysis.particleContainer import ParticleContainer
from .analysis import particleCharacterization as pc
from .errors import InvalidParticleError
from .scenePyramid import ScenePyramid
currentVersion = 4
currentVersion = 5
def legacyConversion(dset, recreatefullimage=False):
if dset.version==0:
......@@ -110,6 +111,12 @@ def legacyConversion(dset, recreatefullimage=False):
removeLegacyAttributes(dset)
dset.version = 4
dset.save()
if dset.version == 4:
print("Converting legacy version 4 to 5")
ScenePyramid.createFromFullImage(dset)
dset.version = 5
dset.save()
# add later conversion for higher version numbers here
......
......@@ -106,7 +106,7 @@ class BackGroundManager(QtWidgets.QWidget):
p0 = [points[:,0].min(), points[:,1].max()]
p1 = [points[:,0].max(), points[:,1].min()]
reply = QtWidgets.QMessageBox.question(self, 'Message',f"The stage will move {round(3*width)} in x and {round(3*height)} in y.\nContinue?",
reply = QtWidgets.QMessageBox.question(self, 'Message', f"The stage will move {round(3*width)} in x and {round(3*height)} in y.\nContinue?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
fullimg = None
......
......@@ -31,6 +31,7 @@ from time import time
import datetime
from .opticalbackground import BackGroundManager
from .zlevelsetter import ZLevelSetter
from .scenePyramid import ScenePyramid
def scan(path, sol, zpositions, grid, controlclass, dataqueue,
stopevent, logpath='', ishdr=False):
......@@ -84,10 +85,10 @@ def scan(path, sol, zpositions, grid, controlclass, dataqueue,
def subtractBackground(image, background):
avg = np.mean(background)
subtracted = np.clip(np.array(image - background + avg, dtype = np.uint8), 0, 255)
subtracted = np.clip(np.array(image - background + avg, dtype=np.uint8), 0, 255)
return subtracted
def loadAndPasteImage(srcnames, fullimage, fullzval, width, height,
def loadAndPasteImage(srcnames, pyramid, fullzval, width, height,
rotationvalue, p0, p1, p, background=None):
colimgs = []
for name in srcnames:
......@@ -102,15 +103,22 @@ def loadAndPasteImage(srcnames, fullimage, fullzval, width, height,
c, s = np.cos(np.radians(rotationvalue)), np.sin(np.radians(rotationvalue))
dx, dy = (x-p0[0])/width*img.shape[1], (p0[1]-y)/height*img.shape[0]
M = np.float32([[c,s,dx],[-s,c,dy]])
if fullimage is not None:
cv2.warpAffine(img, M, (Nx, Ny), fullimage, borderMode=cv2.BORDER_TRANSPARENT)
cv2.warpAffine(zval, M, (Nx, Ny), fullzval, borderMode=cv2.BORDER_TRANSPARENT)
dst = fullimage
zval = fullzval
else:
dst = cv2.warpAffine(img, M, (Nx, Ny))
zval = cv2.warpAffine(zval, M, (Nx, Ny))
M = np.float32([[c, s, dx], [-s, c, dy]])
dst = None
if pyramid is not None:
if fullzval is not None:
cv2.warpAffine(zval, M, (Nx, Ny), fullzval, borderMode=cv2.BORDER_TRANSPARENT)
zval = fullzval
else:
zval = cv2.warpAffine(zval, M, (Nx, Ny))
pyramid.addSrcTile(
img,
np.float32([[c, s, 0], [-s, c, 0]]),
(dx, dy),
(Nx, Ny)
)
return dst, zval
......@@ -231,12 +239,13 @@ class OpticalScan(QtWidgets.QWidget):
def __init__(self, ramanctrl, dataset, logpath='', parent=None):
super().__init__(parent, QtCore.Qt.Window)
self.logpath = logpath
self.view = parent
self.view: QtWidgets.QGraphicsView = parent
mainLayout = QtWidgets.QVBoxLayout()
pointgroup = QtWidgets.QGroupBox("Point coordinates [µm]", self)
self.ramanctrl = ramanctrl
self.dataset = dataset
self.pyramid: ScenePyramid = None
self.positions = []
self.process = None
self.points = PointCoordinates(5, self.ramanctrl, self)
......@@ -425,6 +434,9 @@ class OpticalScan(QtWidgets.QWidget):
self.boundaryUpdate.emit()
self.prun.setEnabled(True)
def setPyramid(self, pyramid):
self.pyramid = pyramid
def resetDataset(self, ds):
self.dataset = ds
self.points.createWidgets(5, list(zip(ds.fitindices,ds.fitpoints)))
......@@ -482,22 +494,34 @@ class OpticalScan(QtWidgets.QWidget):
Nx, Ny = int((p1[0]-p0[0]+width)/width*img.shape[1]), int((p0[1]-p1[1]+height)/height*img.shape[0]) + 10 # + 10 because of rotation and hopefully it will be small
c, s = np.cos(np.radians(rotationvalue)), np.sin(np.radians(rotationvalue))
dx, dy = (x-p0[0])/width*img.shape[1], (p0[1]-y)/height*img.shape[0]
M = np.float32([[c,s,dx],[-s,c,dy]])
dst = cv2.warpAffine(img, M, (Nx, Ny))
if self.view.imgdata is not None and self.dataset.lastpos is not None:
lp = self.dataset.lastpos
dx, dy = (lp[0]-p0[0])/width*img.shape[1], (p0[1]-lp[1])/height*img.shape[0]
full = self.view.imgdata
M = np.float32([[1,0,dx],[0,1,dy]])
try:
full = cv2.warpAffine(full, M, (Nx, Ny)) #fails, if image dimensions are >32767x32767px...
dst = cv2.max(full, dst)
except:
QtWidgets.QMessageBox.critical(self, 'Error', 'Image is too large\nSelect smaller region.')
raise
return
M = np.float32([[c, s, dx], [-s, c, dy]])
#dst = cv2.warpAffine(img, M, (Nx, Ny))
M_rot = np.float32([[c, s, 0], [-s, c, 0]])
img_rot = cv2.warpAffine(img, M_rot, (img.shape[1], img.shape[0] + 10))
# calc new pixel start coords for tile
# @see https://stackoverflow.com/a/43166421/9880753
c_dest = cv2.transform(np.float32([[(0, 0)]]), np.float32([[c, s, dx], [-s, c, dy]]))
self.pyramid.addSrcTileSimple(
img_rot,
(c_dest[0][0][0], c_dest[0][0][1]),
p0
)
#if self.view.imgdata is not None and self.dataset.lastpos is not None:
# lp = self.dataset.lastpos
# dx, dy = (lp[0]-p0[0])/width*img.shape[1], (p0[1]-lp[1])/height*img.shape[0]
# full = self.view.imgdata
# M = np.float32([[1,0,dx],[0,1,dy]])
# try:
# full = cv2.warpAffine(full, M, (Nx, Ny)) #fails, if image dimensions are >32767x32767px...
# dst = cv2.max(full, dst)
# except:
# QtWidgets.QMessageBox.critical(self, 'Error', 'Image is too large\nSelect smaller region.')
# raise
# return
self.view.imgdata = dst
#self.view.imgdata = dst
self.dataset.lastpos = p0
self.dataset.maxdim = p0 + p1
self.dataset.readin = False
......@@ -621,7 +645,8 @@ class OpticalScan(QtWidgets.QWidget):
self.progressbar.setEnabled(True)
self.progressbar.setRange(0, len(self.dataset.grid))
self.progressbar.setValue(0)
self.view.imgdata = None
#self.view.imgdata = None
self.pyramid.resetScene()
self.view.blockUI()
grid = np.asarray(self.dataset.grid)
p0 = [grid[:,0].min(), grid[:,1].max()]
......@@ -657,7 +682,7 @@ class OpticalScan(QtWidgets.QWidget):
else:
background_img = None
self.view.imgdata, self.dataset.zvalimg = loadAndPasteImage(names, self.view.imgdata, self.dataset.zvalimg, width, height,
self.view.imgdata, self.dataset.zvalimg = loadAndPasteImage(names, self.pyramid, self.dataset.zvalimg, width, height,
rotationvalue, p0, p1, p, background=background_img)
self.progressbar.setValue(i+1)
if i>3:
......@@ -665,15 +690,19 @@ class OpticalScan(QtWidgets.QWidget):
ttot = timerunning*Ngrid/(i+1)
time2go = ttot - timerunning
self.progresstime.setText(self.timelabeltext + str(datetime.timedelta(seconds=round(time2go))))
self.imageUpdate.emit(self.view.microscopeMode)
# reload image in sampleview, calls loadPixmap
# not needed anymore as the scene gets manipulated directly via self.pyramid
#self.imageUpdate.emit(self.view.microscopeMode)
if i==Ngrid-1:
cv2imwrite_fix(self.dataset.getImageName(), cv2.cvtColor(self.view.imgdata, cv2.COLOR_RGB2BGR))
#cv2imwrite_fix(self.dataset.getImageName(), cv2.cvtColor(self.view.imgdata, cv2.COLOR_RGB2BGR))
self.dataset.saveZvalImg()
self.process.join()
self.dataqueue.close()
self.dataqueue.join_thread()
self.pyramid.toDataset()
if self.deleteImgChecker.isChecked():
path = self.dataset.getScanPath()
files = os.listdir(path)
......
# -*- 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/>.
"""
from PyQt5 import QtCore, QtGui, QtWidgets
import numpy as np
import os
import cv2
import time
from .dataset import DataSet, loadData
from .ramancom.ramancontrol import RamanControl, simulatedRaman
from .opticalscan import OpticalScan
from .ramanscanui import RamanScanUI
from .detectionview import ParticleDetectionView
from .analysis.analysisview import ParticleAnalysis
from .zeissimporter import ZeissImporter
from .viewitems import FitPosIndicator, Node, Edge, ScanIndicator, RamanScanIndicator, SegmentationContour, ParticleInfo, SeedPoint
from .helperfunctions import polygoncovering, cv2imread_fix
from .analysis.colorlegend import getColorFromNameWithSeed
from .analysis.particleEditor import ParticleEditor
from .ramancom.configRaman import RamanConfigWin
class SampleView(QtWidgets.QGraphicsView):
ScalingChanged = QtCore.pyqtSignal(float)
ParticleOfIndexSelected = QtCore.pyqtSignal(int)
def __init__(self, logpath):
super(SampleView, self).__init__()
self.logpath = logpath
self.item = QtWidgets.QGraphicsPixmapItem()
self.item.setPos(0, 0)
self.item.setAcceptedMouseButtons(QtCore.Qt.NoButton)
self.scaleFactor = 1.0
scene = QtWidgets.QGraphicsScene(self)
scene.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex)
scene.addItem(self.item)
scene.setBackgroundBrush(QtCore.Qt.darkGray)
self.setScene(scene)
self.setCacheMode(QtWidgets.QGraphicsView.CacheBackground)
self.setViewportUpdateMode(QtWidgets.QGraphicsView.BoundingRectViewportUpdate)
self.setRenderHint(QtGui.QPainter.Antialiasing)
self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorViewCenter)
self.ramanctrl = RamanControl()
self.simulatedRaman = simulatedRaman
self.ramanSwitchNeeded = False
if self.ramanctrl.name == 'RenishawCOM':
#determine, if ramanSwitch is needed:
self.ramanctrl.connect()
if not self.ramanctrl.connected:
QtWidgets.QMessageBox.warning(self, 'Error', 'Please enable Raman Connection')
return
if self.ramanctrl.getImageDimensions(mode='bf')[0] == self.ramanctrl.getImageDimensions(mode='df')[0]:
self.ramanSwitchNeeded = False
else:
self.ramanSwitchNeeded = True
self.ramanctrl.disconnect()
self.drag = None
self.mode = None
self.dataset = None
self.particleEditor = None
self.fititems = []
self.boundaryitems = [[],[]]
self.scanitems = []
self.ramanscanitems = []
self.particleInfoBox = None
self.imgdata = None
self.isblocked = False
self.contourItems = []
self.selectedParticleIndices = []
self.seedPoints = []
self.particlePainter = None
self.detectionwidget = None
self.ramanwidget = RamanScanUI(self.ramanctrl, None, self.logpath, self)
self.ramanwidget.imageUpdate.connect(self.loadPixmap)
self.oscanwidget = OpticalScan(self.ramanctrl, None, self.logpath, self)
self.oscanwidget.imageUpdate.connect(self.loadPixmap)
self.oscanwidget.boundaryUpdate.connect(self.resetBoundary)
self.analysiswidget = None
self.setMinimumSize(600, 600)
self.darkenPixmap = False
self.microscopeMode = None
self.update()
def takeScreenshot(self):
#TODO:
#LIMIT SCREENSHOT TO ACTUAL VIEWSIZE OF LOADED IMAGE...
#hide scrollbars
self.setHorizontalScrollBarPolicy(1)
self.setVerticalScrollBarPolicy(1)
#capture screen
screen = QtWidgets.QApplication.primaryScreen()
self.repaint()
screenshot = screen.grabWindow(self.winId())
#unhide scrollbars
self.setHorizontalScrollBarPolicy(0)
self.setVerticalScrollBarPolicy(0)
fname = self.dataset.path + '/screenshot.png'
validFileName = False
incr = 1
while not validFileName:
if not os.path.exists(fname):
validFileName = True
else:
fname = self.dataset.path + '/screenshot ({}).png'.format(incr)
incr += 1
screenshot.save(fname , 'png')
QtWidgets.QMessageBox.about(self, 'Message', 'Saved as {} to project directory.'.format(fname.split('/')[-1]))
def closeEvent(self, event):
reply = QtWidgets.QMessageBox.question(self, 'Message',
"Do you really want to quit?", QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
self.disconnectRaman()
self.saveDataSet()
event.accept()
self.oscanwidget.close()
if self.detectionwidget is not None:
self.detectionwidget.close()
if self.analysiswidget is not None:
self.analysiswidget.close()
self.ramanwidget.close()
else:
event.ignore()
def configureRamanControl(self):
"""
Launches a window for updating Raman instrument configuration.
:return:
"""
self.configWin = RamanConfigWin(self)
self.configWin.show()
def saveDataSet(self):
if self.dataset is not None:
self.dataset.save()
@QtCore.pyqtSlot()
def zoomIn(self):
self.zoomDisplay(1.25)
@QtCore.pyqtSlot()
def zoomOut(self):
self.zoomDisplay(0.8)
@QtCore.pyqtSlot()
def normalSize(self):
self.scaleFactor = 1.0
self.setTransform(QtGui.QTransform.fromScale(1., 1.))
self.announceScaling()
@QtCore.pyqtSlot()
def fitToWindow(self):
"""
Fits the window to show the entire sample.
:return:
"""
brect = self.item.sceneBoundingRect()
self.fitInView(0, 0, brect.width(), brect.height(), QtCore.Qt.KeepAspectRatio)
self.scaleFactor = self.transform().m11()
self.announceScaling()
def switchMode(self, mode, loadnew=False):
"""
Switch the gepard to another module
:return:
"""
if mode is None:
return
assert mode in ["OpticalScan", "ParticleDetection", "RamanScan", "ParticleAnalysis"]
print("switching to mode:", mode, flush=True)
self.oscanwidget.setVisible(False)
# if self.detectionwidget is not None:
# self.detectionwidget.close()
# self.detectionwidget = None
self.ramanwidget.setVisible(False)
self.mode = mode
self.loadPixmap(self.microscopeMode)
if mode == "OpticalScan":
self.oscanwidget.setVisible(True)
self.oscanwidget.resetDataset(self.dataset)
elif mode == "ParticleDetection":
if self.detectionwidget is None:
print('creating new detect window')
self.detectionwidget = ParticleDetectionView(self.imgdata, self.dataset, self)
self.detectionwidget.imageUpdate.connect(self.detectionUpdate)
self.detectionwidget.detectionFinished.connect(self.activateMaxMode)
self.updateSeedPointMarkers()
self.detectionwidget.show()
elif mode == "RamanScan":
self.removeSeedPointMarkers()
self.ramanwidget.resetDataset(self.dataset)
self.ramanwidget.setVisible(True)
elif mode == "ParticleAnalysis":
self.removeSeedPointMarkers()
if self.ramanwidget.isVisible():
self.ramanwidget.setVisible(False)
if self.analysiswidget is None:
print('creating new analysiswidget')
self.analysiswidget = ParticleAnalysis(self.dataset, self)
self.analysiswidget.showMaximized()
self.setupParticleEditor()
else:
print('show maximized already exisiting analysiswidget')
self.analysiswidget.showMaximized()
if self.detectionwidget is not None:
self.detectionwidget.setVisible(False)
#show legend:
self.imparent.legend.show()
if loadnew:
self.fitToWindow()
self.imparent.updateModes(mode, self.getMaxMode())
def open(self, fname):
self.saveDataSet()