Location: Stomach Annotator for SPARC @ 9ea33dda840d / digitiser / model.py

Author:
rjag008 <rjag008@auckland.ac.nz>
Date:
2018-08-18 17:01:42+12:00
Desc:
Final Release
Permanent Source URI:
https://models.physiomeproject.org/workspace/51d/rawfile/9ea33dda840d259caf68e26d21300bece4eeff3f/digitiser/model.py

'''
Created on 15/06/2018

@author: rjag008
'''

try:
    from PySide import QtCore
    from PySide.QtGui import QColor, QBrush, QPen 
    signalHandle = QtCore.Signal
except ImportError:
    #from PyQt4 import QtCore
    #from PyQt4.Qt import QColor, QBrush, QPen
    #signalHandle = QtCore.pyqtSignal
    pass
from collections import OrderedDict
from os import path
import pickle
import numpy as np
    
class PaintModel(QtCore.QObject):

    # Signals
    modelChanged = signalHandle()

    def __init__(self):
        QtCore.QObject.__init__(self, None)
        self.reset()

    def reset(self):
        self._pointIndex = 0
        self._backgroundFile = None

        self._points = OrderedDict()
        self.datasetColors = dict()
        self._datasets = OrderedDict()
        self._datasetKey = 0
        self._currentDataset = None
        #Target axis 
        self._axisRefs = {}
        self._mode = 0
        #Current Axis
        self.x0 = 0.0
        self.x1 = 1.0
        self.y0 = 0.0
        self.y1 = 1.0

    def newDataSet(self, background_file = None):
        self.reset()
        if not background_file is None:
            self.addImage(background_file)
        self.addDataset()

    def saveDataset(self, projectFile):
        if len(self._points)==0:
            return False
        # Construct dict to dump
        d = {}
        if not self._backgroundFile is None:
            dir_name = path.dirname(str(projectFile))
            rel_path = path.relpath(str(self._backgroundFile), str(dir_name))
            d["background_file"] = str(rel_path)
        else:
            d["background_file"] = None
        d["x0"] = self.x0
        d["x1"] = self.x1
        d["y0"] = self.y0
        d["y1"] = self.y1

        # Store axis refs coordinates
        axis_refs = {}
        for key in self._axisRefs:
            if self._axisRefs[key] is None:
                axis_refs[key] = None
            else:
                axis_refs[key] = self._axisRefs[key]
        d["axisRefs"] = axis_refs

        # Store items coordinates
        points = []
        for key in self._points:
            p, dataset = self._points[key]
            points.append([p, dataset])
        d["points"] = points

        datasets = self._datasets
        datasetKey = self._datasetKey
        currentDataset = self._currentDataset
        datasetColors = dict()
        for k,v in self.datasetColors.items():
            if isinstance(v[0],QPen): #Handle difference between data stored between QPainter and ZincDigitizer
                datasetColors[k] = [v[0].color().rgba()]
            else:
                crgba = v[1].rgba()
                datasetColors[k] = [v[0],crgba]
        d["datasets"] = datasets
        d["datasetKey"] = datasetKey
        d["currentDataset"] = currentDataset
        d['datasetColors'] = datasetColors
        try:
            with open(projectFile, "wb") as oF:    
                pickle.dump("SPARCPaintV0.1", oF,2)
                pickle.dump(d, oF,2)
            return True
        except:
            return False

    def reassignIds(self,d):
        dsk = self._datasetKey
        kmap = dict()
        if "datasets" in d:
            ds = d['datasets']
            newDataset = OrderedDict()
            for k,v in ds.items():
                if k in self._datasets:
                    kmap[k] = dsk
                    newDataset[dsk] = v
                    dsk +=1
                else:
                    kmap[k] = k
                    newDataset[k] = v
            d['datasets'] = newDataset
            d['datasetKey'] = dsk
            
        if 'datasetColors' in d:
            dcolors = dict()
            ds = d['datasetColors']
            for k,v in ds.items():
                if not k in self.datasetColors: 
                    dcolors[k] = v
            d['datasetColors'] = ds
            
        newPoints = []
        for data in d["points"]:
            if isinstance(data, list) or isinstance(data, tuple):
                p, dataset = data
                newPoints.append([p,kmap[dataset]])
            else:
                newPoints.append(data)
        d['points'] = newPoints
        
        return d

    def loadDataset(self, projectFile):
        #self.reset()

        with open(projectFile, "rb") as iF:
            header = pickle.load(iF)
            if header == "SPARCPaintV0.1":
                d = pickle.load(iF)
                if len(self._datasets) > 0:
                    #Renumber dataset to ensure it doesnt clash with existing keys
                    d = self.reassignIds(d)
                    
                def assignIfExists(var, d, keyname):
                    if keyname in d:
                        return d[keyname]
                    else:
                        return var

                background_file = d["background_file"]
                if header == "PaintV0.3" and not background_file is None:
                    dir_name = path.dirname(str(projectFile))
                    background_file = path.normpath(path.join(dir_name, background_file))
                    self.addImage(background_file)
                    
                self.x0 = assignIfExists(self.x0, d, "x0")
                self.x1 = assignIfExists(self.x1, d, "x1")
                self.y0 = assignIfExists(self.y0, d, "y0")
                self.y1 = assignIfExists(self.y1, d, "y1")

                self._axisRefs = {}
                for key in d["axisRefs"]:
                    if not d["axisRefs"][key] is None:
                        p = d["axisRefs"][key]
                        if isinstance(p, QtCore.QPointF):
                            p = [p.x(), p.y()]
                        self._axisRefs[key]=p

                #self.datasetColors = dict()
                if 'datasetColors' in d:
                    ds = d['datasetColors'] 
                    for k,v in ds.items():
                        if len(v)==2:
                            color = QColor(v[1])
                            self.datasetColors[k] = [v[0],color]  
                        else:
                            color = QColor(v[0])
                            brush = QBrush()
                            bcolor = QColor(color)
                            bcolor.setAlpha(127)
                            brush.setColor(bcolor)
                            brush.setStyle(QtCore.Qt.CrossPattern)
                            pen = QPen(color, 1, QtCore.Qt.SolidLine,QtCore.Qt.FlatCap, QtCore.Qt.MiterJoin)
                            self.datasetColors[k] = [pen,brush]
                            
                if "datasets" in d:
                    #self._datasets = d["datasets"]
                    for k,v in d['datasets'].items():
                        self._datasets[k] = v
                    self._datasetKey = d["datasetKey"]
                    self._currentDataset = d["currentDataset"]
                else:
                    self._datasets = OrderedDict({0: "Dataset 0"})
                    self._datasetKey = 1
                    self._currentDataset = 0

                for data in d["points"]:
                    p, dataset = [0,0], 0
                    if isinstance(data, list) or isinstance(data, tuple):
                        p, dataset = data
                    elif isinstance(data, QtCore.QPointF):
                        p = [data.x(), data.y()]
                    else:
                        continue
                    if dataset in self._datasets:
                        self._currentDataset = dataset
                    self.addPointPassive(p)
                         
                self.modelChanged.emit()
                return True

        self.modelChanged.emit()
        return False

    def addImage(self, background_file):
        self._backgroundFile = str(background_file)

    def getBackgroundFile(self):
        return self._backgroundFile

    def getPoints(self):
        return self._points

    def addPointPassive(self, pos):
        self._points[self._pointIndex] = [pos, self._currentDataset]
        self._pointIndex += 1

    def addPoint(self, pos):
        self._points[self._pointIndex] = [pos, self._currentDataset]
        self._pointIndex += 1

        self.modelChanged.emit()

    def removePoint(self, key):
        del self._points[key]
        self.modelChanged.emit()

    def movePoint(self, key, pos):
        if not key in self._points:
            return
        #Update if it belongs to current dataset
        ds = self._points[key][1]
        if ds == self._currentDataset:
            self._points[key][0] = pos
            self.modelChanged.emit()

    def setInsertMode(self,mode):
        self._mode = mode

    def computeCoordinates(self, pos):
        if len(self._axisRefs.keys()) != 4:
            return pos
        coord = np.zeros(2)
        x0Pos = self._axisRefs[1]
        x1Pos = self._axisRefs[2]
        y0Pos = self._axisRefs[3]
        y1Pos = self._axisRefs[4]
        pos = np.array(pos)
        # Find projecting over x axis
        A0 = x1Pos-  x0Pos
        A1 = y1Pos-  y0Pos
        b = pos - x0Pos
        alpha = np.dot(A0,b)/np.linalg.norm(A0)
        b = pos - y0Pos
        beta = np.dot(A1,b)/np.linalg.norm(A1)
        #Go from reference axis lengths to preferred axis lengths given by (x1,y1)-(x0,y0)
        scalingx =  (self.x1 - self.x0)/np.linalg.norm(A0)
        scalingy =  (self.y1 - self.y0)/np.linalg.norm(A1)
        coord[0] = self.x0 + (self.x1 - self.x0)/np.linalg.norm(self.x1-self.x0) * alpha * scalingx
        coord[1] = self.y0 + (self.y1 - self.y0)/np.linalg.norm(self.y1-self.y0) * beta * scalingy
        return coord

    def getPointsPerDataset(self):
        pointsPerDataset = OrderedDict()
        for dataset in self._datasets:
            pointsPerDataset[dataset] = []

        for key in self._points:
            p, dataset = self._points[key]
            pointsPerDataset[dataset].append(p)

        count = max(len(pointsPerDataset[dataset]) for dataset in self._datasets)
        return pointsPerDataset, count
    
    def getPointsForDataset(self,dataset):
        pointInDataset = []

        for key in self._points:
            p, pdataset = self._points[key]
            if pdataset==dataset:
                pointInDataset.append(p)

        return pointInDataset

    def getDatasetInTransformedCoordinates(self):
        pointsPerDataset, _ = self.getPointsPerDataset()
        result = dict()
        for dataset,name in self._datasets.items():
            resultArray = []
            for i in range(len(pointsPerDataset[dataset])):
                p = pointsPerDataset[dataset][i]
                resultArray.append(self.computeCoordinates(p))
            result[name] = resultArray
        return result        

    def exportToCSV(self, filename):
        pointsPerDataset, count = self.getPointsPerDataset()

        with open(filename, "w") as oF:
            for i in range(count):
                for dataset in self._datasets:
                    if i < len(pointsPerDataset[dataset]):
                        p = pointsPerDataset[dataset][i]
                        oF.write("%f, %f, " % tuple(self.computeCoordinates(p)))
                    else:
                        oF.write(", , ")
                oF.write("\n")

    def addDataset(self, name = None):
        if name is None:
            name = "Group %i" % self._datasetKey

        key = self._datasetKey
        self._datasets[key] = name
        self._currentDataset = key
        self._datasetKey += 1
        self.modelChanged.emit()

    def changeDatasetName(self,key,name):
        self._datasets[key] = name

    def removeDataset(self, key):
        del self._datasets[key]

        # Delete dangling points
        for pkey in self._points.keys():
            if self._points[pkey][1] == key:
                del self._points[pkey]

        if self._currentDataset == key:
            if len(self._datasets.keys()) == 0:
                # If the last dataset was removed, add a new one instead
                self.addDataset()
            self._currentDataset = self._datasets.keys()[-1]

        if len(self._datasets.keys()) == 0:
            # If the last dataset was removed, add a new one instead
            self.addDataset()

        self.modelChanged.emit()

    def changeCurrentDataset(self, key):
        self._currentDataset = key

    def getDatasets(self):
        return self._datasets

    def getCurrentDataset(self):
        return self._currentDataset

    def setToScaffoldMeshScaling(self,meshCoordinateScalingFactor):
        self.x0 = -0.5
        self.y0 = -0.5
        self.x1 = 0.5 #Mesh is bound to unit cube with centroid in the middle
        self.y1 = 0.5
        factor = meshCoordinateScalingFactor/2.0
        self._axisRefs[1] = np.array([-factor,0.0])
        self._axisRefs[2] = np.array([factor,0.0])
        self._axisRefs[3] = np.array([0.0,-factor])        
        self._axisRefs[4] = np.array([0.0,factor])