# -*- coding: utf-8 -*- """ GEPARD - Gepard-Enabled PARticle Detection Copyright (C) 2018 Lars Bittrich and Josef Brandt, Leibniz-Institut für Polymerforschung Dresden e. V. 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 . """ import os import pickle import numpy as np import cv2 from helperfunctions import cv2imread_fix, cv2imwrite_fix from copy import copy currentversion = 1 def loadData(fname): retds = None with open(fname, "rb") as fp: ds = pickle.load(fp) ds.fname = fname ds.readin = True ds.updatePath() retds = DataSet(fname) retds.version = 0 retds.__dict__.update(ds.__dict__) if retds.version < currentversion: retds.legacyConversion() elif retds.zvalimg=="saved": retds.loadZvalImg() return retds def saveData(dataset, fname): with open(fname, "wb") as fp: # zvalimg is rather large and thus it is saved separately in a tif file # only onces after its creation zvalimg = dataset.zvalimg if zvalimg is not None: dataset.zvalimg = "saved" pickle.dump(dataset, fp, protocol=-1) dataset.zvalimg = zvalimg class DataSet(object): def __init__(self, fname, newProject=False): self.fname = fname # parameters specifically for optical scan self.version = currentversion self.lastpos = None self.maxdim = None self.pixelscale = None # µm / pixel self.imagedim = None # width, height, angle self.fitpoints = [] # manually adjusted positions aquired to define the specimen geometry self.fitindices = [] # which of the five positions in the ui are already known self.boundary = [] # scan boundary computed by a circle around the fitpoints + manual adjustments self.grid = [] # scan grid positions for optical scan self.zpositions = [] # z-positions for optical scan self.heightmap = None self.zvalimg = None # parameters specifically for raman scan self.pshift = None # shift of raman scan position relative to image center self.seedpoints = np.array([]) self.seeddeletepoints = np.array([]) self.detectParams = {'points': np.array([[50,0],[100,200],[200,255]]), 'contrastcurve': True, 'blurRadius': 9, 'threshold': 0.2, 'maxholebrightness': 0.5, 'erodeconvexdefects': 0, 'minparticlearea': 20, 'minparticledistance': 20, 'measurefrac': 1, 'compactness': 0.1, 'seedRad': 3} self.ramanpoints = [] self.particlecontours = [] self.particlestats = [] self.ramanscansortindex = None self.ramanscandone = False self.readin = True # a value that is always set to True at loadData # and mark that the coordinate system might be changed in the meantime self.mode = "prepare" if newProject: self.fname = self.newProject(fname) self.updatePath() def saveZvalImg(self): if self.zvalimg is not None: cv2imwrite_fix(self.getZvalImageName(), self.zvalimg) def loadZvalImg(self): if os.path.exists(self.getZvalImageName()): self.zvalimg = cv2imread_fix(self.getZvalImageName(), cv2.IMREAD_GRAYSCALE) def legacyConversion(self, recreatefullimage=False): if self.version==0: print("Converting legacy version 0 to 1") print("This may take some time") # local imports as these functions are only needed for the rare occasion of legacy conversion from opticalscan import loadAndPasteImage # try to load png and check for detection contours buggyimage = recreatefullimage if not buggyimage and os.path.exists(self.getLegacyImageName()): img = cv2imread_fix(self.getLegacyImageName()) Nc = len(self.particlecontours) if Nc>0: contour = self.particlecontours[Nc//2] contpixels = img[contour[:,0,1],contour[:,0,0]] if np.all(contpixels[:,1]==255) and np.all(contpixels[:,2]==0) \ and np.all(contpixels[:,0]==0): buggyimage = True if not buggyimage: cv2imwrite_fix(self.getImageName(), img) del img if buggyimage: print("recreating fullimage from grid data") imgdata = None zvalimg = None Ngrid = len(self.grid) width, height, rotationvalue = self.imagedim p0, p1 = self.maxdim[:2], self.maxdim[2:] for i in range(Ngrid): print(f"Processing image {i+1} of {Ngrid}") names = [] for k in range(len(self.zpositions)): names.append(os.path.join(self.getScanPath(), f"image_{i}_{k}.bmp")) p = self.grid[i] imgdata, zvalimg = loadAndPasteImage(names, imgdata, zvalimg, width, height, rotationvalue, p0, p1, p) self.zvalimg = zvalimg cv2imwrite_fix(self.getImageName(), cv2.cvtColor(imgdata, cv2.COLOR_RGB2BGR)) del imgdata self.saveZvalImg() if "particleimgs" in self.__dict__: del self.particleimgs self.version = 1 #os.remove(self.getLegacyImageName()) #os.remove(self.getLegacyDetectImageName()) #self.save() # add later conversion for higher version numbers here def getSubImage(self, img, index, draw=True): contour = self.particlecontours[index] x0, x1 = contour[:,0,0].min(), contour[:,0,0].max() y0, y1 = contour[:,0,1].min(), contour[:,0,1].max() subimg = img[y0:y1+1,x0:x1+1].copy() if draw: cv2.drawContours(subimg, [contour], -1, (0,255,0), 1) return subimg def getZval(self, pixelpos): assert self.zvalimg is not None zp = self.zvalimg[round(pixelpos[1]), round(pixelpos[0])] z0, z1 = self.zpositions.min(), self.zpositions.max() return zp/255.*(z1-z0) + z0 def mapHeight(self, x, y): assert not self.readin assert self.heightmap is not None return self.heightmap[0]*x + self.heightmap[1]*y + self.heightmap[2] def mapToPixel(self, p, force=False): if not force: assert not self.readin p0 = copy(self.lastpos) p0[0] -= self.imagedim[0]/2 p0[1] += self.imagedim[1]/2 return (p[0] - p0[0])/self.pixelscale, (p0[1] - p[1])/self.pixelscale def mapToLength(self, pixelpos, force=False): if not force: assert not self.readin p0 = copy(self.lastpos) p0[0] -= self.imagedim[0]/2 p0[1] += self.imagedim[1]/2 return (pixelpos[0]*self.pixelscale + p0[0]), (p0[1] - pixelpos[1]*self.pixelscale) def mapToLengthRaman(self, pixelpos, noz=False): p0x, p0y = self.mapToLength(pixelpos) x, y = p0x + self.pshift[0], p0y + self.pshift[1] z = None if not noz: z = self.mapHeight(x, y) z += self.getZval(pixelpos) return x, y, z def newProject(self, fname): path = os.path.split(fname)[0] name = os.path.splitext(os.path.basename(fname))[0] newpath = os.path.join(path, name) fname = os.path.join(newpath, name + ".pkl") if not os.path.exists(newpath): os.mkdir(newpath) # for new projects a directory will be created elif os.path.exists(fname): # if this project is already there, load it instead self.__dict__.update(loadData(fname).__dict__) return fname def getScanPath(self): scandir = os.path.join(self.path, "scanimages") if not os.path.exists(scandir): os.mkdir(scandir) return scandir def updatePath(self): self.path = os.path.split(self.fname)[0] self.name = os.path.splitext(os.path.basename(self.fname))[0] def getImageName(self): return os.path.join(self.path, "fullimage.tif") def getZvalImageName(self): return os.path.join(self.path, "zvalues.tif") def getLegacyImageName(self): return os.path.join(self.path, "fullimage.png") def getLegacyDetectImageName(self): return os.path.join(self.path, "detectimage.png") def getDetectImageName(self): raise NotImplementedError("No longer implemented due to change in API") def getTmpImageName(self): return os.path.join(self.path, "tmp.bmp") def saveParticleData(self): if len(self.ramanscansortindex)>0: data = [] for i in self.ramanscansortindex: data.append(list(self.ramanpoints[i])+list(self.particlestats[i])) data = np.array(data) data[:,0], data[:,1], z = self.mapToLengthRaman((data[:,0], data[:,1]), noz=True) data[:,2:7] *= self.pixelscale header = "x [µm], y [µm], length [µm], height [µm], length_ellipse [µm], height_ellipse [µm]" if data.shape[1]>6: header = header + ", area [µm^2]" data[:,6] *= self.pixelscale np.savetxt(os.path.join(self.path, "particledata.txt"), data, header=header) def save(self): saveData(self, self.fname) if __name__ == '__main__': dset = loadData(r'C:\Users\brandt\Desktop\20180723DemoTZW\20180723DemoTZW.pkl') print(dset.detectParams) # dset.seedpoints = np.array([]) # dset.seeddeletepoints = np.array([]) # dset.save()