Rémi Synave 6 mēneši atpakaļ
vecāks
revīzija
da71eb863e

+ 11 - 0
requirements.txt

@@ -0,0 +1,11 @@
+PyQt5
+colour
+colour-science
+scikit-learn
+pathos
+geomdl
+rawpy
+imageio
+scikit-image
+numba
+torch

+ 0 - 23
uHDR.sln

@@ -1,23 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.31515.178
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "uHDR", "uHDR\uHDR.pyproj", "{49B3C98A-5ADB-4366-A42B-9182AE5030C9}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Release|Any CPU = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{49B3C98A-5ADB-4366-A42B-9182AE5030C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{49B3C98A-5ADB-4366-A42B-9182AE5030C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-	GlobalSection(ExtensibilityGlobals) = postSolution
-		SolutionGuid = {E56C746F-8D4A-439C-BDE5-50DD2B82F759}
-	EndGlobalSection
-EndGlobal

BIN
uHDR/HDRip.dll


BIN
uHDR/MSESig505_0419.pth


+ 428 - 426
uHDR/guiQt/controller.py

@@ -14,101 +14,19 @@
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 
-# -----------------------------------------------------------------------------
-# --- VERSION TRACKING --------------------------------------------------------
-# -----------------------------------------------------------------------------
-
-# -----------------------------------------------------------------------------
-# (1): DEBUG
-#      HDRImageViewer doesn't open 
-#      add time.sleep() in HDRviewerController.displayFile(...)
-#      ---- Rémi Cozot, June 2021
-# -----------------------------------------------------------------------------
-# (2): Prevent Freezing
-#      Add multithreading in:
-#      i  - processpipe processing (except for export and display)
-#      ii - loading images
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (3): Prevent Freezing and speed up
-#      Add multithreading in:
-#      i  - processpipe processing for HDR display
-#      ii - processpipe procssing export single image to vesaHDR1000
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (4): Fix ev value and accuracy in color editor 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (5): Fix Y curve plot 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (6): Ability to export SDR (.jpg) image to HDR (tone expansion).
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
 
 
 # -----------------------------------------------------------------------------
 # --- Package hdrGUI ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
-package hdrGUI consists of the classes for GUI:
-(1) controller classes:
-    class GalleryMode(enum.Enum)
-    class ImageWidgetController
-    class ImageGalleryController()
-    class AppController(object)
-    class MultiDockController()
-    class EditImageController
-    class ImageInfoController
-    class AdvanceSliderController()
-    class ToneCurveController()
-    class LightnessMaskController()
-    class HDRviewerController()
-    class LchColorSelectorController()
-    class GeometryController()
-(2) model classes: 
-    class ImageWidgetModel(object)
-    class ImageGalleryModel
-    class AppModel(object)
-    class EditImageModel(object)
-    class AdvanceSliderModel()
-    class ToneCurveModel()
-    class LightnessMaskModel()
-    class ImageInfoModel(object)
-    class HDRviewerModel(object)
-    class ImageQualityModel(object)
-    class LchColorSelectorModel(object)
-    class GeometryModel(object)
-(3) view classes:
-    class ImageWidgetView(QWidget)
-    class FigureWidget(FigureCanvas)
-    class ImageGalleryView(QSplitter)
-    class AppView(QMainWindow)
-    class ImageInfoView(QSplitter)
-    class AdvanceLineEdit(object)
-    class AdvanceCheckBox(object)
-    class EditImageView(QSplitter)
-    class MultiDockView(QDockWidget)
-    class AdvanceSliderView(QFrame)
-    class ToneCurveView(QFrame)
-    class LightnessMaskView(QGroupBox)
-    class HDRviewerView(QFrame)
-    class ImageQualityView(QScrollArea)
-    class LchColorSelectorView(QFrame)
-    class GeometryView(QFrame)
-(4) Qt multi threading classes:
-    class RequestCompute(object)
-    class RunCompute(QRunnable)
-    class RequestLoadImage(object)
-    class RunLoadImage(QRunnable)
-    class pCompute(object)
-    class pRun(QRunnable)
+package hdrGUI consists of the classes for GUI.
 """
 # -----------------------------------------------------------------------------
 # --- Import ------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 
-import enum, sys, subprocess, copy, colour, time, os, shutil, datetime
+import enum, sys, subprocess, copy, colour, time, os, shutil, datetime, ctypes
 import numpy as np
 # pyQT5 import
 from PyQt5.QtWidgets import QFileDialog, QApplication
@@ -116,8 +34,14 @@ from PyQt5.QtWidgets import QMessageBox
 
 from . import model, view, thread
 import hdrCore.image, hdrCore.processing, hdrCore.utils
+import hdrCore.coreC
 import preferences.preferences as pref
 
+# zj add for semi-auto curve 
+import torch
+from hdrCore.net import Net
+from torch.autograd import Variable
+
 # -----------------------------------------------------------------------------
 # --- package methods ---------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -301,7 +225,9 @@ class ImageGalleryController():
         return (nb*nbImagePage), ((nb+1)*nbImagePage)
 
     def getFilenamesOfCurrentPage(self): return self.model.getFilenamesOfCurrentPage()
+
     def getProcessPipeById(self,i) : return self.model.getProcessPipeById(i)
+
     def getProcessPipes(self): return self.model.processPipes
 # -----------------------------------------------------------------------------
 # --- Class AppController -----------------------------------------------------
@@ -316,17 +242,16 @@ class AppController(object):
             model
             
         Methods:
-            callBackInfo
-            callBackSelectDir
-            callBackSave
-            callBackQuit
-            callBackDisplayHDR
-            callBackEndDisplay
-            callBackCloseDisplayHDR
-            callBackCompareRawEditedHDR
-            callBackExportHDR
-            callBackEndExportHDR
-            callBackExportAllHDR
+            callBackSelectDir(self)
+            callBackSave(self)
+            callBackDisplayHDR(self)
+            callBackEndDisplay(self, img)
+            callBackCloseDisplayHDR(self)
+            callBackCompareRawEditedHDR(self)
+            callBackExportHDR(self)
+            callBackEndExportHDR(self, img)
+            callBackExportAllHDR(self)            
+            callBackEndAllExportHDR(self, img)  
 
     """
 
@@ -339,50 +264,37 @@ class AppController(object):
         self.hdrDisplay = HDRviewerController(self)
         self.view =  view.AppView(self, HDRcontroller = self.hdrDisplay)                         
         self.model = model.AppModel(self)
+
+        self.dirName = None
+        self.imagesName = []
         
         self.view.show()
-
-    def callBackPython(self):
-        if pref.verbose:  print(" [CONTROL] >> AppController.callBackPython()")
-        pref.computation = pref.target[0]
-
-    def callBackNumba(self):
-        if pref.verbose:  print(" [CONTROL] >> AppController.callBackNumba()")
-        pref.computation = pref.target[1]
-
-    def callBackCuda(self):
-        if pref.verbose:  print(" [CONTROL] >> AppController.callBackCuda()")
-        pref.computation = pref.target[2]
+    # -----------------------------------------------------------------------------
 
     def callBackSelectDir(self):
+        """Callback of export HDR menu: open file dialog, store image filenames (self.imagesName), set directory to model
+        """
         if pref.verbose: print(" [CONTROL] >> AppController.callBackSelectDir()")
-        if not self.view.assessmentRunning(): 
-            dirName = QFileDialog.getExistingDirectory(None, 'Select Directory', self.model.directory)
-            if dirName != "":
-                # save current images (metadata)
-                self.view.imageGalleryController.save()
-                # get images in the selected directory
-                self.view.imageGalleryController.setImages(self.model.setDirectory(dirName))
-                self.hdrDisplay.displaySplash()
-
-        else: self.view.statusBar().showMessage('stop assessment before') 
+        dirName = QFileDialog.getExistingDirectory(None, 'Select Directory', self.model.directory)
+        if dirName != "":
+            # save current images (metadata)
+            self.view.imageGalleryController.save()
+            # get images in the selected directory
+            self.imagesName = []; self.imagesName = list(self.model.setDirectory(dirName))
 
+            self.view.imageGalleryController.setImages(self.imagesName)
+            self.hdrDisplay.displaySplash()
+    # -----------------------------------------------------------------------------
     def callBackSave(self): self.view.imageGalleryController.save()
-
+    # -----------------------------------------------------------------------------
     def callBackQuit(self):
         if pref.verbose: print(" [CB] >> AppController.callBackQuit()")
-        if not self.view.assessmentRunning(): 
-            self.view.imageGalleryController.save()
-            self.hdrDisplay.close()
-            sys.exit()
-        else:
-            okCancel = okCancelBox('uHDR: WARNING', 'closing now, assessment results will be lost!')
-            if okCancel==QMessageBox.Ok:
-                self.view.imageGalleryController.save()
-                self.hdrDisplay.close()
-                sys.exit()
-
+        self.view.imageGalleryController.save()
+        self.hdrDisplay.close()
+        sys.exit()
+    # -----------------------------------------------------------------------------
     def callBackDisplayHDR(self):
+
         if pref.verbose:  print(" [CONTROL] >> AppController.callBackDisplayHDR()")
 
         selectedProcessPipe = self.view.imageGalleryController.model.getSelectedProcessPipe()
@@ -404,17 +316,16 @@ class AppController(object):
             processpipe = copy.deepcopy(selectedProcessPipe)
 
             # set size to display size
-            size = self.hdrDisplay.model.displayModel['shape']
+            size = pref.getDisplayShape()
             img = img.process(hdrCore.processing.resize(),size=(None, size[1]))
 
             # set image to process-pipe
             processpipe.setImage(img)
 
-            # star parallel computation
-            nbWidth,  nbHeight = 3,3
-            thread.pCompute(self.callBackEndDisplay, processpipe,nbWidth,nbHeight, toneMap=False, progress=self.view.statusBar().showMessage)
+            thread.cCompute(self.callBackEndDisplay, processpipe, toneMap=False, progress=self.view.statusBar().showMessage)
+    # -----------------------------------------------------------------------------
+    def callBackEndDisplay(self, img):
 
-    def callBackEndDisplay(self, img, addParams=None):
         if pref.verbose:  print(" [CONTROL] >> AppController.callBackEndDisplay()")
 
         # turn off: autoResize
@@ -424,16 +335,15 @@ class AppController(object):
 
         # clip, scale
         img = img.process(hdrCore.processing.clip())
-        img.colorData = img.colorData*hdrCore.utils.HDRdisplay['vesaDisplayHDR1000']['scaling']
+        img.colorData = img.colorData*pref.getDisplayScaling()
 
         colour.write_image(img.colorData,"temp.hdr", method='Imageio') # local copy for display
         self.hdrDisplay.displayFile("temp.hdr")
-
+    # -----------------------------------------------------------------------------
     def callBackCloseDisplayHDR(self):
         if pref.verbose: print(" [CONTROL] >> AppController.callBackCloseDisplayHDR()")
-        if not self.view.assessmentRunning(): self.hdrDisplay.displaySplash()
-        else: self.view.statusBar().showMessage('stop assessment before') 
-
+        self.hdrDisplay.displaySplash()
+    # -----------------------------------------------------------------------------
     def callBackCompareRawEditedHDR(self):
         """
         Callback of compare raw/edited HDR menu
@@ -443,61 +353,60 @@ class AppController(object):
 
         if pref.verbose:  print(" [CONTROL] >> AppController.callBackCompareOriginalInputHDR()")
 
-        if not self.view.assessmentRunning():
-            # process real size image
-            # get selected process pipe
-            selectedProcessPipe = self.view.imageGalleryController.model.getSelectedProcessPipe()
-
-            if selectedProcessPipe:         # check if a process pipe is selected
-
-                # read original image
-                img = hdrCore.image.Image.read(selectedProcessPipe.originalImage.path+'/'+selectedProcessPipe.originalImage.name)
-
-                # resize
-                screenY, screenX = self.hdrDisplay.model.displayModel['shape']  
-                imgY, imgX,_ = img.shape
-
-                marginY = int((screenY - imgY/2)/2)
-                marginX = int(marginY/4)
-                imgXp = int((screenX - 3*marginX)/2)
-                img = img.process(hdrCore.processing.resize(),size=(None,imgXp))
-                imgY, imgX, _ = img.shape
-
-                # original image after resize
-                ori = copy.deepcopy(img)
-
-                # build process pipe from selected one them compute
-                pp = hdrCore.processing.ProcessPipe()
-                hdrCore.processing.ProcessPipe.autoResize = False   # stop autoResize
-                params= []
-                for p in selectedProcessPipe.processNodes: 
-                    pp.append(copy.deepcopy(p.process),paramDict=None, name=copy.deepcopy(p.name))
-                    params.append({p.name:p.params})
-                img.metadata.metadata['processpipe'] = params
-                pp.setImage(img)
-                pp.compute()
-                res = pp.getImage(toneMap=False)
-                res = res.process(hdrCore.processing.clip())
-                imgYres, imgXres, _ = res.colorData.shape
-
-                hdrCore.processing.ProcessPipe.autoResize = True    # return to autoResize
-
-                # make comparison image
-                oriColorData = ori.colorData*self.hdrDisplay.model.displayModel['scaling']
-                resColorData = res.colorData*self.hdrDisplay.model.displayModel['scaling']
-                display = np.ones((screenY,screenX,3))*0.2
-                marginY = int((screenY - imgY)/2)
-                marginYres = int((screenY - imgYres)/2)
-
-                display[marginY:marginY+imgY, marginX:marginX+imgX,:] = oriColorData
-                display[marginYres:marginYres+imgYres, 2*marginX+imgX:2*marginX+imgX+imgXres,:] = resColorData
+        # process real size image
+        # get selected process pipe
+        selectedProcessPipe = self.view.imageGalleryController.model.getSelectedProcessPipe()
+
+        if selectedProcessPipe:         # check if a process pipe is selected
+
+            # read original image
+            img = hdrCore.image.Image.read(selectedProcessPipe.originalImage.path+'/'+selectedProcessPipe.originalImage.name)
+
+            # resize
+            screenY, screenX = pref.getDisplayShape()
+
+            imgY, imgX,_ = img.shape
+
+            marginY = int((screenY - imgY/2)/2)
+            marginX = int(marginY/4)
+            imgXp = int((screenX - 3*marginX)/2)
+            img = img.process(hdrCore.processing.resize(),size=(None,imgXp))
+            imgY, imgX, _ = img.shape
+
+            # original image after resize
+            ori = copy.deepcopy(img)
+
+            # build process pipe from selected one them compute
+            pp = hdrCore.processing.ProcessPipe()
+            hdrCore.processing.ProcessPipe.autoResize = False   # stop autoResize
+            params= []
+            for p in selectedProcessPipe.processNodes: 
+                pp.append(copy.deepcopy(p.process),paramDict=None, name=copy.deepcopy(p.name))
+                params.append({p.name:p.params})
+            img.metadata.metadata['processpipe'] = params
+            pp.setImage(img)
+
+            res = hdrCore.coreC.coreCcompute(img, pp)
+            res = res.process(hdrCore.processing.clip())
             
-                # save as compOrigFinal.hdr
-                colour.write_image(display,'compOrigFinal.hdr', method='Imageio')
-                self.hdrDisplay.displayFile('compOrigFinal.hdr')
-        else:
-            self.view.statusBar().showMessage('stop assessment before') 
+            imgYres, imgXres, _ = res.colorData.shape
+
+            hdrCore.processing.ProcessPipe.autoResize = True    # return to autoResize
+
+            # make comparison image
+            oriColorData = ori.colorData*pref.getDisplayScaling()
+            resColorData = res.colorData*pref.getDisplayScaling()
+            display = np.ones((screenY,screenX,3))*0.2
+            marginY = int((screenY - imgY)/2)
+            marginYres = int((screenY - imgYres)/2)
 
+            display[marginY:marginY+imgY, marginX:marginX+imgX,:] = oriColorData
+            display[marginYres:marginYres+imgYres, 2*marginX+imgX:2*marginX+imgX+imgXres,:] = resColorData
+            
+            # save as compOrigFinal.hdr
+            colour.write_image(display,'compOrigFinal.hdr', method='Imageio')
+            self.hdrDisplay.displayFile('compOrigFinal.hdr')
+    # -----------------------------------------------------------------------------
     def callBackExportHDR(self):
         """
         Callback of export HDR menu
@@ -509,12 +418,15 @@ class AppController(object):
 
         selectedProcessPipe = self.view.imageGalleryController.model.getSelectedProcessPipe()
 
+
         if selectedProcessPipe:
             # select dir where to save export
             self.dirName = QFileDialog.getExistingDirectory(None, 'Select Directory where to export HDR file', self.model.directory)
 
-            self.view.statusBar().showMessage('exporting HDR image (HDR vesa 1000), full size image computation: start, please wait !')
+            # show export message
+            self.view.statusBar().showMessage('exporting HDR image ('+pref.getHDRdisplay()['tag']+'), full size image computation: start, please wait !')
             self.view.statusBar().repaint()
+
             # save current processpipe metada
             originalImage = copy.deepcopy(selectedProcessPipe.originalImage)
             originalImage.metadata.metadata['processpipe'] = selectedProcessPipe.toDict()
@@ -531,43 +443,107 @@ class AppController(object):
             # set image to process-pipe
             processpipe.setImage(img)
 
-            # star parallel computation
-            nbWidth,  nbHeight = 3,3
-            thread.pCompute(self.callBackEndExportHDR, processpipe,nbWidth,nbHeight, toneMap=False, progress=self.view.statusBar().showMessage, meta=copy.deepcopy(img.metadata) )
-
-    def callBackEndExportHDR(self, img, meta):
+            thread.cCompute(self.callBackEndExportHDR, processpipe, toneMap=False, progress=self.view.statusBar().showMessage)
+    # -----------------------------------------------------------------------------
+    def callBackEndExportHDR(self, img):
         # turn off: autoResize
         hdrCore.processing.ProcessPipe.autoResize = True  
         
-        self.view.statusBar().showMessage('exporting HDR image (HDR vesa 1000), full size image computation: done !')
+        self.view.statusBar().showMessage('exporting HDR image ('+pref.getHDRdisplay()['tag']+'), full size image computation: done !')
 
         # clip, scale
         img = img.process(hdrCore.processing.clip())
-        img.colorData = img.colorData*hdrCore.utils.HDRdisplay['vesaDisplayHDR1000']['scaling']
+        img.colorData = img.colorData*pref.getDisplayScaling()
 
         if self.dirName:
-            pathExport = os.path.join(self.dirName, img.name[:-4]+hdrCore.utils.HDRdisplay['vesaDisplayHDR1000']['post']+'.hdr')
+            pathExport = os.path.join(self.dirName, img.name[:-4]+pref.getHDRdisplay()['post']+'.hdr')
             img.type = hdrCore.image.imageType.HDR
-            img.metadata = meta
             img.metadata.metadata['processpipe'] = None
-            img.metadata.metadata['display'] =hdrCore.utils.HDRdisplay['vesaDisplayHDR1000']['tag']
+            img.metadata.metadata['display'] = pref.getHDRdisplay()['tag']
 
             img.write(pathExport)
 
         colour.write_image(img.colorData,"temp.hdr", method='Imageio') # local copy for display
         self.hdrDisplay.displayFile("temp.hdr")
-
+    # -----------------------------------------------------------------------------
     def callBackExportAllHDR(self):
         if pref.verbose:  print(" [CONTROL] >> AppController.callBackExportAllHDR()")
 
-        processPipes = self.view.imageGalleryController.getProcessPipes()
+        self.processPipes = self.view.imageGalleryController.getProcessPipes()
 
         # select dir where to save export
-        dirName = QFileDialog.getExistingDirectory(None, 'Select Directory where to export HDR file', self.model.directory)
-        self.view.statusBar().showMessage('exporting HDR vesa 1000 images ... please wait')
+        self.dirName = QFileDialog.getExistingDirectory(None, 'Select Directory where to export HDR file', self.model.directory)
+        self.view.statusBar().showMessage('exporting '+str(len(self.processPipes))+' HDR images ... please wait')
         self.view.statusBar().repaint()
+        self.imageToExport = len(self.processPipes) ; self.imageExportDone = 0
+
+        pp = self.processPipes[0]
+
+        # save current processpipe metada
+        originalImage = copy.deepcopy(pp.originalImage)
+        originalImage.metadata.metadata['processpipe'] = pp.toDict()
+        originalImage.metadata.save()
+
+        # load full size image
+        img = hdrCore.image.Image.read(originalImage.path+'/'+originalImage.name)
+
+        # turn off: autoResize
+        hdrCore.processing.ProcessPipe.autoResize = False 
+        # make a copy of selectedProcessPipe  
+        processpipe = copy.deepcopy(pp)
+
+        # set image to process-pipe
+        processpipe.setImage(img)
+
+        thread.cCompute(self.callBackEndAllExportHDR, processpipe, toneMap=False, progress=self.view.statusBar().showMessage)            
+    # -----------------------------------------------------------------------------
+    def callBackEndAllExportHDR(self, img):
+        # last image ?
+        self.imageExportDone +=1
+
+        self.view.statusBar().showMessage('exporting HDR images ('+pref.getHDRdisplay()['tag']+'):'+str(int(100*self.imageExportDone/self.imageToExport))+'% done !')
 
-        for pp_i in processPipes: res = pp_i.export(dirName,  size=None, to=hdrCore.utils.HDRdisplay['vesaDisplayHDR1000'],  progress = None)
+        # clip, scale
+        img = img.process(hdrCore.processing.clip())
+        img.colorData = img.colorData*pref.getDisplayScaling()
+
+        if self.dirName:
+            pathExport = os.path.join(self.dirName, img.name[:-4]+pref.getHDRdisplay()['post']+'.hdr')
+            img.type = hdrCore.image.imageType.HDR
+            img.metadata.metadata['processpipe'] = None
+            img.metadata.metadata['display'] = pref.getHDRdisplay()['tag']
+
+            img.write(pathExport)
+
+        if self.imageExportDone == self.imageToExport :
+            # turn off: autoResize
+            hdrCore.processing.ProcessPipe.autoResize = True
+        else:
+            pp = self.processPipes[self.imageExportDone]
+
+            if not pp:
+                img = hdrCore.image.Image.read(self.imagesName[self.imageExportDone], thumb=True)
+                pp = model.EditImageModel.buildProcessPipe()
+                pp.setImage(img)                      
+
+
+            # save current processpipe metada
+            originalImage = copy.deepcopy(pp.originalImage)
+            originalImage.metadata.metadata['processpipe'] = pp.toDict()
+            originalImage.metadata.save()
+
+            # load full size image
+            img = hdrCore.image.Image.read(originalImage.path+'/'+originalImage.name)
+
+            # turn off: autoResize
+            hdrCore.processing.ProcessPipe.autoResize = False 
+            # make a copy of selectedProcessPipe  
+            processpipe = copy.deepcopy(pp)
+
+            # set image to process-pipe
+            processpipe.setImage(img)
+
+            thread.cCompute(self.callBackEndAllExportHDR, processpipe, toneMap=False, progress=self.view.statusBar().showMessage)            
 # ------------------------------------------------------------------------------------------
 # --- class MultiDockController() ----------------------------------------------------------
 # ------------------------------------------------------------------------------------------
@@ -578,25 +554,20 @@ class MultiDockController():
         self.parent = parent
         self.view = view.MultiDockView(self, HDRcontroller)
         self.model = None
-
-
+    # ---------------------------------------------------------------------------------------
     def activateEDIT(self): self.switch(0)
     def activateINFO(self): self.switch(1)
-    def activateIQA(self):  self.switch(2)
-
+    def activateMIAM(self):  self.switch(2)
+    # ---------------------------------------------------------------------------------------
     def switch(self,nb):
         if pref.verbose:  print(" [CONTROL] >> MultiDockController.switch()")
-
-        if not self.assessmentRunning(): self.view.switch(nb)
-        else: self.parent.statusBar().showMessage('stop assessment before switching dock') 
-
+        self.view.switch(nb)
+    # --------------------------------------------------------------------------------------
     def setProcessPipe(self, processPipe): 
         if pref.verbose: print(" [CONTROL] >> MultiDockController.setProcessPipe(",processPipe.getImage().name,")")
 
         return self.view.setProcessPipe(processPipe)
-
-    def assessmentRunning(self):
-        return self.view.childControllers[2].assessmentRunning()
+# ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class EditImageController:
@@ -611,7 +582,7 @@ class EditImageController:
 
         self.view = view.EditImageView(self)
         self.model = model.EditImageModel(self)
-
+    # -----------------------------------------------------------------------------
     def setProcessPipe(self, processPipe): 
         if pref.verbose: print(" [CONTROL] >> EditImageController.setProcessPipe(",")")
 
@@ -629,14 +600,16 @@ class EditImageController:
             return True
         else:
             return False
-
+    # -----------------------------------------------------------------------------
+    def getProcessPipe(self) : return self.model.getProcessPipe()
+    # -----------------------------------------------------------------------------
     def buildView(self,processPipe=None):
         if pref.verbose: print(" [CONTROL] >> EditImageController.buildView(",")")
 
         """ called when MultiDockController recall a controller/view """
         self.view = view.EditImageView(self, build=True)
         if processPipe: self.setProcessPipe(processPipe)
-
+    # -----------------------------------------------------------------------------
     def autoExposure(self): 
         if pref.verbose: print(" [CONTROL] >> EditImageController.autoExposure(",")")
         if self.model.processpipe:
@@ -649,35 +622,35 @@ class EditImageController:
 
             qPixmap =  self.view.setImage(img)
             self.parent.controller.parent.controller.view.imageGalleryController.setProcessPipeWidgetQPixmap(qPixmap)
-
+    # -----------------------------------------------------------------------------
     def changeExposure(self,value):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeExposure(",value,")")
         if self.model.processpipe: self.model.changeExposure(value)
-
+    # -----------------------------------------------------------------------------
     def changeContrast(self,value):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeContrast(",value,")")
         if self.model.processpipe: self.model.changeContrast(value)
-
+    # -----------------------------------------------------------------------------
     def changeToneCurve(self,controlPoints):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeToneCurve("")")
         if self.model.processpipe: self.model.changeToneCurve(controlPoints)
-
+    # -----------------------------------------------------------------------------
     def changeLightnessMask(self, maskValues):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeLightnessMask(",maskValues,")")
         if self.model.processpipe: self.model.changeLightnessMask(maskValues)
-
+    # -----------------------------------------------------------------------------
     def changeSaturation(self,value):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeSaturation(",value,")")
         if self.model.processpipe: self.model.changeSaturation(value)
-
+    # -----------------------------------------------------------------------------
     def changeColorEditor(self,values, idName):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeColorEditor(",values,")")
         if self.model.processpipe: self.model.changeColorEditor(values, idName)
-
+    # -----------------------------------------------------------------------------
     def changeGeometry(self,values):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeGeometry(",values,")")
         if self.model.processpipe: self.model.changeGeometry(values)
-
+    # -----------------------------------------------------------------------------
     def updateImage(self,imgTM):
         """
         updateImage: called when process-pipe computation is done
@@ -689,9 +662,14 @@ class EditImageController:
         self.parent.controller.parent.controller.view.imageGalleryController.setProcessPipeWidgetQPixmap(qPixmap)
         self.view.plotToneCurve()
 
+        # if aesthetics model > notify required update
+
+
         if self.previewHDR and self.model.autoPreviewHDR:
             self.controllerHDR.callBackUpdate()
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class ImageInfoController:
 
     def __init__(self, parent=None):
@@ -702,133 +680,25 @@ class ImageInfoController:
         self.model = model.ImageInfoModel(self)
 
         self.callBackActive = True
-
+    # -----------------------------------------------------------------------------
     def setProcessPipe(self, processPipe): 
         if pref.verbose: print(" [CONTROL] >> ImageInfoController.setProcessPipe(",processPipe.getImage().name,")")
         self.model.setProcessPipe(processPipe)
-        #### self.view.setImage(processPipe.getImage())
         self.view.setProcessPipe(processPipe)
         return True
-
+    # -----------------------------------------------------------------------------
     def buildView(self,processPipe=None):
         if pref.verbose: print(" [CONTROL] >> ImageInfoController.buildView()")
 
         """ called when MultiDockController recall a controller/view """
         self.view = view.ImageInfoView(self)
         if processPipe: self.setProcessPipe(processPipe)
-
-    def useCaseChange(self,useCaseClass,useCase, on_off): 
-        if pref.verbose: print(" [CONTROL] >> ImageInfoController.useCaseChange(",useCaseClass,",", useCase,",", on_off,")")
-        self.model.changeUseCase(useCaseClass,useCase, on_off)
+    # -----------------------------------------------------------------------------
+    def metadataChange(self,metaGroup,metaTag, on_off): 
+        if pref.verbose: print(" [CONTROL] >> ImageInfoController.useCaseChange(",metaGroup,",", metaTag,",", on_off,")")
+        self.model.changeMeta(metaGroup,metaTag, on_off)
+# ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
-class ImageQualityController:
-    def __init__(self, parent=None, HDRcontroller = None):
-        if pref.verbose: print(" [CONTROL] >> ImageQualityController.__init__()")
-
-        self.parent = parent
-        self.model = model.ImageQualityModel(self)
-        self.view = view.ImageQualityView(self)
-
-        self.HDRcontroller = HDRcontroller
-
-        self.running = False
-        self.callBackActive = True
-
-    def assessmentRunning(self): return self.running
-
-    def setProcessPipe(self, processPipe): 
-        if pref.verbose: print(" [CONTROL] >> ImageQualityController.setProcessPipe(",processPipe.getImage().name,")")
-
-        pass
-
-    def buildView(self,processPipe=None):
-        if pref.verbose: print(" [CONTROL] >> ImageQualityController.buildView()")
-        self.view = view.ImageQualityView(self)
-        if processPipe: self.setProcessPipe(processPipe)
-
-    def startStop(self): 
-
-        if pref.verbose: print(" [CONTROL] >> ImageQualityController.startStop(",")")
-        self.running = not self.running
-
-        if self.running:
-
-            if not self.model.userName:
-                messageBox("uHDR: WARNING", "you must enter your name/pseudo!")
-                self.running = False
-
-            else:
-
-                # start assessment: create vesaDisplayHDR1000 export images
-                imgGalController = self.parent.controller.parent.imageGalleryController
-                status = self.parent.controller.parent.statusBar()
-                processPipes = imgGalController.getProcessPipes()
-
-                if len(processPipes)==0:
-                    messageBox("uHDR: WARNING", "you must select the directory with images to assess!")
-                    self.running = False
-
-                else:
-
-                    localCopies = []
-
-                    status.showMessage("exporting HDR images")
-                    status.repaint()
-
-                    for i,pp_i in enumerate(processPipes):
-
-                        res = pp_i.export(None,  size=None, to=hdrCore.utils.HDRdisplay['vesaDisplayHDR1000'],  progress = None)
-                        destFile = "temp_"+str(i)+".hdr"
-                        colour.write_image(res.colorData,destFile, method='Imageio') # local copy for display
-                        localCopies.append(destFile)
-            
-                    # start assessment 
-                    self.model.processPipes = processPipes  # 
-                    self.model.setImages(localCopies)       # send data to model
-                    self.model.start()
-                    self.model.next()                       # start
-        else: 
-            # stop assessment: save score, remove local copies
-            self.model.save()
-            self.model.resetImages()
-            for localFile in self.model.localImageNames: os.remove(localFile)
-
-    def next(self):
-        if pref.verbose: print(" [CONTROL] >> ImageQualityController.next(",")")
-        if self.assessmentRunning():
-            q = self.model.next()
-            self.setValues(q)
-
-    def previous(self):
-        if pref.verbose: print(" [CONTROL] >> ImageQualityController.previous(",")")
-        if self.assessmentRunning():
-            q = self.model.previous()
-            self.setValues(q)
-
-    def pseudo(self, name):
-        if pref.verbose: print(" [CONTROL] >> ImageQualityController.pseudo(",name,")")
-
-        if self.callBackActive:
-            self.model.setPseudo(name)
-
-    def score(self, label, value):
-        if pref.verbose: print(" [CONTROL] >> ImageQualityController.score(",label,",", value,")")
-
-        if self.callBackActive:
-            self.model.setScore(label,value)
-
-    def artifact(self, label, value):
-        if pref.verbose: print(" [CONTROL] >> ImageQualityController.artifact(",label,",", value,")")
-
-        if self.callBackActive:
-            self.model.setArtifact(label,value)
-
-    def setValues(self,q):
-        if pref.verbose: print(" [CONTROL] >> ImageQualityController.setValues(",q,")")
-
-        self.callBackActive = False
-        self.view.setValues(q)
-        self.callBackActive = True
 # ------------------------------------------------------------------------------------------
 class AdvanceSliderController():
     def __init__(self, parent,name, defaultValue, range, step,callBackValueChange=None,callBackAutoPush= None):
@@ -845,7 +715,7 @@ class AdvanceSliderController():
         self.callBackActive = True
         self.callBackValueChange = callBackValueChange
         self.callBackAutoPush = callBackAutoPush
-
+    # -----------------------------------------------------------------------------
     def sliderChange(self):
 
         value = self.view.slider.value()*self.step
@@ -855,29 +725,31 @@ class AdvanceSliderController():
         self.model.value = value
         self.view.editValue.setText(str(value))
         if self.callBackActive and self.callBackValueChange: self.callBackValueChange(value)
-
+    # -----------------------------------------------------------------------------
     def setValue(self, value, callBackActive = True):
         if pref.verbose: print(" [CONTROL] >> AdvanceSliderController.setValue(",value,") ")
 
         """ set value value in 'model' range"""
         self.callBackActive = callBackActive
-        self.view.slider.setValue(value/self.step)
+        self.view.slider.setValue(int(value/self.step))
         self.view.editValue.setText(str(value))
-        self.model.setValue(value)
+        self.model.setValue(int(value))
 
         self.callBackActive = True
-
+    # -----------------------------------------------------------------------------
     def reset(self):
         if pref.verbose : print(" [CB] >> AdvanceSliderController.reset(",") ")
 
         self.setValue(self.defaultValue,callBackActive = False)
         if self.callBackValueChange: self.callBackValueChange(self.defaultValue)
-
+    # -----------------------------------------------------------------------------
     def auto(self):
         if pref.verbose: print(" [CB] >> AdvanceSliderController.auto(",") ")
 
         if self.callBackAutoPush: self.callBackAutoPush()
 # ------------------------------------------------------------------------------------------
+# --- class AdvanceSliderController --------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class ToneCurveController():
     def __init__(self, parent):
 
@@ -889,8 +761,14 @@ class ToneCurveController():
         # tone curve display control
         self.showInput =False
         self.showbefore = False
-        self.showAfter = True
-
+        self.showAfter = False
+        self.showOutput = True
+
+        # zj add semi-auto curve
+        # machine learning network and weight file 
+        self.weightFile = 'MSESig505_0419.pth'
+        self.networkModel = None  
+    # -----------------------------------------------------------------------------
     def sliderChange(self, key, value):
         if pref.verbose: print(" [CB] >> ToneCurveController.sliderChange(",key,",",value,")[callBackActive:",self.callBackActive,"] ")
 
@@ -902,23 +780,23 @@ class ToneCurveController():
 
             self.callBackActive =  False
 
-            self.view.sliderShadows.setValue(newValues["shadows"][1])
+            self.view.sliderShadows.setValue(int(newValues["shadows"][1]))
             self.view.editShadows.setText(str(newValues["shadows"][1]))
 
-            self.view.sliderBlacks.setValue(newValues["blacks"][1])
+            self.view.sliderBlacks.setValue(int(newValues["blacks"][1]))
             self.view.editBlacks.setText(str(newValues["blacks"][1]))
 
-            self.view.sliderMediums.setValue(newValues["mediums"][1])
+            self.view.sliderMediums.setValue(int(newValues["mediums"][1]))
             self.view.editMediums.setText(str(newValues["mediums"][1]))
 
-            self.view.sliderWhites.setValue(newValues["whites"][1])
+            self.view.sliderWhites.setValue(int(newValues["whites"][1]))
             self.view.editWhites.setText(str(newValues["whites"][1]))
 
-            self.view.sliderHighlights.setValue(newValues["highlights"][1])
+            self.view.sliderHighlights.setValue(int(newValues["highlights"][1]))
             self.view.editHighlights.setText(str(newValues["highlights"][1]))
 
             self.callBackActive =  True
-
+    # -----------------------------------------------------------------------------
     def setValues(self, valuesDict,callBackActive = False):
         if pref.verbose: print(" [CONTROL] >> ToneCurveController.setValue(",valuesDict,") ")
 
@@ -927,23 +805,58 @@ class ToneCurveController():
         self.model.setValues(valuesDict)
         points = self.model.evaluate()
 
-        self.view.sliderShadows.setValue(valuesDict["shadows"][1])
+        self.view.sliderShadows.setValue(int(valuesDict["shadows"][1]))
         self.view.editShadows.setText(str(valuesDict["shadows"][1]))
 
-        self.view.sliderBlacks.setValue(valuesDict["blacks"][1])
+        self.view.sliderBlacks.setValue(int(valuesDict["blacks"][1]))
         self.view.editBlacks.setText(str(valuesDict["blacks"][1]))
 
-        self.view.sliderMediums.setValue(valuesDict["mediums"][1])
+        self.view.sliderMediums.setValue(int(valuesDict["mediums"][1]))
         self.view.editMediums.setText(str(valuesDict["mediums"][1]))
 
-        self.view.sliderWhites.setValue(valuesDict["whites"][1])
+        self.view.sliderWhites.setValue(int(valuesDict["whites"][1]))
         self.view.editWhites.setText(str(valuesDict["whites"][1]))
 
-        self.view.sliderHighlights.setValue(valuesDict["highlights"][1])
+        self.view.sliderHighlights.setValue(int(valuesDict["highlights"][1]))
         self.view.editHighlights.setText(str(valuesDict["highlights"][1]))
 
         self.callBackActive = True
+    # -----------------------------------------------------------------------------     
+    # zj add for semi-auto curve begin
+    def autoCurve(self):
+        processPipe = self.parent.controller.model.getProcessPipe()
+        if processPipe != None :
+            idExposure = processPipe.getProcessNodeByName("tonecurve")
+            bins = np.linspace(0,1,50+1)
+
+            imageBeforeColorData = processPipe.processNodes[idExposure-1].outputImage.colorData
+            imageBeforeColorData[imageBeforeColorData>1]=1
+            imageBeforeY = colour.sRGB_to_XYZ(imageBeforeColorData, apply_cctf_decoding=False)[:,:,1]
+            nphistBefore  = np.histogram(imageBeforeY, bins)[0]
+            nphistBefore  = nphistBefore/np.amax(nphistBefore)
 
+            npImgHistCumuNorm = np.empty_like(nphistBefore)
+            npImgHistCumu = np.cumsum(nphistBefore)
+            npImgHistCumuNorm = npImgHistCumu/np.max(npImgHistCumu)
+            
+            #predict keypoint value
+            if self.networkModel == None:
+                self.networkModel = Net(50,5)
+                self.networkModel.load_state_dict(torch.load(self.weightFile))
+                self.networkModel.eval()
+
+            with torch.no_grad():
+                x = Variable(torch.FloatTensor([npImgHistCumuNorm.tolist(),]), requires_grad=True)
+                y_predict = self.networkModel(x)
+
+            kpc = (y_predict[0]*100).tolist() 
+            kpcDict = {'start':[0.0,0.0], 'shadows': [10.0,kpc[0]], 'blacks': [30.0,kpc[1]], 'mediums': [50.0,kpc[2]], 'whites': [70.0,kpc[3]], 'highlights': [90.0,kpc[4]], 'end': [100.0,100.0]}
+            self.setValues(kpcDict,callBackActive = True)
+            self.parent.controller.changeToneCurve(kpcDict) 
+     
+    # zj add for semi-auto curve end   
+
+    # -----------------------------------------------------------------------------
     def reset(self, key):
         if pref.verbose: print(" [CONTROL] >> ToneCurveController.reset(",key,") ")
 
@@ -952,50 +865,64 @@ class ToneCurveController():
         self.setValues(controls,callBackActive = False)
 
         self.parent.controller.changeToneCurve(controls) 
-
+    # -----------------------------------------------------------------------------
     def plotCurve(self):
-        self.view.curve.plot([0,100],[0,100],'r--', clear=True)
-        self.view.curve.plot([20,20],[0,100],'r--', clear=False)
-        self.view.curve.plot([40,40],[0,100],'r--', clear=False)
-        self.view.curve.plot([60,60],[0,100],'r--', clear=False)
-        self.view.curve.plot([80,80],[0,100],'r--', clear=False)
-
-        processPipe = self.parent.controller.model.getProcessPipe()
-        idExposure = processPipe.getProcessNodeByName("tonecurve")
-
-        bins = np.linspace(0,1,50+1)
-
-        if self.showInput:
-            imageInput = copy.deepcopy(processPipe.getInputImage())
-            if imageInput.linear: imageInputColorData =colour.cctf_encoding(imageInput.colorData, function='sRGB')
-            else: imageInputColorData = imageInput.colorData
-            imageInputColorData[imageInputColorData>1]=1
-            imageInputY = colour.sRGB_to_XYZ(imageInputColorData, apply_cctf_decoding=False)[:,:,1]
-            nphistInput  = np.histogram(imageInputY, bins)[0]
-            nphistInput  = nphistInput/np.amax(nphistInput)
-            self.view.curve.plot(bins[:-1]*100,nphistInput*100,'k--',  clear=False)
-
-        if self.showbefore:
-            imageBeforeColorData = processPipe.processNodes[idExposure-1].outputImage.colorData
-            imageBeforeColorData[imageBeforeColorData>1]=1
-            imageBeforeY = colour.sRGB_to_XYZ(imageBeforeColorData, apply_cctf_decoding=False)[:,:,1]
-            nphistBefore  = np.histogram(imageBeforeY, bins)[0]
-            nphistBefore  = nphistBefore/np.amax(nphistBefore)
-            self.view.curve.plot(bins[:-1]*100,nphistBefore*100,'b--',  clear=False)
-
-        if self.showAfter:
-            imageAftercolorData = processPipe.processNodes[idExposure].outputImage.colorData
-            imageAftercolorData[imageAftercolorData>1]=1
-            imageAfterY  = colour.sRGB_to_XYZ(imageAftercolorData,  apply_cctf_decoding=False)[:,:,1]
-            nphistAfter   = np.histogram(imageAfterY, bins)[0]
-            nphistAfter   =nphistAfter/np.amax(nphistAfter)
-            self.view.curve.plot(bins[:-1]*100,nphistAfter*100,'b',     clear=False)
-
-        controlPointCoordinates= np.asarray(list(self.model.control.values()))
-        self.view.curve.plot(controlPointCoordinates[1:-1,0],controlPointCoordinates[1:-1,1],'ro', clear=False)
-        points = np.asarray(self.model.curve.evalpts)
-        x = points[:,0]
-        self.view.curve.plot(points[x<100,0],points[x<100,1],'r',clear=False)
+        try:
+            self.view.curve.plot([0,100],[0,100],'r--', clear=True)
+            self.view.curve.plot([20,20],[0,100],'r--', clear=False)
+            self.view.curve.plot([40,40],[0,100],'r--', clear=False)
+            self.view.curve.plot([60,60],[0,100],'r--', clear=False)
+            self.view.curve.plot([80,80],[0,100],'r--', clear=False)
+
+            processPipe = self.parent.controller.model.getProcessPipe()
+            idExposure = processPipe.getProcessNodeByName("tonecurve")
+
+            bins = np.linspace(0,1,50+1)
+
+            if self.showInput:
+                imageInput = copy.deepcopy(processPipe.getInputImage())
+                if imageInput.linear: imageInputColorData =colour.cctf_encoding(imageInput.colorData, function='sRGB')
+                else: imageInputColorData = imageInput.colorData
+                imageInputColorData[imageInputColorData>1]=1
+                imageInputY = colour.sRGB_to_XYZ(imageInputColorData, apply_cctf_decoding=False)[:,:,1]
+                nphistInput  = np.histogram(imageInputY, bins)[0]
+                nphistInput  = nphistInput/np.amax(nphistInput)
+                self.view.curve.plot(bins[:-1]*100,nphistInput*100,'k--',  clear=False)
+
+            if self.showbefore:
+                imageBeforeColorData = processPipe.processNodes[idExposure-1].outputImage.colorData
+                imageBeforeColorData[imageBeforeColorData>1]=1
+                imageBeforeY = colour.sRGB_to_XYZ(imageBeforeColorData, apply_cctf_decoding=False)[:,:,1]
+                nphistBefore  = np.histogram(imageBeforeY, bins)[0]
+                nphistBefore  = nphistBefore/np.amax(nphistBefore)
+                self.view.curve.plot(bins[:-1]*100,nphistBefore*100,'b--',  clear=False)
+
+            if self.showAfter:
+                imageAftercolorData = processPipe.processNodes[idExposure].outputImage.colorData
+                imageAftercolorData[imageAftercolorData>1]=1
+                imageAfterY  = colour.sRGB_to_XYZ(imageAftercolorData,  apply_cctf_decoding=False)[:,:,1]
+                nphistAfter   = np.histogram(imageAfterY, bins)[0]
+                nphistAfter   =nphistAfter/np.amax(nphistAfter)
+                self.view.curve.plot(bins[:-1]*100,nphistAfter*100,'b',     clear=False)
+
+            if self.showOutput:
+                imageAftercolorData = processPipe.getImage(toneMap=True).colorData
+                imageAftercolorData[imageAftercolorData>1]=1
+                imageAfterY  = colour.sRGB_to_XYZ(imageAftercolorData,  apply_cctf_decoding=False)[:,:,1]
+                nphistAfter   = np.histogram(imageAfterY, bins)[0]
+                nphistAfter   =nphistAfter/np.amax(nphistAfter)
+                self.view.curve.plot(bins[:-1]*100,nphistAfter*100,'b',     clear=False)
+
+            controlPointCoordinates= np.asarray(list(self.model.control.values()))
+            self.view.curve.plot(controlPointCoordinates[1:-1,0],controlPointCoordinates[1:-1,1],'ro', clear=False)
+            points = np.asarray(self.model.curve.evalpts)
+            x = points[:,0]
+            self.view.curve.plot(points[x<100,0],points[x<100,1],'r',clear=False)
+        except:
+            time.sleep(0.5)
+            self.plotCurve()
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class LightnessMaskController():
     def __init__(self, parent):
@@ -1006,13 +933,13 @@ class LightnessMaskController():
         self.view = view.LightnessMaskView(self)
 
         self.callBackActive = True
-
+    # -----------------------------------------------------------------------------
     def maskChange(self,key, on_off):
         if pref.verbose: print(" [CB] >> MaskLightnessController.maskChange(",key,",",on_off,")[callBackActive:",self.callBackActive,"] ")
 
         maskState = self.model.maskChange(key, on_off)  
         self.parent.controller.changeLightnessMask(maskState) 
-
+    # -----------------------------------------------------------------------------
     def setValues(self, values,callBackActive = False):
         if pref.verbose: print(" [CONTROL] >> LightnessMaskController.setValue(",values,") ")
 
@@ -1028,6 +955,8 @@ class LightnessMaskController():
 
         self.callBackActive = True
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class HDRviewerController():
     def __init__(self, parent):
         if pref.verbose: print(" [CONTROL] >> HDRviewerController.__init__(",")")
@@ -1132,6 +1061,8 @@ class HDRviewerController():
             subprocess.run(['taskkill', '/F', '/T', '/IM', "HDRImageViewer*"],capture_output=False)
             self.viewerProcess = None
 # ------------------------------------------------------------------------------------------
+# ---- Class LchColorSelectorController ----------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class LchColorSelectorController:
     def __init__(self, parent, idName = None):
         if pref.verbose: print(" [CONTROL] >> LchColorSelectorController.__init__(",") ")
@@ -1181,8 +1112,8 @@ class LchColorSelectorController:
         self.callBackActive = callBackActive
         # slider hue selection
         v = values['selection']['hue'] if 'hue' in values['selection'].keys() else (0,360)
-        self.view.sliderHueMin.setValue(v[0])
-        self.view.sliderHueMax.setValue(v[1])
+        self.view.sliderHueMin.setValue(int(v[0]))
+        self.view.sliderHueMax.setValue(int(v[1]))
 
         # slider chroma selection
         v = values['selection']['chroma'] if 'chroma' in values['selection'].keys() else (0,100)
@@ -1191,39 +1122,64 @@ class LchColorSelectorController:
 
         # slider lightness
         v = values['selection']['lightness'] if 'lightness' in values['selection'].keys() else (0,100)
-        self.view.sliderLightMin.setValue(v[0]*3)
-        self.view.sliderLightMax.setValue(v[1]*3)
+        self.view.sliderLightMin.setValue(int(v[0]*3))
+        self.view.sliderLightMax.setValue(int(v[1]*3))
 
         # hue shift editor
         v = values['edit']['hue'] if 'hue' in values['edit'].keys() else 0
-        self.view.sliderHueShift.setValue(v)
+        self.view.sliderHueShift.setValue(int(v))
         self.view.valueHueShift.setText(str(v)) 
 
         # exposure editor
-        v = values['edit']['exposure'] if 'exposure' in values['edit'].keys() else 0
-        self.view.sliderExposure.setValue(v*30)
+        v : int = values['edit']['exposure'] if 'exposure' in values['edit'].keys() else 0
+        self.view.sliderExposure.setValue(int(v*30))
         self.view.valueExposure.setText(str(v)) 
 
         # contrast editor
-        v = values['edit']['contrast'] if 'contrast' in values['edit'].keys() else 0
-        self.view.sliderContrast.setValue(v)
+        v : int = values['edit']['contrast'] if 'contrast' in values['edit'].keys() else 0
+        self.view.sliderContrast.setValue(int(v))
         self.view.valueContrast.setText(str(v))  
 
         # saturation editor
-        v = values['edit']['saturation'] if 'saturation' in values['edit'].keys() else 0
-        self.view.sliderSaturation.setValue(v)
+        v : int = values['edit']['saturation'] if 'saturation' in values['edit'].keys() else 0
+        self.view.sliderSaturation.setValue(int(v))
         self.view.valueSaturation.setText(str(v))  
 
         # mask
-        v = values['mask'] if 'mask' in values.keys() else False
+        v : bool = values['mask'] if 'mask' in values.keys() else False
         self.view.checkboxMask.setChecked(values['mask'])             
 
         self.model.setValues(values)
 
         self.callBackActive = True
+
+    # -----
+    def resetSelection(self): 
+        if pref.verbose: print(" [CONTROL] >> LchColorSelectorController.resetSelection(",") ")
+
+        default = copy.deepcopy(self.model.default)
+        current = copy.deepcopy(self.model.getValues())
+        
+        current['selection'] = default['selection']
+
+        self.setValues(current,callBackActive = True)
+        self.callBackActive = True
+
+    def resetEdit(self): 
+        if pref.verbose: print(" [CONTROL] >> LchColorSelectorController.resetEdit(",") ")
+
+        default = copy.deepcopy(self.model.default)
+        current = copy.deepcopy(self.model.getValues())
+        
+        current['edit'] = default['edit']
+
+        self.setValues(current,callBackActive = True)
+        self.callBackActive = True
+# ------------------------------------------------------------------------------------------
+# ---- Class LchColorSelectorController ----------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class GeometryController:
-    def __init__(self, parent, ):
+    def __init__(self, parent ):
         if pref.verbose: print(" [CONTROL] >> GeometryController.__init__(",") ")
         self.parent = parent
         self.model =    model.GeometryModel(self)
@@ -1247,19 +1203,42 @@ class GeometryController:
 
         self.callBackActive = callBackActive
 
-        self.view.sliderCroppingVerticalAdjustement.setValue(up)
-        self.view.sliderRotation.setValue(rotation*6)
+        self.view.sliderCroppingVerticalAdjustement.setValue(int(up))
+        self.view.sliderRotation.setValue(int(rotation*6))
        
         self.model.setValues(values)
 
         self.callBackActive = True
+# ------------------------------------------------------------------------------------------
+# ---- Class AestheticsImageController -----------------------------------------------------
+# ------------------------------------------------------------------------------------------
+class ImageAestheticsController:
+    def __init__(self, parent=None, HDRcontroller = None):
+        if pref.verbose: print(" [CONTROL] >> AestheticsImageController.__init__(",")")
+
+        self.parent = parent
+        self.model = model.ImageAestheticsModel(self)
+        self.view = view.ImageAestheticsView(self)
+    # --------------------------------------------------------------------------------------
+    def buildView(self,processPipe=None):
+        if pref.verbose: print(" [CONTROL] >> AestheticsImageController.buildView()")
+
+        # called when MultiDockController recall a controller/view 
+        self.view = view.ImageAestheticsView(self)
+        if processPipe: self.setProcessPipe(processPipe)
+    # --------------------------------------------------------------------------------------
+    def setProcessPipe(self, processPipe): 
+        if pref.verbose: print(" [CONTROL] >> AestheticsImageController.setProcessPipe()")
 
+        self.model.setProcessPipe(processPipe)
+        self.view.setProcessPipe(processPipe, self.model.getPaletteImage())
+
+        return True
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
-# message widget functions
+# --- message widget functions -------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
-
 def messageBox(title, text):
         msg = QMessageBox()
         msg.setText(text)
@@ -1268,6 +1247,7 @@ def messageBox(title, text):
         msg.setStandardButtons(QMessageBox.Ok)
         msg.setEscapeButton(QMessageBox.Close)
         msg.exec_()
+# -----------------------------------------------------------------------------
 def okCancelBox(title, text):
         msg = QMessageBox()
         msg.setText(text)
@@ -1276,4 +1256,26 @@ def okCancelBox(title, text):
         msg.setEscapeButton(QMessageBox.Close)
         return msg.exec_()
 # ------------------------------------------------------------------------------------------
+# ---- Class ColorEditorsAutoController ----------------------------------------------------
+# ------------------------------------------------------------------------------------------
+class  ColorEditorsAutoController:
+    def __init__(self, parent, controlledColorEditors, stepName ):
+        if pref.verbose: print(" [CONTROL] >> ColorEditorsAutoController.__init__(",") ")
+
+        self.parent = parent
+        self.controlled = controlledColorEditors
+        self.stepName =stepName
+
+        self.model =    model.ColorEditorsAutoModel(self, stepName,len(controlledColorEditors), removeBlack= True)
+        self.view =     view.ColorEditorsAutoView(self)
+
+        self.callBackActive = True
+    # callbacks
+    def auto(self): 
+        if pref.verbose: print(" [CONTROL] >> ColorEditorsAutoController.auto(",") ")
+        for ce in self.controlled: ce.resetSelection(); ce.resetEdit()
+        values = self.model.compute()
+
+        if values != None:
+            for i,v in enumerate(values): self.controlled[i].setValues(v, callBackActive = False)
 

+ 219 - 176
uHDR/guiQt/model.py

@@ -14,101 +14,19 @@
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 
-# -----------------------------------------------------------------------------
-# --- VERSION TRACKING --------------------------------------------------------
-# -----------------------------------------------------------------------------
-
-# -----------------------------------------------------------------------------
-# (1): DEBUG
-#      HDRImageViewer doesn't open 
-#      add time.sleep() in HDRviewerController.displayFile(...)
-#      ---- Rémi Cozot, June 2021
-# -----------------------------------------------------------------------------
-# (2): Prevent Freezing
-#      Add multithreading in:
-#      i  - processpipe processing (except for export and display)
-#      ii - loading images
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (3): Prevent Freezing and speed up
-#      Add multithreading in:
-#      i  - processpipe processing for HDR display
-#      ii - processpipe procssing export single image to vesaHDR1000
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (4): Fix ev value and accuracy in color editor 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (5): Fix Y curve plot 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (6): Ability to export SDR (.jpg) image to HDR (tone expansion).
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-
 # -----------------------------------------------------------------------------
 # --- Package hdrGUI ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
-package hdrGUI consists of the classes for GUI:
-(1) controller classes:
-    class GalleryMode(enum.Enum)
-    class ImageWidgetController
-    class ImageGalleryController()
-    class AppController(object)
-    class MultiDockController()
-    class EditImageController
-    class ImageInfoController
-    class AdvanceSliderController()
-    class ToneCurveController()
-    class LightnessMaskController()
-    class HDRviewerController()
-    class LchColorSelectorController()
-    class GeometryController()
-(2) model classes: 
-    class ImageWidgetModel(object)
-    class ImageGalleryModel
-    class AppModel(object)
-    class EditImageModel(object)
-    class AdvanceSliderModel()
-    class ToneCurveModel()
-    class LightnessMaskModel()
-    class ImageInfoModel(object)
-    class HDRviewerModel(object)
-    class ImageQualityModel(object)
-    class LchColorSelectorModel(object)
-    class GeometryModel(object)
-(3) view classes:
-    class ImageWidgetView(QWidget)
-    class FigureWidget(FigureCanvas)
-    class ImageGalleryView(QSplitter)
-    class AppView(QMainWindow)
-    class ImageInfoView(QSplitter)
-    class AdvanceLineEdit(object)
-    class AdvanceCheckBox(object)
-    class EditImageView(QSplitter)
-    class MultiDockView(QDockWidget)
-    class AdvanceSliderView(QFrame)
-    class ToneCurveView(QFrame)
-    class LightnessMaskView(QGroupBox)
-    class HDRviewerView(QFrame)
-    class ImageQualityView(QScrollArea)
-    class LchColorSelectorView(QFrame)
-    class GeometryView(QFrame)
-(4) Qt multi threading classes:
-    class RequestCompute(object)
-    class RunCompute(QRunnable)
-    class RequestLoadImage(object)
-    class RunLoadImage(QRunnable)
-    class pCompute(object)
-    class pRun(QRunnable)
+package hdrGUI consists of the classes for GUI.
+
 """
 
 # -----------------------------------------------------------------------------
 # --- Import ------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 
-import os, colour, copy, json, time
+import os, colour, copy, json, time, sklearn.cluster, math
 import pathos.multiprocessing, multiprocessing, functools
 import numpy as np
 from geomdl import BSpline
@@ -116,7 +34,7 @@ from geomdl import utilities
 
 from datetime import datetime
 
-import hdrCore.image, hdrCore.utils
+import hdrCore.image, hdrCore.utils, hdrCore.aesthetics, hdrCore.image
 from . import controller, thread
 import hdrCore.processing, hdrCore.quality
 import preferences.preferences as pref
@@ -152,6 +70,8 @@ class ImageGalleryModel:
             processPipes (list[hdrCore.porocessing.ProcessPipe]): list of process-pipes associated to images
             _selectedImage (int): index of current (selected) process-pipe
 
+            aestheticsModels (list[hdrCore.aesthetics.MultidimensionalImageAestheticsModel])
+
         Methods:
             setSelectedImage
             selectedImage
@@ -171,11 +91,15 @@ class ImageGalleryModel:
         self.processPipes = []
         self._selectedImage= -1
 
+        self.aesthetics = []
+
     def setSelectedImage(self,id): self._selectedImage = id
 
     def selectedImage(self): return self._selectedImage
 
     def getSelectedProcessPipe(self):
+        if pref.verbose: print(" [MODEL] >> ImageGalleryModel.getSelectedProcessPipe(",  ")")
+
         res = None
         if self._selectedImage != -1: res= self.processPipes[self._selectedImage]
         return res
@@ -185,6 +109,9 @@ class ImageGalleryModel:
 
         self.imageFilenames = list(filenames)
         self.imagesMetadata, self.processPipes =  [], [] # reset metadata and processPipes
+
+        self.aestheticsModels = [] # reset aesthetics models
+
         nbImagePage = controller.GalleryMode.nbRow(self.controller.view.shapeMode)*controller.GalleryMode.nbCol(self.controller.view.shapeMode)
         for f in self.imageFilenames: # load only first page
             self.processPipes.append(None)
@@ -225,6 +152,9 @@ class ImageGalleryModel:
 
     def getProcessPipeById(self,i):
         return self.processPipes[i]
+
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class AppModel(object):
     """ class for main window model """
@@ -232,22 +162,23 @@ class AppModel(object):
     def __init__(self, controller):
         # attributes
         self.controller = controller
-        self.directory = '.'
+        self.directory = pref.getImagePath()
         self.imageFilenames = []
         self.displayHDRProcess = None
+        #V5
         self.displayModel = {'scaling':12, 'shape':(2160,3840)}
 
     def setDirectory(self,path):
         # read directory and return image filename list
         self.directory =path
+        pref.setImagePath(path)
         self.imageFilenames = map(lambda x: os.path.join(self.directory,x),
                                   hdrCore.utils.filterlistdir(self.directory,('.jpg','.JPG','.hdr','.HDR')))
 
         return self.imageFilenames
-
-# -----------------------------------------------------------------------------
-# --- Class EditImageModel ----------------------------------------------------
-# -----------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# --- Class EditImageModel -----------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class EditImageModel(object):
     """
     xxxx xxxx xxxx.
@@ -301,6 +232,7 @@ class EditImageModel(object):
         else:
             return False
 
+    @staticmethod
     def buildProcessPipe():
         """
         WARNING: 
@@ -341,24 +273,44 @@ class EditImageModel(object):
         processPipe.setParameters(idLightnessMaskProcessNode, defaultMask)  
 
         # saturation ---------------------------------------------------------------------------------------------------------
-        defaultValue = {'saturation': 0.0, 
-                        'method': 'gamma'}
+        defaultValue = {'saturation': 0.0,  'method': 'gamma'}
         idSaturationProcessNode = processPipe.append(hdrCore.processing.saturation(), paramDict=None, name="saturation")    
         processPipe.setParameters(idSaturationProcessNode, defaultValue)                     
 
         # colorEditor0 ---------------------------------------------------------------------------------------------------------
         defaultParameterColorEditor0= {'selection': {'lightness': (0,100),'chroma': (0,100),'hue':(0,360)},  
-                                       'edit': {'exposure':0.0,'contrast':0.0,'saturation':0.0}, 
+                                       'edit': {'hue': 0.0, 'exposure':0.0, 'contrast':0.0,'saturation':0.0}, 
                                        'mask': False}        
         idColorEditor0ProcessNode = processPipe.append(hdrCore.processing.colorEditor(), paramDict=None, name="colorEditor0")  
         processPipe.setParameters(idColorEditor0ProcessNode, defaultParameterColorEditor0)
 
         # colorEditor1 ---------------------------------------------------------------------------------------------------------
         defaultParameterColorEditor1= {'selection': {'lightness': (0,100),'chroma': (0,100),'hue':(0,360)},  
-                                       'edit': {'exposure':0.0,'contrast':0.0,'saturation':0.0}, 
+                                       'edit': {'hue': 0.0, 'exposure':0.0, 'contrast':0.0,'saturation':0.0}, 
                                        'mask': False}        
         idColorEditor1ProcessNode = processPipe.append(hdrCore.processing.colorEditor(), paramDict=None, name="colorEditor1")  
         processPipe.setParameters(idColorEditor1ProcessNode, defaultParameterColorEditor1)
+        
+        # colorEditor2 ---------------------------------------------------------------------------------------------------------
+        defaultParameterColorEditor2= {'selection': {'lightness': (0,100),'chroma': (0,100),'hue':(0,360)},  
+                                       'edit': {'hue': 0.0, 'exposure':0.0, 'contrast':0.0,'saturation':0.0}, 
+                                       'mask': False}        
+        idColorEditor2ProcessNode = processPipe.append(hdrCore.processing.colorEditor(), paramDict=None, name="colorEditor2")  
+        processPipe.setParameters(idColorEditor2ProcessNode, defaultParameterColorEditor2)
+        
+        # colorEditor3 ---------------------------------------------------------------------------------------------------------
+        defaultParameterColorEditor3= {'selection': {'lightness': (0,100),'chroma': (0,100),'hue':(0,360)},  
+                                       'edit': {'hue': 0.0, 'exposure':0.0, 'contrast':0.0,'saturation':0.0}, 
+                                       'mask': False}        
+        idColorEditor3ProcessNode = processPipe.append(hdrCore.processing.colorEditor(), paramDict=None, name="colorEditor3")  
+        processPipe.setParameters(idColorEditor3ProcessNode, defaultParameterColorEditor3)
+        
+        # colorEditor4 ---------------------------------------------------------------------------------------------------------
+        defaultParameterColorEditor4= {'selection': {'lightness': (0,100),'chroma': (0,100),'hue':(0,360)},  
+                                       'edit': {'hue': 0.0, 'exposure':0.0, 'contrast':0.0,'saturation':0.0}, 
+                                       'mask': False}        
+        idColorEditor4ProcessNode = processPipe.append(hdrCore.processing.colorEditor(), paramDict=None, name="colorEditor4")  
+        processPipe.setParameters(idColorEditor4ProcessNode, defaultParameterColorEditor4)
 
         # geometry ---------------------------------------------------------------------------------------------------------
         defaultValue = { 'ratio': (16,9), 'up': 0,'rotation': 0.0}
@@ -444,6 +396,8 @@ class EditImageModel(object):
     def updateImage(self, imgTM):
         self.controller.updateImage(imgTM)
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class AdvanceSliderModel():
     def __init__(self, controller, value):
         self.controller = controller
@@ -451,6 +405,8 @@ class AdvanceSliderModel():
     def setValue(self, value): self.value =  value
     def toDict(self): return {'value': self.value}
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class ToneCurveModel():
     """
 
@@ -475,7 +431,7 @@ class ToneCurveModel():
         |   o   |       |       |       |       |                              |
         |       |       |       |       |       |                              |
        [o]-------+-------+-------+-------+-------+-----------------------------+ 
-  zeros ^ shadows  black   medium  white  highlights                          max
+  zeros ^ shadows  black   medium  white  highlights                          200
 
 
 
@@ -552,6 +508,8 @@ class ToneCurveModel():
     def setValues(self, controlPointsDict):
         self.control = copy.deepcopy(controlPointsDict)
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class LightnessMaskModel():
     def __init__(self, _controller):
         self.controller = _controller
@@ -564,6 +522,8 @@ class LightnessMaskModel():
 
     def setValues(self, values): self.masks = copy.deepcopy(values)
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class ImageInfoModel(object):
 
     def __init__(self,controller):
@@ -576,103 +536,50 @@ class ImageInfoModel(object):
 
     def setProcessPipe(self, processPipe):  self.processPipe = processPipe
 
-    def changeUseCase(self,useCaseClass,useCase, on_off): 
-        if pref.verbose:  print(" [MODEL] >> ImageInfoModel.changeUseCase(",useCaseClass,",",useCase,",", on_off,")")
+    def changeMeta(self,tagGroup,tag, on_off): 
+        if pref.verbose:  print(" [MODEL] >> ImageInfoModel.changeMeta(",tagGroup,",",tag,",", on_off,")")
 
         if isinstance(self.processPipe, hdrCore.processing.ProcessPipe):
-            hdrUseCases = copy.deepcopy(self.processPipe.getImage().metadata.metadata["hdr-use-case"])
-            found, updatedHdrUseCases = False, []
-            for useCC in hdrUseCases:
-                if useCaseClass in useCC.keys():
-                    if useCase in useCC[useCaseClass].keys():
+            tagRootName = self.processPipe.getImage().metadata.otherTags.getTagsRootName()
+            tags = copy.deepcopy(self.processPipe.getImage().metadata.metadata[tagRootName])
+            found, updatedMeta = False, []
+            for tt in tags:
+                if tagGroup in tt.keys():
+                    if tag in tt[tagGroup].keys():
                         found = True
-                        useCC[useCaseClass][useCase] = on_off 
-                updatedHdrUseCases.append(copy.deepcopy(useCC))
-            self.processPipe.updateHDRuseCase(updatedHdrUseCases)
+                        tt[tagGroup][tag] = on_off 
+                updatedMeta.append(copy.deepcopy(tt))
+            self.processPipe.updateUserMeta(tagRootName,updatedMeta)
             if pref.verbose: 
                 print(" [MODEL] >> ImageInfoModel.changeUseCase(",")")
-                for useCC in updatedHdrUseCases: print(useCC)     
+                for tt in updatedMeta: print(tt)     
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class CurveControlModel(object): pass
 # ------------------------------------------------------------------------------------------
+# ---- HDRviewerModel ----------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class HDRviewerModel(object):
     def __init__(self,_controller):
+        if pref.verbose: print(" [MODEL] >> HDRviewerModel.__init__(",")")
+
         self.controller = _controller
 
         # current image
         self.currentIMG = None
 
-        # display model
-        self.displayModel = {'scaling':12, 'shape':(2160,3840)}
-
-    def scaling(self): return self.displayModel['scaling']
-    def shape(self): return self.displayModel['shape']
-# ------------------------------------------------------------------------------------------
-class ImageQualityModel(object):
-    def __init__(self, _controller):
-        self.controller = _controller
+        self.displayModel = pref.getHDRdisplays()
 
-        self.acrStr = ["not assessed","bad","poor","fair","good","excellent"]
+    def scaling(self): 
+        if pref.verbose: print(f" [MODEL] >> HDRviewerModel.scaling():{self.displayModel['scaling']}")
+        return self.displayModel['scaling']
 
-        self.processPipes =     []
-        self.assessments =      []
-        self.localImageNames =  []
-        self.currentImage =     -1
-        self.userName =         None
-
-    def setImages(self, filenames): 
-        self.localImageNames = filenames
-        for li in self.localImageNames: 
-            self.assessments.append(hdrCore.quality.quality())
-
-    def resetImages(self): 
-        self.localImagesNames = []
-        self.currentImage =     -1
-        self.controller.HDRcontroller.displaySplash()
-
-    def next(self):
-        if pref.verbose:  print(" [MODEL] >> ImageQualityModel.next()")
-
-        self.currentImage = (self.currentImage+1)%len(self.localImageNames)
-        self.controller.HDRcontroller.displayFile(self.localImageNames[self.currentImage])
-        return self.assessments[self.currentImage]
-
-    def previous(self):
-        if pref.verbose:  print(" [MODEL] >> ImageQualityModel.previous()")
-
-        self.currentImage = (self.currentImage-1)%len(self.localImageNames)
-        self.controller.HDRcontroller.displayFile(self.localImageNames[self.currentImage])       
-        return self.assessments[self.currentImage]
-
-    def setPseudo(self,name):  self.userName = name
-
-    def start(self):
-        for i, q in enumerate(self.assessments):
-            img = self.processPipes[i].getInputImage()
-            q.user['pseudo'] = copy.deepcopy(self.userName)
-            q.imageNpath['name'] = copy.deepcopy(img.name)
-            q.imageNpath['path'] = copy.deepcopy(img.path)
-
-    def setScore(self, label, value):
-
-        if self.currentImage != -1 :
-            self.assessments[self.currentImage].score[label] = value
-
-    def setArtifact(self, label, value):
-
-        if self.currentImage != -1 :
-            self.assessments[self.currentImage].artifact[label] = value
-
-    def save(self):
-        assessmentDicts = []
-        for ass in self.assessments: assessmentDicts.append(ass.toDict())
-        resDict={'assessment':assessmentDicts}
-        # -----------------------------------------------------------
-        i=0
-        path = self.assessments[0].imageNpath['path']+'/'+'iqa_'
-        while(os.path.isfile(path+str(i)+'.json')): i+= 1
-        # -----------------------------------------------------------
-        with open(path+str(i)+'.json', "w") as file: json.dump(resDict,file)
+    def shape(self): 
+        if pref.verbose: print(f" [MODEL] >> HDRviewerModel.shape():{self.displayModel['shape']}")        
+        return self.displayModel['shape']
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class LchColorSelectorModel(object):
     def __init__(self, _controller):
@@ -689,6 +596,14 @@ class LchColorSelectorModel(object):
 
         self.mask =         False
 
+        # -------------
+        self.default = {
+            "selection": {"lightness": [ 0, 100 ],"chroma": [ 0, 100 ],"hue": [ 0, 360 ]},
+            "edit": {"hue": 0,"exposure": 0,"contrast": 0,"saturation": 0},
+            "mask": False}
+        # -------------
+
+
     def setHueSelection(self, hMin,hMax):
         self.hueSelection = hMin,hMax
         return self.getValues()
@@ -740,6 +655,8 @@ class LchColorSelectorModel(object):
 
         self.mask =         values['mask']  if 'mask' in values.keys() else False
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class GeometryModel(object):
     def __init__(self, _controller):
         self.controller = _controller
@@ -764,11 +681,137 @@ class GeometryModel(object):
         self.up =       values['up']        if 'up'         in values.keys() else 0.0
         self.rotation = values['rotation']  if 'rotation'   in values.keys() else 0.0
 # ------------------------------------------------------------------------------------------
+# ---- Class AestheticsImageModel ----------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+class ImageAestheticsModel:
+    """class ImageAesthetics: encapsulates color palette (and related parameters), convexHull composition (and related parameters), etc.
 
+        Attributes:
+            parent (guiQt.controller.ImageAestheticsController): controller
+            processPipe (hdrCore.processing.ProcessPipe): current selected process-pipe
 
+        Methods:
 
 
+    """
+    def __init__(self, parent):
+        if pref.verbose: print(" [MODEL] >> ImageAestheticsModel.__init__(",")")
+        
+        self.parent = parent
+
+        # processPipeHasChanged
+        self.requireUpdate = True
+
+
+        # ref to ImageGalleryModel.processPipes[ImageGalleryModel._selectedImage]
+        self.processPipe = None 
 
+        # color palette
+        self.colorPalette = hdrCore.aesthetics.Palette('defaultLab5',
+                                                       np.linspace([0,0,0],[100,0,0],5),
+                                                       hdrCore.image.ColorSpace.build('Lab'), 
+                                                       hdrCore.image.imageType.SDR)
+    # ------------------------------------------------------------------------------------------
+    def getProcessPipe(self): return self.processPipe
+    # ------------------------------------------------------------------------------------------
+    def setProcessPipe(self, processPipe):  
+        if pref.verbose: print(" [MODEL] >> ImageAestheticsModel.setProcessPipe(",")")
+
+        if processPipe != self.processPipe:
         
+            self.processPipe = processPipe
+            self.requireUpdate = True
+
+        if self.requireUpdate:
+
+            self.colorPalette = hdrCore.aesthetics.Palette.build(self.processPipe)
+            # COMPUTE IMAGE OF PALETTE
+            paletteIMG = self.colorPalette.createImageOfPalette()
+
+            self.endComputing()
+
+        else: pass
+    # ------------------------------------------------------------------------------------------
+    def endComputing(self):
+        self.requireUpdate = False
+    # ------------------------------------------------------------------------------------------
+    def getPaletteImage(self):
+        return self.colorPalette.createImageOfPalette()
+# ------------------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+class ColorEditorsAutoModel:
+    def __init__(self,_controller, processStepName,nbColors, removeBlack= True):
+        self.controller = _controller
+        self.processStepId = processStepName
+        self.nbColors = nbColors
+        self.removeBlack = removeBlack
+
+    def compute(self):
+        # get image according to processId
+        processPipe = self.controller.parent.controller.getProcessPipe()
+        if processPipe != None:
+            image_ = processPipe.processNodes[processPipe.getProcessNodeByName(self.processStepId)].outputImage
+
+            if image_.colorSpace.name == 'Lch':
+                LchPixels = image_.colorData
+            elif image_.colorSpace.name == 'sRGB':
+                if image_.linear: 
+                    colorLab = hdrCore.processing.sRGB_to_Lab(image_.colorData, apply_cctf_decoding=False)
+                    LchPixels = colour.Lab_to_LCHab(colorLab)
+                else:
+                    colorLab = hdrCore.processing.sRGB_to_Lab(image_.colorData, apply_cctf_decoding=True)
+                    LchPixels = colour.Lab_to_LCHab(colorLab)
+
+            # to Lab then to Vector
+            LabPixels = colour.LCHab_to_Lab(LchPixels)
+            LabPixelsVector = hdrCore.utils.ndarray2vector(LabPixels)
+
+            # k-means: nb cluster = nbColors + 1
+            kmeans_cluster_Lab = sklearn.cluster.KMeans(n_clusters=self.nbColors+1)
+            kmeans_cluster_Lab.fit(LabPixelsVector)
+            cluster_centers_Lab = kmeans_cluster_Lab.cluster_centers_
+            cluster_labels_Lab = kmeans_cluster_Lab.labels_
+                
+            # remove darkness one
+            idxLmin = np.argmin(cluster_centers_Lab[:,0])                           # idx of darkness
+            cluster_centers_Lab = np.delete(cluster_centers_Lab, idxLmin, axis=0)   # remove min from cluster_centers_Lab
+
+            # go to Lch
+            cluster_centers_Lch = colour.Lab_to_LCHab(cluster_centers_Lab) 
+
+            # sort cluster by hue
+            cluster_centersIdx = np.argsort(cluster_centers_Lch[:,2])
+
+            dictValuesList = []
+            for j in range(len(cluster_centersIdx)):
+                i = cluster_centersIdx[j]
+                if j==0: Hmin = 0
+                else: Hmin = 0.5*(cluster_centers_Lch[cluster_centersIdx[j-1]][2] + cluster_centers_Lch[cluster_centersIdx[j]][2])
+                if j == len(cluster_centersIdx)-1: Hmax = 360
+                else: Hmax = 0.5*(cluster_centers_Lch[cluster_centersIdx[j]][2] + cluster_centers_Lch[cluster_centersIdx[j+1]][2])
+
+                Cmin = max(0, cluster_centers_Lch[cluster_centersIdx[j]][1]-25) 
+                Cmax = min(100, cluster_centers_Lch[cluster_centersIdx[j]][1]+25) 
+
+                Lmin = max(0, cluster_centers_Lch[cluster_centersIdx[j]][0]-25) 
+                Lmax = min(100, cluster_centers_Lch[cluster_centersIdx[j]][0]+25) 
+
+                dictSegment =  {
+                    "selection": {
+                        "lightness":    [ Lmin,  Lmax],
+                        "chroma":       [ Cmin,  Cmax],
+                        "hue":          [ Hmin,  Hmax ]},
+                    "edit": {"hue": 0,"exposure": 0,"contrast": 0,"saturation": 0},
+                    "mask": False}
+                dictValuesList.append(dictSegment)
+
+            return dictValuesList
+# ------------------------------------------------------------------------------------------
+
+
+
+
 
 

+ 192 - 93
uHDR/guiQt/thread.py

@@ -14,95 +14,11 @@
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 
-# -----------------------------------------------------------------------------
-# --- VERSION TRACKING --------------------------------------------------------
-# -----------------------------------------------------------------------------
-
-# -----------------------------------------------------------------------------
-# (1): DEBUG
-#      HDRImageViewer doesn't open 
-#      add time.sleep() in HDRviewerController.displayFile(...)
-#      ---- Rémi Cozot, June 2021
-# -----------------------------------------------------------------------------
-# (2): Prevent Freezing
-#      Add multithreading in:
-#      i  - processpipe processing (except for export and display)
-#      ii - loading images
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (3): Prevent Freezing and speed up
-#      Add multithreading in:
-#      i  - processpipe processing for HDR display
-#      ii - processpipe procssing export single image to vesaHDR1000
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (4): Fix ev value and accuracy in color editor 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (5): Fix Y curve plot 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (6): Ability to export SDR (.jpg) image to HDR (tone expansion).
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-
-
 # -----------------------------------------------------------------------------
 # --- Package hdrGUI ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
-package hdrGUI consists of the classes for GUI:
-(1) controller classes:
-    class GalleryMode(enum.Enum)
-    class ImageWidgetController
-    class ImageGalleryController()
-    class AppController(object)
-    class MultiDockController()
-    class EditImageController
-    class ImageInfoController
-    class AdvanceSliderController()
-    class ToneCurveController()
-    class LightnessMaskController()
-    class HDRviewerController()
-    class LchColorSelectorController()
-    class GeometryController()
-(2) model classes: 
-    class ImageWidgetModel(object)
-    class ImageGalleryModel
-    class AppModel(object)
-    class EditImageModel(object)
-    class AdvanceSliderModel()
-    class ToneCurveModel()
-    class LightnessMaskModel()
-    class ImageInfoModel(object)
-    class HDRviewerModel(object)
-    class ImageQualityModel(object)
-    class LchColorSelectorModel(object)
-    class GeometryModel(object)
-(3) view classes:
-    class ImageWidgetView(QWidget)
-    class FigureWidget(FigureCanvas)
-    class ImageGalleryView(QSplitter)
-    class AppView(QMainWindow)
-    class ImageInfoView(QSplitter)
-    class AdvanceLineEdit(object)
-    class AdvanceCheckBox(object)
-    class EditImageView(QSplitter)
-    class MultiDockView(QDockWidget)
-    class AdvanceSliderView(QFrame)
-    class ToneCurveView(QFrame)
-    class LightnessMaskView(QGroupBox)
-    class HDRviewerView(QFrame)
-    class ImageQualityView(QScrollArea)
-    class LchColorSelectorView(QFrame)
-    class GeometryView(QFrame)
-(4) Qt multi threading classes:
-    class RequestCompute(object)
-    class RunCompute(QRunnable)
-    class RequestLoadImage(object)
-    class RunLoadImage(QRunnable)
-    class pCompute(object)
-    class pRun(QRunnable)
+package hdrGUI consists of the classes for GUI.
 """
 # -----------------------------------------------------------------------------
 # --- Import ------------------------------------------------------------------
@@ -172,6 +88,7 @@ class RequestCompute(object):
         """
         self.requestDict[id] = copy.deepcopy(params)
 
+
         if self.readyToRun:
             # start processing processpipe
             self.pool.start(RunCompute(self))
@@ -221,12 +138,19 @@ class RunCompute(QRunnable):
         """
         self.parent.readyToRun = False
         for k in self.parent.requestDict.keys(): self.parent.processpipe.setParameters(k,self.parent.requestDict[k])
-        start = timer()
-        self.parent.processpipe.compute()
-        dt = timer() - start
-        print("############# processpipe compute (",pref.computation,"):",dt)
-        self.parent.readyToRun = True
-        self.parent.endCompute()
+        cpp = True
+        if cpp:
+            img  = copy.deepcopy(self.parent.processpipe.getInputImage())
+            imgRes = hdrCore.coreC.coreCcompute(img, self.parent.processpipe)
+            self.parent.processpipe.setOutput(imgRes)
+            self.parent.readyToRun = True
+            self.parent.endCompute()
+        else:
+            start = timer()
+            self.parent.processpipe.compute()
+            dt = timer() - start
+            self.parent.readyToRun = True
+            self.parent.endCompute()
 # -----------------------------------------------------------------------------
 # --- Class RequestLoadImage --------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -316,7 +240,6 @@ class RunLoadImage(QRunnable):
             self.parent.endLoadImage(False, self.minIdxInPage, self.imgIdxInPage, processPipe, self.filename)
         except(IOError, ValueError) as e:
             self.parent.endLoadImage(True, self.minIdxInPage, self.imgIdxInPage, None, self.filename)
-
 # -----------------------------------------------------------------------------
 # --- Class pCompute ----------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -416,4 +339,180 @@ class pRun(QRunnable):
         """
         self.processpipe.compute()
         pRes = self.processpipe.getImage(toneMap=self.toneMap)
-        self.parent.endCompute(self.idx[0],self.idx[1], pRes)
+        self.parent.endCompute(self.idx[0],self.idx[1], pRes)
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# --- Class cCompute ----------------------------------------------------------
+# -----------------------------------------------------------------------------
+class cCompute(object):
+    """xxx
+    """
+
+    def __init__(self, callBack, processpipe, toneMap=True, progress=None):
+        self.callBack = callBack
+        self.progress =progress
+
+        # recover image
+        input =  processpipe.getInputImage()
+
+        self.pool = QThreadPool.globalInstance() 
+        self.pool.start(cRun(self,processpipe,toneMap))
+
+    def endCompute(self, img):
+        """
+        Args:
+
+        Returns:
+        
+        """
+        self.callBack(img)
+# -----------------------------------------------------------------------------
+# --- Class cRun --------------------------------------------------------------
+# -----------------------------------------------------------------------------
+class cRun(QRunnable):
+    """
+        Args:
+
+        Returns:
+        
+    """
+    def __init__(self,parent,processpipe,toneMap):
+        """
+        """
+        super().__init__()
+        self.parent = parent
+        self.processpipe = processpipe
+        self.toneMap = toneMap
+
+    def run(self):
+        """
+        Args:
+
+        Returns:
+        
+        """
+        img  = copy.deepcopy(self.processpipe.getInputImage())
+        imgRes = hdrCore.coreC.coreCcompute(img, self.processpipe)
+        self.processpipe.setOutput(imgRes)
+
+        pRes = self.processpipe.getImage(toneMap=self.toneMap)
+        self.parent.endCompute(pRes)
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# --- Class RequestAestheticsCompute ----------------------------------------------------
+# -----------------------------------------------------------------------------
+class RequestAestheticsCompute(object):
+    """
+    manage parallel (multithreading) computation of processpipe.compute() when editing image (not used for display HDR or export HDR):
+        - uses a single/specific thread to compute process-pipe,
+        - store compute request when user changes editing values, restart process-pipe computing when the previous one is finished.
+
+    Attributes:
+        parent (guiQt.model.EditImageModel): reference to parent, used to callback parent when processing is over.
+        requestDict (dict): dict that stores editing values.
+        pool (QThreadPool): Qt thread pool.
+        processpipe (hdrCore.processing.Processpipe): active processpipe.
+        readyToRun (bool): True when no processing is ongoing, else False.
+        waitingUpdate (bool): True if requestCompute has been called during a processing.
+
+    Methods:
+        setProcessPipe
+        requestCompute
+        endCompute
+    """
+
+    def __init__(self, parent):
+
+        self.parent = parent
+
+        self.requestDict= {} # store resqustCompute key:processNodeId, value: processNode params
+
+        self.pool = QThreadPool.globalInstance()        # get global pool
+        self.processpipe = None                         # processpipe ref
+
+        self.readyToRun = True
+        self.waitingUpdate = False
+
+    def setProcessPipe(self,pp):
+        """set the current active processpipe.
+
+            Args:
+                pp (hrdCore.processing.ProcessPipe, Required)
+
+            Returns:
+                
+        """
+        self.processpipe = pp
+    
+    def requestCompute(self, id, params):
+        """send new parameters for a process-node and request a new processpipe computation.
+
+            Args:
+                id (int, Required): index of process-node in processpipe
+                params (dict, Required): parameters of process-node 
+
+            Returns:
+                
+        """
+        self.requestDict[id] = copy.deepcopy(params)
+
+        if self.readyToRun:
+            # start processing processpipe
+            self.pool.start(RunAestheticsCompute(self))
+        else:
+            # if a computation is already running
+            self.waitingUpdate = True
+
+    def endCompute(self):
+        """called when process-node computation is finished.
+            Get processed image and send it to parent (guiQt.model.EditImageModel).
+            If there are new requestCompute, restart computation of processpipe
+
+        Args:
+
+        Retruns:
+
+        """
+        imgTM = self.processpipe.getImage(toneMap=True)
+        self.parent.updateImage(imgTM)
+        if self.waitingUpdate:
+            self.pool.start(RunAestheticsCompute(self))
+            self.waitingUpdate = False
+# -----------------------------------------------------------------------------
+# --- Class RunCompute --------------------------------------------------------
+# -----------------------------------------------------------------------------
+class RunAestheticsCompute(QRunnable):
+    """defines the run method that executes on a dedicated thread: processpipe computation.
+    
+        Attributes:
+            parent (guiQt.thread.RequestCompute): parent called.endCompute() when processing is over.
+
+        Methods:
+            run
+    """
+    def __init__(self,parent):
+        super().__init__()
+        self.parent = parent
+
+    def run(self):
+        """method called by the Qt Thread pool.
+            Calls parent.endCompute() when process is over.
+
+            Args:
+
+            Returns:
+
+        """
+        self.parent.readyToRun = False
+        for k in self.parent.requestDict.keys(): self.parent.processpipe.setParameters(k,self.parent.requestDict[k])
+        start = timer()
+        self.parent.processpipe.compute()
+        dt = timer() - start
+        self.parent.readyToRun = True
+        self.parent.endCompute()
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------

+ 279 - 535
uHDR/guiQt/view.py

@@ -14,95 +14,13 @@
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 
-# -----------------------------------------------------------------------------
-# --- VERSION TRACKING --------------------------------------------------------
-# -----------------------------------------------------------------------------
-
-# -----------------------------------------------------------------------------
-# (1): DEBUG
-#      HDRImageViewer doesn't open 
-#      add time.sleep() in HDRviewerController.displayFile(...)
-#      ---- Rémi Cozot, June 2021
-# -----------------------------------------------------------------------------
-# (2): Prevent Freezing
-#      Add multithreading in:
-#      i  - processpipe processing (except for export and display)
-#      ii - loading images
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (3): Prevent Freezing and speed up
-#      Add multithreading in:
-#      i  - processpipe processing for HDR display
-#      ii - processpipe procssing export single image to vesaHDR1000
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (4): Fix ev value and accuracy in color editor 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (5): Fix Y curve plot 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (6): Ability to export SDR (.jpg) image to HDR (tone expansion).
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
 
 
 # -----------------------------------------------------------------------------
 # --- Package hdrGUI ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
-package hdrGUI consists of the classes for GUI:
-(1) controller classes:
-    class GalleryMode(enum.Enum)
-    class ImageWidgetController
-    class ImageGalleryController()
-    class AppController(object)
-    class MultiDockController()
-    class EditImageController
-    class ImageInfoController
-    class AdvanceSliderController()
-    class ToneCurveController()
-    class LightnessMaskController()
-    class HDRviewerController()
-    class LchColorSelectorController()
-    class GeometryController()
-(2) model classes: 
-    class ImageWidgetModel(object)
-    class ImageGalleryModel
-    class AppModel(object)
-    class EditImageModel(object)
-    class AdvanceSliderModel()
-    class ToneCurveModel()
-    class LightnessMaskModel()
-    class ImageInfoModel(object)
-    class HDRviewerModel(object)
-    class ImageQualityModel(object)
-    class LchColorSelectorModel(object)
-    class GeometryModel(object)
-(3) view classes:
-    class ImageWidgetView(QWidget)
-    class FigureWidget(FigureCanvas)
-    class ImageGalleryView(QSplitter)
-    class AppView(QMainWindow)
-    class ImageInfoView(QSplitter)
-    class AdvanceLineEdit(object)
-    class AdvanceCheckBox(object)
-    class EditImageView(QSplitter)
-    class MultiDockView(QDockWidget)
-    class AdvanceSliderView(QFrame)
-    class ToneCurveView(QFrame)
-    class LightnessMaskView(QGroupBox)
-    class HDRviewerView(QFrame)
-    class ImageQualityView(QScrollArea)
-    class LchColorSelectorView(QFrame)
-    class GeometryView(QFrame)
-(4) Qt multi threading classes:
-    class RequestCompute(object)
-    class RunCompute(QRunnable)
-    class RequestLoadImage(object)
-    class RunLoadImage(QRunnable)
-    class pCompute(object)
-    class pRun(QRunnable)
+package hdrGUI consists of the classes for GUI.
 """
 
 # -----------------------------------------------------------------------------
@@ -111,7 +29,7 @@ package hdrGUI consists of the classes for GUI:
 from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QMainWindow, QSplitter, QFrame, QDockWidget, QDesktopWidget
 from PyQt5.QtWidgets import QSplitter, QFrame, QSlider, QCheckBox, QGroupBox
 from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QGridLayout, QLayout, QScrollArea, QFormLayout
-from PyQt5.QtWidgets import QPushButton, QTextEdit,QLineEdit
+from PyQt5.QtWidgets import QPushButton, QTextEdit,QLineEdit, QComboBox, QSpinBox
 from PyQt5.QtWidgets import QAction, QProgressBar, QDialog
 from PyQt5.QtGui import QPixmap, QImage, QDoubleValidator
 from PyQt5.QtCore import Qt
@@ -121,12 +39,15 @@ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
 from matplotlib.figure import Figure
 
 from datetime import datetime
+import time
 
 import numpy as np
 import hdrCore.image, hdrCore.processing
 import math, enum
+import functools
 
-from . import controller
+from . import controller, model
+import hdrCore.metadata
 import preferences.preferences as pref
 
 # ------------------------------------------------------------------------------------------
@@ -190,7 +111,12 @@ class FigureWidget(FigureCanvas):
     def plot(self,X,Y,mode, clear=False):
         if clear: self.axes.clear()
         self.axes.plot(X,Y,mode)
-        self.fig.canvas.draw()
+        try:
+            self.fig.canvas.draw()
+        except Exception:
+            time.sleep(0.5)
+            self.fig.canvas.draw()
+
 # ------------------------------------------------------------------------------------------
 # --- class ImageGalleryView(QSplitter) ----------------------------------------------------
 # ------------------------------------------------------------------------------------------
@@ -386,7 +312,7 @@ class AppView(QMainWindow):
         # ----------------------------------
         self.dock = controller.MultiDockController(self, HDRcontroller)
         self.addDockWidget(Qt.RightDockWidgetArea,self.dock.view)
-        self.resizeDocks([self.dock.view],[self.controller.screenSize[0].width()*scale//4],Qt.Horizontal)
+        self.resizeDocks([self.dock.view],[int(self.controller.screenSize[0].width()*scale//4)],Qt.Horizontal)
 
         # ----------------------------------
         # build menu
@@ -396,6 +322,8 @@ class AppView(QMainWindow):
         self.buildExport()
         self.buildPreferences()
     # ------------------------------------------------------------------------------------------
+    def getImageGalleryController(self): return self.imageGalleryController
+    # ------------------------------------------------------------------------------------------
     def resizeEvent(self, event): super().resizeEvent(event)
     # ------------------------------------------------------------------------------------------
     def setWindowGeometry(self, scale=0.8):
@@ -442,20 +370,22 @@ class AppView(QMainWindow):
         menubar = self.menuBar()# get menubar
         prefMenu = menubar.addMenu('&Preferences')# file menu
 
-        prefPython = QAction('&Set to pure python', self)        
-        prefPython.setStatusTip('[Preferences] set to pure python computation mode (no hardware acceleration).')
-        prefPython.triggered.connect(self.controller.callBackPython)
-        prefMenu.addAction(prefPython)
+        displayList = pref.getHDRdisplays().keys()
 
-        prefNumba = QAction('&Set to mutlicore jit python', self)        
-        prefNumba.setStatusTip('[Preferences] set to multicore jit computation mode (multicore acceleration).')
-        prefNumba.triggered.connect(self.controller.callBackNumba)
-        prefMenu.addAction(prefNumba)
+        # function for callback
+        def cbd(tag): 
+            pref.setHDRdisplay(tag)
+            self.statusBar().showMessage("swithcing HDR Display to: "+tag+"!")
+            self.menuExport.setText('&Export to '+pref.getHDRdisplay()['tag'])
+            self.menuExportAll.setText('&Export All to '+pref.getHDRdisplay()['tag'])        
 
-        prefCuda = QAction('&Set to cuda', self)        
-        prefCuda.setStatusTip('[Preferences] set to cuda computation mode (cuda acceleration).')
-        prefCuda.triggered.connect(self.controller.callBackCuda)
-        prefMenu.addAction(prefCuda)
+        prefDisplays = []
+        for i,d in enumerate(displayList):
+            if d != 'none':
+                prefDisplay = QAction('&Set display to '+d, self) 
+                p_cbd = functools.partial(cbd, d)
+                prefDisplay.triggered.connect(p_cbd)
+                prefMenu.addAction(prefDisplay)
 
     # ------------------------------------------------------------------------------------------
     def buildDisplayHDR(self):
@@ -484,17 +414,17 @@ class AppView(QMainWindow):
         menubar = self.menuBar()# get menubar
         exportHDR = menubar.addMenu('&Export HDR image')# file menu
 
-        menuExport = QAction('&Export vesa Display HDR 1000', self)        
-        menuExport.setShortcut('Ctrl+X')
-        menuExport.setStatusTip('[Export HDR image] save HDR image file for vesa Display HDR 1000')
-        menuExport.triggered.connect(self.controller.callBackExportHDR)
-        exportHDR.addAction(menuExport)
-
-        menuExportAll = QAction('&Export All vesa Display HDR 1000', self)        
-        menuExportAll.setShortcut('Ctrl+Y')
-        menuExportAll.setStatusTip('[Export all HDR images] save HDR image files for vesa Display HDR 1000')
-        menuExportAll.triggered.connect(self.controller.callBackExportAllHDR)
-        exportHDR.addAction(menuExportAll)
+        self.menuExport = QAction('&Export to '+pref.getHDRdisplay()['tag'], self)        
+        self.menuExport.setShortcut('Ctrl+X')
+        self.menuExport.setStatusTip('[Export HDR image] save HDR image file for HDR display')
+        self.menuExport.triggered.connect(self.controller.callBackExportHDR)
+        exportHDR.addAction(self.menuExport)
+
+        self.menuExportAll = QAction('&Export All to '+pref.getHDRdisplay()['tag'], self)        
+        self.menuExportAll.setShortcut('Ctrl+Y')
+        self.menuExportAll.setStatusTip('[Export all HDR images] save HDR image files for HDR display.')
+        self.menuExportAll.triggered.connect(self.controller.callBackExportAllHDR)
+        exportHDR.addAction(self.menuExportAll)
     # ------------------------------------------------------------------------------------------        
     def buildDockMenu(self):
         menubar = self.menuBar()# get menubar
@@ -512,18 +442,16 @@ class AppView(QMainWindow):
         edit.triggered.connect(self.dock.activateEDIT)
         dockMenu.addAction(edit)
 
-        iqa = QAction('&Image Quality Assessment', self)        
+        iqa = QAction('&Image Aesthetics', self)        
         iqa.setShortcut('Ctrl+A')
-        iqa.setStatusTip('[Dock] image quality assessment dock')
-        iqa.triggered.connect(self.dock.activateIQA)
+        iqa.setStatusTip('[Dock] image aesthetics dock')
+        iqa.triggered.connect(self.dock.activateMIAM)
         dockMenu.addAction(iqa)
     # ------------------------------------------------------------------------------------------
     def closeEvent(self, event):
         if pref.verbose: print(" [CB] >> AppView.closeEvent()>> ... closing")
         self.imageGalleryController.save()
         self.controller.hdrDisplay.close()
-    # ------------------------------------------------------------------------------------------
-    def assessmentRunning(self): return self.dock.assessmentRunning()
 # ------------------------------------------------------------------------------------------
 # --- class ImageInfoView(QSplitter) -------------------------------------------------------
 # ------------------------------------------------------------------------------------------
@@ -560,62 +488,23 @@ class ImageInfoView(QSplitter):
         line.setFrameShape(QFrame.HLine)
         self.layout.addRow(line)
         # ---------------------------------------------------
-        #1	Inside	Window with view on bright outdoor
-        self.useCase01 = AdvanceCheckBox(self,"inside", "Window with view on bright outdoor", False, self.layout)
-        #2	Inside	High Contrast and Illuminants tend to be over-exposed
-        self.useCase02 = AdvanceCheckBox(self,"inside", "High Contrast and illuminants tend to be over-exposed", False, self.layout)
-        #3	Inside	Backlit portrait
-        self.useCase03 = AdvanceCheckBox(self,"inside", "Backlit portrait", False, self.layout)
-        # ---------------------------------------------------
-        line = QFrame()
-        line.setFrameShape(QFrame.HLine)
-        self.layout.addRow(line)
-        # ---------------------------------------------------        
-        #4	Outside	Sun in the frame
-        self.useCase04 = AdvanceCheckBox(self,"outside","Sun in the frame", False, self.layout)
-        #5	Outside	Backlight
-        self.useCase05 = AdvanceCheckBox(self,"outside","Backlight", False, self.layout)
-        #6	Outside	Shadow and direct lighting
-        self.useCase06 = AdvanceCheckBox(self,"outside", "Shadow and direct lighting", False, self.layout)
-        #7	Outside	Backlit portrait
-        self.useCase07 = AdvanceCheckBox(self,"outside", "Backlit portrait", False, self.layout)
-        #8	Outside	Nature
-        self.useCase08 = AdvanceCheckBox(self,"outside","Nature", False, self.layout)
-        #9	Outside	Quite LDR
-        self.useCase09 = AdvanceCheckBox(self,"outside","Quite LDR", False, self.layout)
-        # ---------------------------------------------------
-        line = QFrame()
-        line.setFrameShape(QFrame.HLine)
-        self.layout.addRow(line)
-        # ---------------------------------------------------
-        #10	Lowlight	Portrait
-        self.useCase10 = AdvanceCheckBox(self,"lowlight", "Portrait", False, self.layout)
-        #11	Lowlight	Outside with bright illuminants
-        self.useCase11 = AdvanceCheckBox(self,"lowlight", "Outside with bright illuminants", False, self.layout)
-        #12	Lowlight	Cityscape
-        self.useCase12 = AdvanceCheckBox(self,"lowlight", "Cityscape", False, self.layout)
-        #13	Lowlight	Event (Concerts/night clubs/bar/restaurants)
-        self.useCase13 = AdvanceCheckBox(self,"lowlight", "Event", False, self.layout)
-        # ---------------------------------------------------
-        line = QFrame()
-        line.setFrameShape(QFrame.HLine)
-        self.layout.addRow(line)
-        # ---------------------------------------------------
-        #14	Special cases	Shiny object  and Specular highlights
-        self.useCase14 = AdvanceCheckBox(self,"special", "Shiny object  and specular highlights", False, self.layout)
-        #15	Special cases	Memory colors
-        self.useCase15 = AdvanceCheckBox(self,"special", "Memory colors", False, self.layout)
-        #16	Special cases	Scene with color checker / gray chart
-        self.useCase16 = AdvanceCheckBox(self,"special", "Scene with color checker or gray chart", False, self.layout)
-        #17	Special cases	Translucent objects and Stained glass
-        self.useCase17 = AdvanceCheckBox(self,"special", "Translucent objects and stained glass", False, self.layout)
-        #18	Special cases	Traditional tone mapping failing cases
-        self.useCase18 = AdvanceCheckBox(self,"special", "Traditional tone mapping failing cases", False, self.layout)        
-        # ---------------------------------------------------
-        line = QFrame()
-        line.setFrameShape(QFrame.HLine)
-        self.layout.addRow(line)
-        # ---------------------------------------------------
+        #  user defined tags
+        # --------------------------------------------------
+        userDefinedTags = hdrCore.metadata.tags()
+        tagRootName = userDefinedTags.getTagsRootName()
+        listOfTags = userDefinedTags.tags[tagRootName]
+        self.userDefinedTags = []
+        for tagGroup in listOfTags:
+            groupKey = list(tagGroup.keys())[0]
+            tagLeafs = tagGroup[groupKey]
+            for tag in tagLeafs.items():
+                self.userDefinedTags.append( AdvanceCheckBox(self,groupKey, tag[0], False, self.layout))
+            line = QFrame()
+            line.setFrameShape(QFrame.HLine)
+            self.layout.addRow(line)
+        # --------------------------------------------------
+
+
         self.layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
 
         self.scroll = QScrollArea()
@@ -664,88 +553,27 @@ class ImageInfoView(QSplitter):
         # ---------------------------------------------------
         self.controller.callBackActive = False
         # ---------------------------------------------------
-        #1	Inside	Window with view on bright outdoor
-        on_off = image_.metadata.metadata['hdr-use-case'][0]["inside"]["Window with view on bright outdoor"] 
-        on_off = on_off if on_off else False
-        self.useCase01.setState(on_off)
-        #2	Inside	High Contrast and Illuminants tend to be over-exposed
-        on_off = image_.metadata.metadata['hdr-use-case'][0]["inside"]["High Contrast and illuminants tend to be over-exposed"] 
-        on_off = on_off if on_off else False
-        self.useCase02.setState(on_off)
-        #3	Inside	Backlit portrait
-        on_off = image_.metadata.metadata['hdr-use-case'][0]["inside"]["Backlit portrait"] 
-        on_off = on_off if on_off else False
-        self.useCase03.setState(on_off)
-        # ---------------------------------------------------      
-        #4	Outside	Sun in the frame
-        on_off = image_.metadata.metadata['hdr-use-case'][1]["outside"]["Sun in the frame"] 
-        on_off = on_off if on_off else False
-        self.useCase04.setState(on_off)
-        #5	Outside	Backlight
-        on_off = image_.metadata.metadata['hdr-use-case'][1]["outside"]["Backlight"] 
-        on_off = on_off if on_off else False
-        self.useCase05.setState(on_off)
-        #6	Outside	Shadow and direct lighting
-        on_off = image_.metadata.metadata['hdr-use-case'][1]["outside"]["Shadow and direct lighting"] 
-        on_off = on_off if on_off else False
-        self.useCase06.setState(on_off)
-        #7	Outside	Backlit portrait
-        on_off = image_.metadata.metadata['hdr-use-case'][1]["outside"]["Backlit portrait"] 
-        on_off = on_off if on_off else False
-        self.useCase07.setState(on_off)
-        #8	Outside	Nature
-        on_off = image_.metadata.metadata['hdr-use-case'][1]["outside"]["Nature"] 
-        on_off = on_off if on_off else False
-        self.useCase08.setState(on_off)
-        #9	Outside	Quite LDR
-        on_off = image_.metadata.metadata['hdr-use-case'][1]["outside"]["Quite LDR"] 
-        on_off = on_off if on_off else False
-        self.useCase09.setState(on_off)
-        # ---------------------------------------------------
-        #10	Lowlight	Portrait
-        on_off = image_.metadata.metadata['hdr-use-case'][2]["lowlight"]["Portrait"] 
-        on_off = on_off if on_off else False
-        self.useCase10.setState(on_off)
-        #11	Lowlight	Outside with bright illuminants
-        on_off = image_.metadata.metadata['hdr-use-case'][2]["lowlight"]["Outside with bright illuminants"] 
-        on_off = on_off if on_off else False
-        self.useCase11.setState(on_off)
-        #12	Lowlight	Cityscape
-        on_off = image_.metadata.metadata['hdr-use-case'][2]["lowlight"]["Cityscape"] 
-        on_off = on_off if on_off else False
-        self.useCase12.setState(on_off)
-        #13	Lowlight	Event (Concerts/night clubs/bar/restaurants)
-        on_off = image_.metadata.metadata['hdr-use-case'][2]["lowlight"]["Event"] 
-        on_off = on_off if on_off else False
-        self.useCase13.setState(on_off)
-        # ---------------------------------------------------
-        #14	Special cases	Shiny object and Specular highlights
-        on_off = image_.metadata.metadata['hdr-use-case'][3]["special"]["Shiny object  and specular highlights"] 
-        on_off = on_off if on_off else False
-        self.useCase14.setState(on_off)
-        #15	Special cases	Memory colors
-        on_off = image_.metadata.metadata['hdr-use-case'][3]["special"]["Memory colors"] 
-        on_off = on_off if on_off else False
-        self.useCase15.setState(on_off)
-        #16	Special cases	Scene with color checker / gray chart
-        on_off = image_.metadata.metadata['hdr-use-case'][3]["special"]["Scene with color checker or gray chart"] 
-        on_off = on_off if on_off else False
-        self.useCase16.setState(on_off)
-        #17	Special cases	Translucent objects and Stained glass
-        on_off = image_.metadata.metadata['hdr-use-case'][3]["special"]["Translucent objects and stained glass"] 
-        on_off = on_off if on_off else False
-        self.useCase17.setState(on_off)
-        #18	Special cases	Traditional tone mapping failing cases
-        on_off = image_.metadata.metadata['hdr-use-case'][3]["special"]["Traditional tone mapping failing cases"] 
-        on_off = on_off if on_off else False
-        self.useCase18.setState(on_off)        
+        tagRootName = image_.metadata.otherTags.getTagsRootName()
+        listOfTags = image_.metadata.metadata[tagRootName]
+
+        for i,tagGroup in enumerate(listOfTags):
+            groupKey = list(tagGroup.keys())[0]
+            tagLeafs = tagGroup[groupKey]
+            for tag in tagLeafs.items():
+                # find advanced checkbox
+                for acb in self.userDefinedTags:
+                    if (acb.rightText ==tag[0] ) and (acb.leftText== groupKey):
+                        on_off = image_.metadata.metadata[tagRootName][i][groupKey][tag[0]]
+                        on_off = on_off if on_off else False
+                        acb.setState(on_off)
+                        break  
         # ---------------------------------------------------        
         self.controller.callBackActive = True
         # ---------------------------------------------------
         return self.imageWidgetController.setImage(image_)
 
-    def useCaseChange(self,useCaseClass,useCase, on_off): 
-        if self.controller.callBackActive: self.controller.useCaseChange(useCaseClass,useCase, on_off)
+    def metadataChange(self,metaGroup,metaTag, on_off): 
+        if self.controller.callBackActive: self.controller.metadataChange(metaGroup,metaTag, on_off)
 # ------------------------------------------------------------------------------------------
 # --- class AdvanceLineEdit(object) --------------------------------------------------------
 # ------------------------------------------------------------------------------------------
@@ -774,7 +602,7 @@ class AdvanceCheckBox(object):
 
     def setState(self, on_off): self.checkbox.setChecked(on_off)
 
-    def toggled(self): self.parent.useCaseChange(self.leftText, self.rightText, self.checkbox.isChecked())
+    def toggled(self): self.parent.metadataChange(self.leftText, self.rightText, self.checkbox.isChecked())
 # ------------------------------------------------------------------------------------------
 # --- class EditImageView(QSplitter) -------------------------------------------------------
 # ------------------------------------------------------------------------------------------
@@ -827,7 +655,31 @@ class EditImageView(QSplitter):
         self.colorEditor1 = controller.LchColorSelectorController(self, idName = "colorEditor1")
         self.layout.addWidget(self.colorEditor1.view)
 
+        # color2 ----------------------
+        self.colorEditor2 = controller.LchColorSelectorController(self, idName = "colorEditor2")
+        self.layout.addWidget(self.colorEditor2.view)
+
+        # color3 ----------------------
+        self.colorEditor3 = controller.LchColorSelectorController(self, idName = "colorEditor3")
+        self.layout.addWidget(self.colorEditor3.view)
+
         # color1 ----------------------
+        self.colorEditor4 = controller.LchColorSelectorController(self, idName = "colorEditor4")
+        self.layout.addWidget(self.colorEditor4.view)
+
+        # auto color selection ----------------------
+        # -----
+        self.colorEditorsAuto = controller.ColorEditorsAutoController(self,
+                                                                      [self.colorEditor0,
+                                                                       self.colorEditor1,
+                                                                       self.colorEditor2,
+                                                                       self.colorEditor3,
+                                                                       self.colorEditor4],
+                                                                       "saturation")
+        self.layout.addWidget(self.colorEditorsAuto.view)
+        # -----
+
+        # geometry ----------------------
         self.geometry = controller.GeometryController(self)
         self.layout.addWidget(self.geometry.view)
 
@@ -937,6 +789,24 @@ class EditImageView(QSplitter):
         values = processPipe.getParameters(id)
         self.colorEditor1.setValues(values, callBackActive = False)
 
+        # colorEditor2
+        # recover data in pipe and restore it
+        id = processPipe.getProcessNodeByName("colorEditor2")
+        values = processPipe.getParameters(id)
+        self.colorEditor2.setValues(values, callBackActive = False)
+
+        # colorEditor3
+        # recover data in pipe and restore it
+        id = processPipe.getProcessNodeByName("colorEditor3")
+        values = processPipe.getParameters(id)
+        self.colorEditor3.setValues(values, callBackActive = False)
+        
+        # colorEditor4
+        # recover data in pipe and restore it
+        id = processPipe.getProcessNodeByName("colorEditor4")
+        values = processPipe.getParameters(id)
+        self.colorEditor4.setValues(values, callBackActive = False)
+        
         # geometry
         # recover data in pipe and restore it
         id = processPipe.getProcessNodeByName("geometry")
@@ -957,18 +827,19 @@ class MultiDockView(QDockWidget):
         self.childControllers = [
             controller.EditImageController(self, HDRcontroller), 
             controller.ImageInfoController(self), 
-            controller.ImageQualityController(self, HDRcontroller)]
+            controller.ImageAestheticsController(self)]
+            #controller.ImageQualityController(self, HDRcontroller)]
         self.childController = self.childControllers[0]
         self.active = 0
         self.setWidget(self.childController.view)
         self.repaint()
-
+    # ------------------------------------------------------------------------------------------
     def switch(self,nb):
         """
             change active dock
             nb = 0 > editing imag dock
             nb = 1 > image info and metadata dock
-            nb = 2 > image quality assessment 
+            nb = 2 > image aesthetics model 
         """
         if pref.verbose:  print(" [VIEW] >> MultiDockView.switch(",nb,")")
 
@@ -982,11 +853,13 @@ class MultiDockView(QDockWidget):
             self.childController.buildView(processPipe)
             self.setWidget(self.childController.view)
             self.repaint()
-
+    # ------------------------------------------------------------------------------------------
     def setProcessPipe(self, processPipe):
         if pref.verbose:  print(" [VIEW] >> MultiDockView.setProcessPipe(",processPipe.getImage().name,")")
         return self.childController.setProcessPipe(processPipe)
 # ------------------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------------------
 # --- class AdvanceSliderView(QFrame) ------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class AdvanceSliderView(QFrame):
@@ -1014,8 +887,8 @@ class AdvanceSliderView(QFrame):
         self.hbox.addWidget(self.reset)
 
         self.slider = QSlider(Qt.Horizontal)
-        self.slider.setRange(range[0]/step,range[1]/step)
-        self.slider.setValue(defaultValue/step)
+        self.slider.setRange(int(range[0]/step),int(range[1]/step))
+        self.slider.setValue(int(defaultValue/step))
         self.slider.setSingleStep(1) 
 
         self.vbox.addWidget(self.firstrow)
@@ -1047,6 +920,12 @@ class ToneCurveView(QFrame):
         self.curve.plot([0.0,100],[0.0,100.0],'r--')
 
         #containers
+        
+        # zj add for semi-auto curve begin
+        self.containerAuto = QFrame() 
+        self.hboxAuto = QHBoxLayout() 
+        self.containerAuto.setLayout(self.hboxAuto)
+        # zj add for semi-auto curve end                                
         self.containerShadows = QFrame()
         self.hboxShadows = QHBoxLayout()
         self.containerShadows.setLayout(self.hboxShadows)
@@ -1068,17 +947,24 @@ class ToneCurveView(QFrame):
         self.containerHighlights.setLayout(self.hboxHighlights)
 
         self.vbox.addWidget(self.curve)
+        self.vbox.addWidget(self.containerAuto) #  zj add for semi-auto curve                                                                       
         self.vbox.addWidget(self.containerHighlights)
         self.vbox.addWidget(self.containerWhites)
         self.vbox.addWidget(self.containerMediums)
         self.vbox.addWidget(self.containerBlacks)
         self.vbox.addWidget(self.containerShadows)
 
+        # zj add for semi-auto curve begin
+        # autoCurve button
+        self.autoCurve = QPushButton("auto")
+        self.hboxAuto.addWidget(self.autoCurve)
+        self.hboxAuto.setAlignment(Qt.AlignCenter)
+        # zj add for semi-auto curve end
         # shadows
         self.labelShadows = QLabel("shadows")
         self.sliderShadows = QSlider(Qt.Horizontal)
         self.sliderShadows.setRange(0,100)
-        self.sliderShadows.setValue(self.controller.model.default['shadows'][1])
+        self.sliderShadows.setValue(int(self.controller.model.default['shadows'][1]))
         self.editShadows = QLineEdit()
         self.editShadows.setText(str(self.controller.model.default['shadows'][1]))
         self.resetShadows = QPushButton("reset")
@@ -1091,7 +977,7 @@ class ToneCurveView(QFrame):
         self.labelBlacks = QLabel("  blacks  ")
         self.sliderBlacks = QSlider(Qt.Horizontal)
         self.sliderBlacks.setRange(0,100)
-        self.sliderBlacks.setValue(self.controller.model.default['blacks'][1])
+        self.sliderBlacks.setValue(int(self.controller.model.default['blacks'][1]))
         self.editBlacks = QLineEdit()
         self.editBlacks.setText(str(self.controller.model.default['blacks'][1]))
         self.resetBlacks = QPushButton("reset")
@@ -1104,7 +990,7 @@ class ToneCurveView(QFrame):
         self.labelMediums = QLabel("mediums")
         self.sliderMediums = QSlider(Qt.Horizontal)
         self.sliderMediums.setRange(0,100)
-        self.sliderMediums.setValue(self.controller.model.default['mediums'][1])
+        self.sliderMediums.setValue(int(self.controller.model.default['mediums'][1]))
         self.editMediums = QLineEdit()
         self.editMediums.setText(str(self.controller.model.default['mediums'][1]))
         self.resetMediums = QPushButton("reset")
@@ -1117,7 +1003,7 @@ class ToneCurveView(QFrame):
         self.labelWhites = QLabel("  whites  ")
         self.sliderWhites = QSlider(Qt.Horizontal)
         self.sliderWhites.setRange(0,100)
-        self.sliderWhites.setValue(self.controller.model.default['whites'][1])
+        self.sliderWhites.setValue(int(self.controller.model.default['whites'][1]))
         self.editWhites = QLineEdit()
         self.editWhites.setText(str(self.controller.model.default['whites'][1]))
         self.resetWhites = QPushButton("reset")
@@ -1130,7 +1016,7 @@ class ToneCurveView(QFrame):
         self.labelHighlights = QLabel("highlights")
         self.sliderHighlights = QSlider(Qt.Horizontal)
         self.sliderHighlights.setRange(0,100)
-        self.sliderHighlights.setValue(self.controller.model.default['highlights'][1])
+        self.sliderHighlights.setValue(int(self.controller.model.default['highlights'][1]))
         self.editHighlights = QLineEdit()
         self.editHighlights.setText(str(self.controller.model.default['highlights'][1]))
         self.resetHighlights = QPushButton("reset")
@@ -1153,6 +1039,8 @@ class ToneCurveView(QFrame):
         self.resetMediums.clicked.connect(self.resetMediumsCB)
         self.resetWhites.clicked.connect(self.resetWhitesCB)
         self.resetHighlights.clicked.connect(self.resetHighlightsCB)
+        self.autoCurve.clicked.connect(self.controller.autoCurve)  #  zj add for semi-auto curve 
+                                                                                         
 
     def sliderShadowsChange(self):
         if self.controller.callBackActive:
@@ -1299,244 +1187,6 @@ class HDRviewerView(QFrame):
 
     def auto(self): self.controller.callBackAuto(self.autoCheckBox.isChecked())
 # ------------------------------------------------------------------------------------------
-# --- class ImageQualityView(QScrollArea) --------------------------------------------------
-# ------------------------------------------------------------------------------------------
-class ImageQualityView(QScrollArea): #(QSplitter):
-    def __init__(self,_controller):
-        if pref.verbose: print(" [VIEW] >> ImageQualityView.__init__(",")")
-
-        super().__init__()
-        #super().__init__(Qt.Vertical)
-        self.controller = _controller
-
-        #self.imageWidgetController = controller.ImageWidgetController()
-
-        self.layout = QVBoxLayout()
-        # ------------------------------------
-        # pseudo
-        self.pseudo = QFrame()
-        self.pseudoLayout = QHBoxLayout()
-        self.pseudo.setLayout(self.pseudoLayout)
-        self.pseudoLabel = QLabel("name/pseudo:")
-        self.pseudoValue = QLineEdit("no name")
-        self.pseudoLayout.addWidget(self.pseudoLabel)
-        self.pseudoLayout.addWidget(self.pseudoValue)
-        self.layout.addWidget(self.pseudo)
-        # ------------------------------------        
-        # Start/stop button
-        self.startStopButton = QPushButton("start assessment")
-        self.layout.addWidget(self.startStopButton)
-        # ------------------------------------
-        line = QFrame()
-        line.setFrameShape(QFrame.HLine)
-        self.layout.addWidget(line)
-        # ------------------------------------
-        # Quality assessment (acr)
-        self.iqaContainer = QFrame()
-        self.iqaLayout = QHBoxLayout()
-        self.iqaContainer.setLayout(self.iqaLayout)
-        self.iqaLabel = QLabel("image quality:")
-        self.iqaValue = QLabel(self.controller.model.acrStr[0])
-        self.iqaLayout.addWidget(self.iqaLabel)
-        self.iqaLayout.addWidget(self.iqaValue)
-        self.iqaSlider = QSlider(Qt.Horizontal)
-        self.iqaSlider.setRange(0,5)
-        self.iqaSlider.setSingleStep(1)
-        self.iqaSlider.setTickInterval(6)
-        # adding to layout
-        self.layout.addWidget(self.iqaContainer)
-        self.layout.addWidget(self.iqaSlider)
-        # ------------------------------------
-        line = QFrame()
-        line.setFrameShape(QFrame.HLine)
-        self.layout.addWidget(line)
-        # ------------------------------------
-        # aesthetics assessment (acr)
-        self.aesContainer = QFrame()
-        self.aesLayout = QHBoxLayout()
-        self.aesContainer.setLayout(self.aesLayout)
-        self.aesLabel = QLabel("image aesthetics:")
-        self.aesValue = QLabel(self.controller.model.acrStr[0])
-        self.aesLayout.addWidget(self.aesLabel)
-        self.aesLayout.addWidget(self.aesValue)
-        self.aesSlider = QSlider(Qt.Horizontal)
-        self.aesSlider.setRange(0,5)
-        self.aesSlider.setSingleStep(1)
-        self.aesSlider.setTickInterval(6)
-        # adding to layout
-        self.layout.addWidget(self.aesContainer)
-        self.layout.addWidget(self.aesSlider)
-        # ------------------------------------
-        line = QFrame()
-        line.setFrameShape(QFrame.HLine)
-        self.layout.addWidget(line)
-        # ------------------------------------
-        # ------------------------------------
-        # naturalness assessment (acr)
-        self.natContainer = QFrame()
-        self.natLayout = QHBoxLayout()
-        self.natContainer.setLayout(self.natLayout)
-        self.natLabel = QLabel("image naturalness:")
-        self.natValue = QLabel(self.controller.model.acrStr[0])
-        self.natLayout.addWidget(self.natLabel)
-        self.natLayout.addWidget(self.natValue)
-        self.natSlider = QSlider(Qt.Horizontal)
-        self.natSlider.setRange(0,5)
-        self.natSlider.setSingleStep(1)
-        self.natSlider.setTickInterval(6)
-        # adding to layout
-        self.layout.addWidget(self.natContainer)
-        self.layout.addWidget(self.natSlider)
-        # ------------------------------------
-        line = QFrame()
-        line.setFrameShape(QFrame.HLine)
-        self.layout.addWidget(line)
-        # ------------------------------------
-        # ------------------------------------
-        # confort assessment (acr)
-        self.conContainer = QFrame()
-        self.conLayout = QHBoxLayout()
-        self.conContainer.setLayout(self.conLayout)
-        self.conLabel = QLabel("image confort:")
-        self.conValue = QLabel(self.controller.model.acrStr[0])
-        self.conLayout.addWidget(self.conLabel)
-        self.conLayout.addWidget(self.conValue)
-        self.conSlider = QSlider(Qt.Horizontal)
-        self.conSlider.setRange(0,5)
-        self.conSlider.setSingleStep(1)
-        self.conSlider.setTickInterval(6)
-        # adding to layout
-        self.layout.addWidget(self.conContainer)
-        self.layout.addWidget(self.conSlider)
-        # ------------------------------------
-        line = QFrame()
-        line.setFrameShape(QFrame.HLine)
-        self.layout.addWidget(line)
-        # ------------------------------------
-        # ------------------------------------
-        # Artifacts, Blur, Ghost, Halo, Other
-        self.artifactLabel = QLabel("artifacts:")
-        self.blurCheckbox =QCheckBox("blur")
-        self.ghostCheckbox =QCheckBox("ghost")
-        self.noiseCheckbox =QCheckBox("noise")
-        self.haloCheckbox =QCheckBox("halo")
-        self.otherCheckbox =QCheckBox("other artifacts")
-        # ------------------------------------
-        self.layout.addWidget(self.artifactLabel)
-        self.layout.addWidget(self.blurCheckbox)
-        self.layout.addWidget(self.ghostCheckbox)
-        self.layout.addWidget(self.noiseCheckbox)
-        self.layout.addWidget(self.haloCheckbox)
-        self.layout.addWidget(self.otherCheckbox)
-        # ------------------------------------
-        line = QFrame()
-        line.setFrameShape(QFrame.HLine)
-        self.layout.addWidget(line)
-        # ------------------------------------
-        self.navigation = QFrame()
-        self.navigationLayout = QHBoxLayout()
-        self.navigation.setLayout(self.navigationLayout)
-        self.previousButton = QPushButton("< previous")
-        self.nextButton = QPushButton("next >")
-        self.navigationLayout.addWidget(self.previousButton)
-        self.navigationLayout.addWidget(self.nextButton)
-        self.layout.addWidget(self.navigation)
-        # ------------------------------------
-        line = QFrame()
-        line.setFrameShape(QFrame.HLine)
-        self.layout.addWidget(line)
-        # ------------------------------------
-        # scroll ------------------------------------------------
-        self.layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
-
-        ##self.scroll = QScrollArea()
-        ##self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
-        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
-        self.container = QLabel()
-        self.container.setLayout(self.layout)
-        ##self.scroll.setWidget(self.container)
-        ##self.scroll.setWidgetResizable(True)        
-        self.setWidget(self.container)
-        self.setWidgetResizable(True)
-
-        # adding widgets to self (QSplitter)
-        #self.addWidget(self.imageWidgetController.view)
-        #self.addWidget(self.scroll)
-        #self.setSizes([60,40])
-
-        # callbacks
-        # -----------------------------------------------
-        self.startStopButton.clicked.connect(self.startStopButtonCallBack)
-        self.iqaSlider.valueChanged.connect(self.iqaSliderCallback)
-        self.aesSlider.valueChanged.connect(self.aesSliderCallback)
-        self.natSlider.valueChanged.connect(self.natSliderCallback)
-        self.conSlider.valueChanged.connect(self.conSliderCallback)
-        self.previousButton.clicked.connect(self.previousButtonCallback)
-        self.nextButton.clicked.connect(self.nextButtonCallback)
-        self.pseudoValue.editingFinished.connect(self.pseudoCallback)
-        self.ghostCheckbox.stateChanged.connect(self.ghostCallBack)
-        self.noiseCheckbox.stateChanged.connect(self.noiseCallBack)
-        self.blurCheckbox.stateChanged.connect(self.blurCallBack)
-        self.haloCheckbox.stateChanged.connect(self.haloCallBack)
-        self.otherCheckbox.stateChanged.connect(self.otherCallBack)
-
-    # callbacks methods
-    def iqaSliderCallback(self):
-        val = self.iqaSlider.value()
-        self.iqaValue.setText(self.controller.model.acrStr[val])
-        self.controller.score('quality', val)
-
-    def aesSliderCallback(self):
-        val = self.aesSlider.value()
-        self.aesValue.setText(self.controller.model.acrStr[val])
-        self.controller.score('aesthetics', val)
-
-    def natSliderCallback(self):
-        val = self.natSlider.value()
-        self.natValue.setText(self.controller.model.acrStr[val])
-        self.controller.score('confort', val)
-
-    def conSliderCallback(self):
-        val = self.conSlider.value()
-        self.conValue.setText(self.controller.model.acrStr[val])
-        self.controller.score('naturalness', val)
-
-    def startStopButtonCallBack(self):
-        currentValue = self.startStopButton.text()
-        if currentValue=="start assessment": 
-            self.startStopButton.setText("stop assessment")
-        elif currentValue=="stop assessment": 
-            self.startStopButton.setText("start assessment")
-        self.controller.startStop()
-        if not self.controller.assessmentRunning(): 
-            self.startStopButton.setText("start assessment")
-
-    def nextButtonCallback(self):       self.controller.next()
-
-    def previousButtonCallback(self):   self.controller.previous()
-
-    def pseudoCallback(self):           self.controller.pseudo(self.pseudoValue.text())
-
-    def ghostCallBack(self): self.controller.artifact('ghost', self.ghostCheckbox.isChecked())
-    def noiseCallBack(self): self.controller.artifact('noise', self.noiseCheckbox.isChecked())
-    def blurCallBack(self): self.controller.artifact('blur', self.blurCheckbox.isChecked())
-    def haloCallBack(self): self.controller.artifact('halo', self.haloCheckbox.isChecked())
-    def otherCallBack(self): self.controller.artifact('other', self.otherCheckbox.isChecked())
-
-
-    def setValues(self, quality):
-
-        self.pseudoValue.setText(quality.user['pseudo'])
-        self.iqaSlider.setValue(quality.score['quality'])
-        self.aesSlider.setValue(quality.score['aesthetics'])
-        self.natSlider.setValue(quality.score['confort'])
-        self.conSlider.setValue(quality.score['naturalness'])
-        self.blurCheckbox.setChecked(quality.artifact['ghost']) 
-        self.ghostCheckbox.setChecked(quality.artifact['noise']) 
-        self.noiseCheckbox.setChecked(quality.artifact['blur'])
-        self.haloCheckbox.setChecked(quality.artifact['halo'])
-        self.otherCheckbox.setChecked(quality.artifact['other'])
-# ------------------------------------------------------------------------------------------
 # --- class LchColorSelectorView(QFrame) ---------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class LchColorSelectorView(QFrame):
@@ -1561,14 +1211,14 @@ class LchColorSelectorView(QFrame):
         self.imageHueRangeController.setImage(hueBarRGB)
         # slider min
         self.sliderHueMin = QSlider(Qt.Horizontal)
-        self.sliderHueMin.setRange(0.0,360.0)
-        self.sliderHueMin.setValue(0.0)
-        self.sliderHueMin.setSingleStep(1.0)
+        self.sliderHueMin.setRange(0,360)
+        self.sliderHueMin.setValue(0)
+        self.sliderHueMin.setSingleStep(1)
         # slider max
         self.sliderHueMax = QSlider(Qt.Horizontal)
-        self.sliderHueMax.setRange(0.0,360.0)
-        self.sliderHueMax.setValue(360.0)
-        self.sliderHueMax.setSingleStep(1.0)
+        self.sliderHueMax.setRange(0,360)
+        self.sliderHueMax.setValue(360)
+        self.sliderHueMax.setSingleStep(1)
 
         # procedural image: Saturation bar
         saturationBarLch = hdrCore.image.Image.buildLchColorData((75,75), (0,100), (180,180), (20,720), width='c', height='L')
@@ -1578,14 +1228,14 @@ class LchColorSelectorView(QFrame):
         self.imageSaturationController.setImage(saturationBarRGB)
         # slider min
         self.sliderChromaMin = QSlider(Qt.Horizontal)
-        self.sliderChromaMin.setRange(0.0,100.0)
-        self.sliderChromaMin.setValue(0.0)
-        self.sliderChromaMin.setSingleStep(1.0)
+        self.sliderChromaMin.setRange(0,100)
+        self.sliderChromaMin.setValue(0)
+        self.sliderChromaMin.setSingleStep(1)
         # slider max
         self.sliderChromaMax = QSlider(Qt.Horizontal)
-        self.sliderChromaMax.setRange(0.0,100.0)
-        self.sliderChromaMax.setValue(100.0)
-        self.sliderChromaMax.setSingleStep(1.0)
+        self.sliderChromaMax.setRange(0,100)
+        self.sliderChromaMax.setValue(100)
+        self.sliderChromaMax.setSingleStep(1)
 
         # procedural image:lightness bar
         lightnessBarLch = hdrCore.image.Image.buildLchColorData((0,100), (0,0), (180,180), (20,720), width='L', height='c')
@@ -1595,14 +1245,14 @@ class LchColorSelectorView(QFrame):
         self.imageLightnessController.setImage(lightnessBarRGB)
         # slider min
         self.sliderLightMin = QSlider(Qt.Horizontal)
-        self.sliderLightMin.setRange(0.0,300.0)
-        self.sliderLightMin.setValue(0.0)
-        self.sliderLightMin.setSingleStep(1.0)        
+        self.sliderLightMin.setRange(0,300)
+        self.sliderLightMin.setValue(0)
+        self.sliderLightMin.setSingleStep(1)        
         # slider max
         self.sliderLightMax = QSlider(Qt.Horizontal)
-        self.sliderLightMax.setRange(0.0,300.0)
-        self.sliderLightMax.setValue(300.0)
-        self.sliderLightMax.setSingleStep(1.0)
+        self.sliderLightMax.setRange(0,300)
+        self.sliderLightMax.setValue(300)
+        self.sliderLightMax.setSingleStep(1)
 
         # editor
         self.labelEditor =      QLabel("color editor: hue shift, exposure, contrast, saturation")
@@ -1612,9 +1262,9 @@ class LchColorSelectorView(QFrame):
         self.layoutHueShift = QHBoxLayout()
         self.frameHueShift.setLayout(self.layoutHueShift)
         self.sliderHueShift =   QSlider(Qt.Horizontal)
-        self.sliderHueShift.setRange(-180.0,+180.0)
-        self.sliderHueShift.setValue(0.0)
-        self.sliderHueShift.setSingleStep(1.0) 
+        self.sliderHueShift.setRange(-180,+180)
+        self.sliderHueShift.setValue(0)
+        self.sliderHueShift.setSingleStep(1) 
         self.valueHueShift = QLineEdit()
         self.valueHueShift.setText(str(0.0))  
         self.layoutHueShift.addWidget(QLabel("hue shift"))
@@ -1626,9 +1276,9 @@ class LchColorSelectorView(QFrame):
         self.layoutExposure = QHBoxLayout()
         self.frameExposure.setLayout(self.layoutExposure)
         self.sliderExposure =   QSlider(Qt.Horizontal)
-        self.sliderExposure.setRange(-90.0,+90.0)
-        self.sliderExposure.setValue(0.0)
-        self.sliderExposure.setSingleStep(1.0) 
+        self.sliderExposure.setRange(-90,+90)
+        self.sliderExposure.setValue(0)
+        self.sliderExposure.setSingleStep(1) 
         self.valueExposure = QLineEdit()
         self.valueExposure.setText(str(0.0))  
         self.layoutExposure.addWidget(QLabel("exposure"))
@@ -1640,9 +1290,9 @@ class LchColorSelectorView(QFrame):
         self.layoutContrast = QHBoxLayout()
         self.frameContrast.setLayout(self.layoutContrast)
         self.sliderContrast =   QSlider(Qt.Horizontal)
-        self.sliderContrast.setRange(-100.0,+100.0)
-        self.sliderContrast.setValue(0.0)
-        self.sliderContrast.setSingleStep(1.0) 
+        self.sliderContrast.setRange(-100,+100)
+        self.sliderContrast.setValue(0)
+        self.sliderContrast.setSingleStep(1) 
         self.valueContrast = QLineEdit()
         self.valueContrast.setText(str(0.0))  
         self.layoutContrast.addWidget(QLabel("contrast"))
@@ -1654,15 +1304,20 @@ class LchColorSelectorView(QFrame):
         self.layoutSaturation = QHBoxLayout()
         self.frameSaturation.setLayout(self.layoutSaturation)
         self.sliderSaturation =   QSlider(Qt.Horizontal)
-        self.sliderSaturation.setRange(-100.0,+100.0)
-        self.sliderSaturation.setValue(0.0)
-        self.sliderSaturation.setSingleStep(1.0) 
+        self.sliderSaturation.setRange(-100,+100)
+        self.sliderSaturation.setValue(0)
+        self.sliderSaturation.setSingleStep(1) 
         self.valueSaturation = QLineEdit()
         self.valueSaturation.setText(str(0.0))  
         self.layoutSaturation.addWidget(QLabel("saturation"))
         self.layoutSaturation.addWidget(self.sliderSaturation)
         self.layoutSaturation.addWidget(self.valueSaturation)
 
+        # -----
+        self.resetSelection  = QPushButton("reset selection")
+        self.resetEdit  = QPushButton("reset edit")
+        # -----
+
         # mask
         self.checkboxMask = QCheckBox("show selection")
         self.checkboxMask.setChecked(False)
@@ -1683,6 +1338,11 @@ class LchColorSelectorView(QFrame):
         self.vbox.addWidget(self.sliderLightMin)
         self.vbox.addWidget(self.sliderLightMax)
 
+        # -----
+        self.vbox.addWidget(self.resetSelection)
+        # -----
+
+
         self.vbox.addWidget(self.labelEditor)
         self.vbox.addWidget(self.frameHueShift)
         self.vbox.addWidget(self.frameSaturation)
@@ -1690,7 +1350,9 @@ class LchColorSelectorView(QFrame):
         self.vbox.addWidget(self.frameContrast)
 
         self.vbox.addWidget(self.checkboxMask)
-
+        # -----
+        self.vbox.addWidget(self.resetEdit)
+        # -----
         self.setLayout(self.vbox)
 
         # callbacks  
@@ -1705,6 +1367,10 @@ class LchColorSelectorView(QFrame):
         self.sliderContrast.valueChanged.connect(self.sliderContrastChange)
         self.sliderHueShift.valueChanged.connect(self.sliderHueShiftChange)
         self.checkboxMask.toggled.connect(self.checkboxMaskChange)
+        # -----
+        self.resetSelection.clicked.connect(self.controller.resetSelection)
+        self.resetEdit.clicked.connect(self.controller.resetEdit)
+
 
     # callbacks
     def sliderHueChange(self):
@@ -1773,9 +1439,9 @@ class GeometryView(QFrame):
         self.layoutCroppingVerticalAdjustement = QHBoxLayout()
         self.frameCroppingVerticalAdjustement.setLayout(self.layoutCroppingVerticalAdjustement)
         self.sliderCroppingVerticalAdjustement =   QSlider(Qt.Horizontal)
-        self.sliderCroppingVerticalAdjustement.setRange(-100.0,+100.0)
-        self.sliderCroppingVerticalAdjustement.setValue(0.0)
-        self.sliderCroppingVerticalAdjustement.setSingleStep(1.0) 
+        self.sliderCroppingVerticalAdjustement.setRange(-100,+100)
+        self.sliderCroppingVerticalAdjustement.setValue(0)
+        self.sliderCroppingVerticalAdjustement.setSingleStep(1) 
         self.valueCroppingVerticalAdjustement = QLineEdit()
         self.valueCroppingVerticalAdjustement.setText(str(0.0))  
         self.layoutCroppingVerticalAdjustement.addWidget(QLabel("cropping adj."))
@@ -1787,9 +1453,9 @@ class GeometryView(QFrame):
         self.layoutRotation = QHBoxLayout()
         self.frameRotation.setLayout(self.layoutRotation)
         self.sliderRotation =   QSlider(Qt.Horizontal)
-        self.sliderRotation.setRange(-60.0,+60.0)
-        self.sliderRotation.setValue(0.0)
-        self.sliderRotation.setSingleStep(1.0) 
+        self.sliderRotation.setRange(-60,+60)
+        self.sliderRotation.setValue(0)
+        self.sliderRotation.setSingleStep(1) 
         self.valueRotation = QLineEdit()
         self.valueRotation.setText(str(0.0))  
         self.layoutRotation.addWidget(QLabel("rotation"))
@@ -1817,4 +1483,82 @@ class GeometryView(QFrame):
         # call controller
         self.controller.sliderRotationChange(v)
 # ------------------------------------------------------------------------------------------
+# --- class AestheticsImageView(QFrame) ----------------------------------------------------
+# ------------------------------------------------------------------------------------------
+class ImageAestheticsView(QSplitter):
+    """class AestheticsImageView(QSplitter): view of AestheticsImageController
+    """
+    def __init__(self, _controller, build=False):
+        if pref.verbose: print(" [VIEW] >> AestheticsImageView.__init__(",")")
+        super().__init__(Qt.Vertical)
+
+        self.controller = _controller
+
+        self.imageWidgetController = controller.ImageWidgetController()
+
+        self.layout = QVBoxLayout()
+
+        # --------------- color palette: node selector(node name), color number, palette image.
+        self.labelColorPalette = QLabel("color palette")
+        self.labelNodeSelector = QLabel("process output:")
+        self.nodeSelector = QComboBox(self)
+
+        # recover process nodes names from buildProcessPipe
+        processNodeNameList = []
+        emptyProcessPipe = model.EditImageModel.buildProcessPipe()
+        for node in emptyProcessPipe.processNodes: processNodeNameList.append(node.name)
+
+        # add 'output' at the end to help user
+        processNodeNameList.append('output')
+
+        self.nodeSelector.addItems(processNodeNameList)
+        self.nodeSelector.setCurrentIndex(len(processNodeNameList)-1)
+
+        # QSpinBox
+        self.labelColorsNumber = QLabel("number of colors:")
+        self.nbColors = QSpinBox(self)
+        self.nbColors.setRange(2,8)
+        self.nbColors.setValue(5)
+
+        self.paletteImageWidgetController = controller.ImageWidgetController()
+        imgPalette = hdrCore.aesthetics.Palette('defaultLab5',np.linspace([0,0,0],[100,0,0],5),hdrCore.image.ColorSpace.build('Lab'), hdrCore.image.imageType.SDR).createImageOfPalette()
+        self.paletteImageWidgetController.setImage(imgPalette)
+        self.paletteImageWidgetController.view.setMinimumSize(40, 10)
+
+        # add widgets to layout
+        self.layout.addWidget(self.labelColorPalette)
+        self.layout.addWidget(self.labelNodeSelector)
+        self.layout.addWidget(self.nodeSelector)
+        self.layout.addWidget(self.labelColorsNumber)
+        self.layout.addWidget(self.nbColors)
+        self.layout.addWidget(self.paletteImageWidgetController.view)
+
+        self.layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
+
+        # scroll and etc.
+        self.scroll = QScrollArea()
+        self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+        self.container = QLabel()
+        self.container.setLayout(self.layout)
+        self.scroll.setWidget(self.container)
+        self.scroll.setWidgetResizable(True)
+
+        # add widget to QSplitter
+        self.addWidget(self.imageWidgetController.view)
+        self.addWidget(self.scroll)
+        self.setSizes([60,40])
+        # --------------- composition:
+        # --------------- strength line:
+
+    def setProcessPipe(self,processPipe, paletteImg):
+        self.imageWidgetController.setImage(processPipe.getImage())
+        self.paletteImageWidgetController.setImage(paletteImg)
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+class ColorEditorsAutoView(QPushButton):
+    def __init__(self,controller):
+        super().__init__("auto color selection [! reset edit]")
+        self.controller = controller
 
+        self.clicked.connect(self.controller.auto)
+# ------------------------------------------------------------------------------------------

+ 208 - 0
uHDR/hdrCore/aesthetics.py

@@ -0,0 +1,208 @@
+# uHDR: HDR image editing software
+#   Copyright (C) 2021  remi cozot 
+#
+#    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.
+#
+# hdrCore project 2020
+# author: remi.cozot@univ-littoral.fr
+
+# -----------------------------------------------------------------------------
+# --- Package hdrCore ---------------------------------------------------------
+# -----------------------------------------------------------------------------
+"""
+package hdrCore consists of the core classes for HDR imaging.
+"""
+
+# -----------------------------------------------------------------------------
+# --- Import ------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+import copy, colour, skimage.transform, math, os
+import sklearn.cluster, skimage.transform
+import numpy as np
+import functools
+from . import processing, utils, image
+import preferences.preferences as pref
+from timeit import default_timer as timer
+
+
+
+# -----------------------------------------------------------------------------
+# --- Class ImageAestheticsModel ----------------------------------------------
+# -----------------------------------------------------------------------------
+class ImageAestheticsModel():
+    """class ImageAestheticsModel: abstract class for image aesthetics model
+
+        Static methods:
+            build
+    """
+    def build(processPipe, **kwargs): return ImageAestheticsModel()
+
+# -----------------------------------------------------------------------------
+# --- Class Palette -----------------------------------------------------------
+# -----------------------------------------------------------------------------
+class Palette(ImageAestheticsModel):
+    """class Palette(ImageAestheticsModel):  color palette
+
+        Attributes:
+            name (str): palette name
+            colorSpace (colour.models.RGB_COLOURSPACES): colorspace): colorspace 
+            nbColors (int):   number of colors in the Palette
+            colors(numpy.ndarray): colors in the palette 
+                        colors[0:nbColors,0:2]
+                        sorted according to distance to black (in the palette colorSpace)
+            type (image.imageType): image type (SDR|HDR)
+
+        Methods:
+            createImageOfPalette
+            __repr__
+            __str__
+
+        Static methods:
+            build
+    """    
+    # constructor
+    def __init__(self, name, colors, colorSpace, type):
+        """
+        constructor of aesthetics.Palette:
+
+        """
+        self.name       = name
+        self.colorSpace = colorSpace
+        self.nbColors   = colors.shape[0]
+        self.colors     = np.asarray(sorted(colors.tolist(), key = lambda u  : np.sqrt(np.dot(u,u))))
+        self.type       = type
+
+    @staticmethod    
+    def build(processpipe, nbColors=5, method='kmean-Lab', processId=-1, **kwargs):
+        """build: create the Palette from an image
+        
+            Args:
+                processpipe (hdrCore.processing.ProcessPipe, Required): processpipe
+                nbColors (int, Optionnal): number of colors in the palette (5 default values)
+                method (str, Optionnal): 'kmean-Lab' (default value)
+                processIdx (int, Optionnal): set the process after wihich computation of color palette is done
+                default= -1 at the end of editing
+                kwargs (dict, Otionnal): supplemental parameters according to method
+
+            Returns:
+                (hdrCore.aesthetics.Palette)
+        """
+        # get image according to processId
+        image_ = processpipe.processNodes[processId].outputImage
+
+        # according to method
+        if method == 'kmean-Lab':
+            # taking into acount supplemental parameters of 'kmean-Lab'
+            #  'removeblack' : bool
+            defaultParams = {'removeBlack': True}
+            if 'removeBlack' in kwargs: removeBlack = kwargs['removeBlack']
+            else: removeBlack = defaultParams['removeBlack']
+
+            # get image according to processId
+            image_ = processpipe.processNodes[processId].outputImage
+
+
+            # to Lab then to Vector
+            imageLab = processing.ColorSpaceTransform().compute(image_,dest='Lab')
+            imgLabDataVector = utils.ndarray2vector(imageLab.colorData)
+
+            if removeBlack:
+                # k-means: nb cluster = nbColors + 1
+                kmeans_cluster_Lab = sklearn.cluster.KMeans(n_clusters=nbColors+1)
+                kmeans_cluster_Lab.fit(imgLabDataVector)
+
+                cluster_centers_Lab = kmeans_cluster_Lab.cluster_centers_
+                
+                # remove darkness one
+                idxLmin = np.argmin(cluster_centers_Lab[:,0])                           # idx of darkness
+                cluster_centers_Lab = np.delete(cluster_centers_Lab, idxLmin, axis=0)   # remove min from cluster_centers_Lab
+
+            else:
+                # k-means: nb cluster = nbColors
+                kmeans_cluster_Lab = sklearn.cluster.KMeans(n_clusters=nbColors)
+                kmeans_cluster_Lab.fit(imgLabDataVector)
+                cluster_centers_Lab = kmeans_cluster_Lab.cluster_centers_
+
+            colors = cluster_centers_Lab
+        else: colors = None
+
+        return Palette('Palette_'+image_.name,colors, image.ColorSpace.Lab(), image_.type)
+
+    def createImageOfPalette(self, colorWidth=100):
+        """
+        """
+        if self.colorSpace.name =='Lab':
+            if self.type == image.imageType.HDR :
+                cRGB = processing.Lab_to_sRGB(self.colors, apply_cctf_encoding=True)
+            else:
+                cRGB = processing.Lab_to_sRGB(self.colors, apply_cctf_encoding=False)
+
+        elif self.colorSpace.name=='sRGB':
+            cRGB = self.colors
+        width = colorWidth*cRGB.shape[0]
+        height=colorWidth
+        # return image
+        img = np.ones((height,width,3))
+
+        for i in range(cRGB.shape[0]):
+            xMin= i*colorWidth
+            xMax= xMin+colorWidth
+            yMin=0
+            yMax= colorWidth
+            img[yMin:yMax, xMin:xMax,0]=cRGB[i,0]
+            img[yMin:yMax, xMin:xMax,1]=cRGB[i,1]
+            img[yMin:yMax, xMin:xMax,2]=cRGB[i,2]
+        # colorData, name, type, linear, colorspace, scalingFactor
+        return image.Image(
+            '.',self.name,
+            img,  
+            image.imageType.SDR, False, image.ColorSpace.sRGB())
+
+    # __repr__ and __str__
+    def __repr__(self):
+   
+        res =   " Palette{ name:"           + self.name                 + "\n"  + \
+                "          colorSpace: "    + self.colorSpace.name      + "\n"  + \
+                "          nbColors: "      + str(self.nbColors)        + "\n"  + \
+                "          colors: \n"      + str(self.colors)          + "\n " + \
+                "          type: "          + str(self.type)            + "\n }"  
+        return res
+
+    def __str__(self) :  return self.__repr__()
+# -----------------------------------------------------------------------------
+# --- Class MultidimensionalImageAestheticsModel ------------------------------
+# -----------------------------------------------------------------------------
+class MultidimensionalImageAestheticsModel():
+    """class MultidimensionalImageAestheticsModel contains Multiple Image Aesthetics Model:
+            1 - color palette
+            2 - composition convex hull 
+            3 - composition strength lines
+    
+    """
+    def __init__(self, processpipe):
+        self.processpipe = processpipe
+        self.processPipeChanged = True
+        self.imageAestheticsModels = {}
+
+    def add(self, key, imageAestheticsModel):
+        self.imageAestheticsModels[key] = imageAestheticsModel
+
+    def get(self, key):
+        iam = None
+        if key in self.imageAestheticsModels: iam = self.imageAestheticsModels[key]
+        return iam
+
+    def build(self, key, builder, processpipe):
+        if not isintance(key,list):
+            key, builder = [key],[builder]
+        for k in key:
+            self.add(k,builder.build(processpipe))
+# -----------------------------------------------------------------------------

+ 153 - 0
uHDR/hdrCore/coreC.py

@@ -0,0 +1,153 @@
+# uHDR: HDR image editing software
+#   Copyright (C) 2021  remi cozot 
+#
+#    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.
+#
+# hdrCore project 2020
+# author: remi.cozot@univ-littoral.fr
+
+# -----------------------------------------------------------------------------
+# --- Package hdrCore ---------------------------------------------------------
+# -----------------------------------------------------------------------------
+"""
+package hdrCore consists of the core classes for HDR imaging.
+
+"""
+
+# -----------------------------------------------------------------------------
+# --- Import ------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+import ctypes, copy
+import numpy as np
+import hdrCore.image, hdrCore.processing, hdrCore.utils
+import preferences.preferences as pref
+
+# -----------------------------------------------------------------------------
+# --- coreCcompute ------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
+def coreCcompute(img, processPipe):
+    """compute image process-pipe in C++ (fast computation), fixed process pipe architecture: (1) exposure, (2) contrast, (3) tone-curve, (4)saturation, (5-10) 5 color editors)
+
+        Args:
+            img (hdrCore.image.Image, Required): image
+            processPipe (hdrCore.processing.ProcessPipe, Required): process pipe
+                
+        Returns:
+            (hdrCore.image.Image, Required): image
+    """
+    if pref.verbose:  print(f"[hdrCore] >> coreCcompute({img})") 
+
+    ppDict = processPipe.toDict()
+
+    exposure = ppDict[0]['exposure']['EV']
+
+    contrast = ppDict[1]['contrast']['contrast']
+
+    tonecurveS = ppDict[2]['tonecurve']['shadows'][1]
+    tonecurveB = ppDict[2]['tonecurve']['blacks'][1]
+    tonecurveM = ppDict[2]['tonecurve']['mediums'][1]
+    tonecurveW = ppDict[2]['tonecurve']['whites'][1]
+    tonecurveH = ppDict[2]['tonecurve']['highlights'][1]
+
+    lightnessMaskS = ppDict[3]['lightnessmask']['shadows']
+    lightnessMaskB = ppDict[3]['lightnessmask']['blacks']
+    lightnessMaskM = ppDict[3]['lightnessmask']['mediums']
+    lightnessMaskW = ppDict[3]['lightnessmask']['whites']
+    lightnessMaskH = ppDict[3]['lightnessmask']['highlights']
+
+    saturation = ppDict[4]['saturation']['saturation']
+
+    ce1_sel_lightness = ppDict[5]['colorEditor0']['selection']['lightness']
+    ce1_sel_chroma = ppDict[5]['colorEditor0']['selection']['chroma']
+    ce1_sel_hue = ppDict[5]['colorEditor0']['selection']['hue'] 
+    ce1_tolerance = 0.1
+    ce1_edit_hue = ppDict[5]['colorEditor0']['edit']['hue'] if 'hue' in ppDict[5]['colorEditor0']['edit'] else 0.0
+    ce1_edit_exposure = ppDict[5]['colorEditor0']['edit']['exposure']
+    ce1_edit_contrast = ppDict[5]['colorEditor0']['edit']['contrast']
+    ce1_edit_saturation = ppDict[5]['colorEditor0']['edit']['saturation']
+    ce1_mask = ppDict[5]['colorEditor0']['mask']
+
+    ce2_sel_lightness = ppDict[6]['colorEditor1']['selection']['lightness']
+    ce2_sel_chroma = ppDict[6]['colorEditor1']['selection']['chroma']
+    ce2_sel_hue = ppDict[6]['colorEditor1']['selection']['hue']
+    ce2_tolerance = 0.1
+    ce2_edit_hue = ppDict[6]['colorEditor1']['edit']['hue'] if 'hue' in ppDict[6]['colorEditor1']['edit'] else 0.0
+    ce2_edit_exposure = ppDict[6]['colorEditor1']['edit']['exposure']
+    ce2_edit_contrast = ppDict[6]['colorEditor1']['edit']['contrast']
+    ce2_edit_saturation = ppDict[6]['colorEditor1']['edit']['saturation']
+    ce2_mask = ppDict[6]['colorEditor1']['mask']
+
+    ce3_sel_lightness = ppDict[7]['colorEditor2']['selection']['lightness']
+    ce3_sel_chroma = ppDict[7]['colorEditor2']['selection']['chroma']
+    ce3_sel_hue = ppDict[7]['colorEditor2']['selection']['hue']
+    ce3_tolerance = 0.1
+    ce3_edit_hue = ppDict[7]['colorEditor2']['edit']['hue'] if 'hue' in ppDict[7]['colorEditor2']['edit'] else 0.0
+    ce3_edit_exposure = ppDict[7]['colorEditor2']['edit']['exposure']
+    ce3_edit_contrast = ppDict[7]['colorEditor2']['edit']['contrast']
+    ce3_edit_saturation = ppDict[7]['colorEditor2']['edit']['saturation']
+    ce3_mask = ppDict[7]['colorEditor2']['mask']
+
+    ce4_sel_lightness = ppDict[8]['colorEditor3']['selection']['lightness']
+    ce4_sel_chroma = ppDict[8]['colorEditor3']['selection']['chroma']
+    ce4_sel_hue = ppDict[8]['colorEditor3']['selection']['hue']
+    ce4_tolerance = 0.1
+    ce4_edit_hue = ppDict[8]['colorEditor3']['edit']['hue'] if 'hue' in ppDict[8]['colorEditor3']['edit'] else 0.0
+    ce4_edit_exposure = ppDict[8]['colorEditor3']['edit']['exposure']
+    ce4_edit_contrast = ppDict[8]['colorEditor3']['edit']['contrast']
+    ce4_edit_saturation = ppDict[8]['colorEditor3']['edit']['saturation']
+    ce4_mask = ppDict[8]['colorEditor3']['mask']
+
+    ce5_sel_lightness = ppDict[9]['colorEditor4']['selection']['lightness']
+    ce5_sel_chroma = ppDict[9]['colorEditor4']['selection']['chroma']
+    ce5_sel_hue = ppDict[9]['colorEditor4']['selection']['hue']
+    ce5_tolerance = 0.1
+    ce5_edit_hue = ppDict[9]['colorEditor4']['edit']['hue'] if 'hue' in ppDict[9]['colorEditor4']['edit'] else 0.0
+    ce5_edit_exposure = ppDict[9]['colorEditor4']['edit']['exposure']
+    ce5_edit_contrast = ppDict[9]['colorEditor4']['edit']['contrast']
+    ce5_edit_saturation = ppDict[9]['colorEditor4']['edit']['saturation']
+    ce5_mask = ppDict[9]['colorEditor4']['mask']
+
+
+
+    mylib = ctypes.cdll.LoadLibrary('./HDRip.dll')
+    mylib.full_process_5CO.argtypes = [np.ctypeslib.ndpointer(dtype=ctypes.c_float), ctypes.c_uint, ctypes.c_uint,
+                                    ctypes.c_float,
+                                    ctypes.c_float,
+                                    ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float,
+                                    ctypes.c_bool, ctypes.c_bool, ctypes.c_bool, ctypes.c_bool, ctypes.c_bool,
+                                    ctypes.c_float,
+                                    ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_bool,
+                                    ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_bool,
+                                    ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_bool,
+                                    ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_bool,
+                                    ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_bool
+    ]
+    mylib.full_process_5CO.restype = np.ctypeslib.ndpointer(dtype=ctypes.c_float, shape=(img.colorData.shape[0],img.colorData.shape[1],3))
+
+    resDLL = mylib.full_process_5CO(img.colorData,
+                                img.colorData.shape[1],
+                                img.colorData.shape[0],
+                                exposure,
+                                contrast,
+                                tonecurveS, tonecurveB, tonecurveM, tonecurveW, tonecurveH,
+                                lightnessMaskS, lightnessMaskB, lightnessMaskM, lightnessMaskW, lightnessMaskH,
+                                saturation,
+                                ce1_sel_lightness[0], ce1_sel_lightness[1], ce1_sel_chroma[0], ce1_sel_chroma[1], ce1_sel_hue[0], ce1_sel_hue[1], ce1_tolerance, ce1_edit_hue, ce1_edit_exposure, ce1_edit_contrast, ce1_edit_saturation, ce1_mask,
+                                ce2_sel_lightness[0], ce2_sel_lightness[1], ce2_sel_chroma[0], ce2_sel_chroma[1], ce2_sel_hue[0], ce2_sel_hue[1], ce2_tolerance, ce2_edit_hue, ce2_edit_exposure, ce2_edit_contrast, ce2_edit_saturation, ce2_mask,
+                                ce3_sel_lightness[0], ce3_sel_lightness[1], ce3_sel_chroma[0], ce3_sel_chroma[1], ce3_sel_hue[0], ce3_sel_hue[1], ce3_tolerance, ce3_edit_hue, ce3_edit_exposure, ce3_edit_contrast, ce3_edit_saturation, ce3_mask,
+                                ce4_sel_lightness[0], ce4_sel_lightness[1], ce4_sel_chroma[0], ce4_sel_chroma[1], ce4_sel_hue[0], ce4_sel_hue[1], ce4_tolerance, ce4_edit_hue, ce4_edit_exposure, ce4_edit_contrast, ce4_edit_saturation, ce4_mask,
+                                ce5_sel_lightness[0], ce5_sel_lightness[1], ce5_sel_chroma[0], ce5_sel_chroma[1], ce5_sel_hue[0], ce5_sel_hue[1], ce5_tolerance, ce5_edit_hue, ce5_edit_exposure, ce5_edit_contrast, ce5_edit_saturation, ce5_mask
+                                )
+
+    img.colorData = copy.deepcopy(resDLL)
+
+    return img

+ 11 - 87
uHDR/hdrCore/image.py

@@ -14,80 +14,11 @@
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 
-# -----------------------------------------------------------------------------
-# --- VERSION TRACKING --------------------------------------------------------
-# -----------------------------------------------------------------------------
-
-# -----------------------------------------------------------------------------
-# (1): DEBUG
-#      HDRImageViewer doesn't open 
-#      add time.sleep() in HDRviewerController.displayFile(...)
-#      ---- Rémi Cozot, June 2021
-# -----------------------------------------------------------------------------
-# (2): Prevent Freezing
-#      Add multithreading in:
-#      i  - processpipe processing (except for export and display)
-#      ii - loading images
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (3): Prevent Freezing and speed up
-#      Add multithreading in:
-#      i  - processpipe processing for HDR display
-#      ii - processpipe procssing export single image to vesaHDR1000
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (4): Fix ev value and accuracy in color editor 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (5): Fix Y curve plot 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (6): Ability to export SDR (.jpg) image to HDR (tone expansion).
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-
-
 # -----------------------------------------------------------------------------
 # --- Package hdrCore ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
-package hdrCoreconsists of the core classes for HDR imaging:
-(1) classes:    class imageType
-                class channel
-                class Image
-                class ColorSpace 
-                class Histogram
-(2) class:      class metadata
-(3) processing functions and classes:
-                def XYZ_to_sRGB(XYZ,apply_cctf_encoding=True):
-                def sRGB_to_XYZ(RGB,apply_cctf_decoding=True):  
-                def Lab_to_XYZ(Lab)
-                def XYZ_to_Lab(XYZ)
-                def Lab_to_sRGB(Lab, apply_cctf_encoding=True, clip = False)
-                def sRGB_to_Lab(RGB, apply_cctf_decoding=True)
-                def Lch_to_sRGB(Lch,apply_cctf_encoding=True, clip=False)   
-                class Processing(object)
-                class tmo_cctf(Processing)
-                class exposure(Processing)
-                class contrast(Processing)
-                class clip(Processing)
-                class ColorSpaceTransform(Processing)
-                class resize(Processing)
-                class Ycurve(Processing)
-                class saturation(Processing)
-                class lightnessMask(Processing)
-                class geometry(Processing)
-(4) processing nodes that encapsulate processing objects in process-pipe: 
-                class ProcessNode(object)
-(5) process-pipe that defines the pipeline of processing:
-                class ProcessPipe(object)
-(6) some utils functions:
-                def filenamesplit(filename)
-                def filterlistdir(path,extList)
-                def ndarray2vector(nda)
-                def NPlinearWeightMask(x,xMin,xMax,xTolerance)
-                def croppRotated(h,w,alpha)
-(7) HDRdisplay model
+package hdrCoreconsists of the core classes for HDR imaging.
 """
 
 # -----------------------------------------------------------------------------
@@ -96,6 +27,7 @@ package hdrCoreconsists of the core classes for HDR imaging:
 import enum, rawpy, colour, imageio, copy, os, functools, skimage.transform
 import numpy as np
 from . import utils, processing, metadata
+import preferences.preferences as pref
 
 imageio.plugins.freeimage.download()
 
@@ -375,7 +307,7 @@ class Image(object):
 
         # create image object
         res =  Image(path, name+'.'+ext, np.float32(imgDouble),type, linear, None, scalingFactor)           # colorspace = None will be set in metadata.metadata.build(res)
-        res.metadata = metadata.metadata.build(res)                                             # build metadata (read if json file exists, else recover from exif data)
+        res.metadata = metadata.metadata.build(res)                                                         # build metadata (read if json file exists, else recover from exif data)
 
         # update path
         res.metadata.metadata['path'] = copy.deepcopy(path)
@@ -392,11 +324,12 @@ class Image(object):
         else: # 'sRGB' in color space <from exif>
             res.colorSpace = RGBcolorspace 
 
-        # vesa display DISPLAY
+        # display ready image
         if 'display' in res.metadata.metadata.keys():
-            if res.metadata.metadata['display'] == "vesaDisplayHDR1000":
-                ##### res.colorData = res.colorData/12  ##### << WARNING
-                res.colorData = res.colorData/utils.HDRdisplay['vesaDisplayHDR1000']['scaling']  ### << WARNING
+            disp = res.metadata.metadata['display']
+            if disp in pref.getHDRdisplays().keys():
+                scaling = pref.getHDRdisplays()[disp]['scaling']
+                res.colorData = res.colorData/scaling  
 
         return res
 
@@ -741,20 +674,14 @@ class ColorSpace(object):
         """
         TODO - Documentation de la méthode Lab
         """
-        return colour.RGB_Colourspace('Lab', primaries = None, whitepoint = None, whitepoint_name=None, 
-                                      RGB_to_XYZ_matrix=None, XYZ_to_RGB_matrix=None, 
-                                      cctf_encoding=None, cctf_decoding=None,
-                                      use_derived_RGB_to_XYZ_matrix=False, use_derived_XYZ_to_RGB_matrix=False)
+        return colour.RGB_Colourspace('Lab', primaries=np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]), whitepoint=np.array([0.32168, 0.33767]))
      
     @staticmethod                                 
     def Lch():
         """
         TODO - Documentation de la méthode Lch
         """
-        return colour.RGB_Colourspace('Lch', primaries = None, whitepoint = None, whitepoint_name=None, 
-                                      RGB_to_XYZ_matrix=None, XYZ_to_RGB_matrix=None, 
-                                      cctf_encoding=None, cctf_decoding=None,
-                                      use_derived_RGB_to_XYZ_matrix=False, use_derived_XYZ_to_RGB_matrix=False)
+        return colour.RGB_Colourspace('Lch', primaries=np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]), whitepoint=np.array([0.32168, 0.33767]))
 
     @staticmethod
     def sRGB():
@@ -777,10 +704,7 @@ class ColorSpace(object):
         """
         TODO - Documentation de la méthode XYZ
         """
-        return colour.RGB_Colourspace('XYZ', primaries = None, whitepoint = None, whitepoint_name=None, 
-                                      RGB_to_XYZ_matrix=None, XYZ_to_RGB_matrix=None, 
-                                      cctf_encoding=None, cctf_decoding=None,
-                                      use_derived_RGB_to_XYZ_matrix=False, use_derived_XYZ_to_RGB_matrix=False)
+        return colour.RGB_Colourspace('XYZ', primaries=np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]), whitepoint=np.array([0.32168, 0.33767]))
 
     @staticmethod
     def build(name='sRGB'):

+ 72 - 106
uHDR/hdrCore/metadata.py

@@ -14,92 +14,44 @@
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 
-# -----------------------------------------------------------------------------
-# --- VERSION TRACKING --------------------------------------------------------
-# -----------------------------------------------------------------------------
-
-# -----------------------------------------------------------------------------
-# (1): DEBUG
-#      HDRImageViewer doesn't open 
-#      add time.sleep() in HDRviewerController.displayFile(...)
-#      ---- Rémi Cozot, June 2021
-# -----------------------------------------------------------------------------
-# (2): Prevent Freezing
-#      Add multithreading in:
-#      i  - processpipe processing (except for export and display)
-#      ii - loading images
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (3): Prevent Freezing and speed up
-#      Add multithreading in:
-#      i  - processpipe processing for HDR display
-#      ii - processpipe procssing export single image to vesaHDR1000
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (4): Fix ev value and accuracy in color editor 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (5): Fix Y curve plot 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (6): Ability to export SDR (.jpg) image to HDR (tone expansion).
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-
-
 # -----------------------------------------------------------------------------
 # --- Package hdrCore ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
-package hdrCore consists of the core classes for HDR imaging:
-(1) classes:    class imageType
-                class channel
-                class Image
-                class ColorSpace 
-                class Histogram
-(2) class:      class metadata
-(3) processing functions and classes:
-                def XYZ_to_sRGB(XYZ,apply_cctf_encoding=True):
-                def sRGB_to_XYZ(RGB,apply_cctf_decoding=True):  
-                def Lab_to_XYZ(Lab)
-                def XYZ_to_Lab(XYZ)
-                def Lab_to_sRGB(Lab, apply_cctf_encoding=True, clip = False)
-                def sRGB_to_Lab(RGB, apply_cctf_decoding=True)
-                def Lch_to_sRGB(Lch,apply_cctf_encoding=True, clip=False)   
-                class Processing(object)
-                class tmo_cctf(Processing)
-                class exposure(Processing)
-                class contrast(Processing)
-                class clip(Processing)
-                class ColorSpaceTransform(Processing)
-                class resize(Processing)
-                class Ycurve(Processing)
-                class saturation(Processing)
-                class lightnessMask(Processing)
-                class geometry(Processing)
-(4) processing nodes that encapsulate processing objects in process-pipe: 
-                class ProcessNode(object)
-(5) process-pipe that defines the pipeline of processing:
-                class ProcessPipe(object)
-(6) some utils functions:
-                def filenamesplit(filename)
-                def filterlistdir(path,extList)
-                def ndarray2vector(nda)
-                def NPlinearWeightMask(x,xMin,xMax,xTolerance)
-                def croppRotated(h,w,alpha)
-(7) HDRdisplay model
+package hdrCore consists of the core classes for HDR imaging.
 """
 
 
 # -----------------------------------------------------------------------------
 # --- Import ------------------------------------------------------------------
 # -----------------------------------------------------------------------------
-import enum, rawpy, colour, imageio, json, os, subprocess, ast
+import enum, rawpy, colour, imageio, json, os, subprocess, ast, copy
 import numpy as np
 from . import utils, processing, image
+import preferences.preferences as pref
 
 # -----------------------------------------------------------------------------
-# --- Class metadata ---------------------------------------------------------
+# --- Class tags --------------------------------------------------------------
+# -----------------------------------------------------------------------------
+class tags:
+    """
+    the class tags is use to defines tags that can be set to an image. The tags definition are defined in a json file (./preferences/tags.json).
+    tags are group in tags group (only one level of hierarchy !
+    """
+    def __init__(self):
+        if os.path.exists("./preferences/tags.json"):
+            with open('./preferences/tags.json') as f: self.tags =  json.load(f)
+        else:
+            self.tags = {'no-tag':[]}
+    # ---------------------------------------------------------------------------
+    def getTagsRootName(self):
+        return list(self.tags.keys())[0]
+    # ---------------------------------------------------------------------------
+
+
+
+# -----------------------------------------------------------------------------
+# --- Class metadata ----------------------------------------------------------
 # -----------------------------------------------------------------------------
 class metadata:
     """
@@ -144,32 +96,36 @@ class metadata:
                      'Software': None,
                      'Lens': None,
                      'Focal Length': None},
-            'hdr-use-case': [
-                {'inside': {
-                    'Window with view on bright outdoor': None,
-                    'High Contrast and illuminants tend to be over-exposed':None,
-                    'Backlit portrait':None}},
-                {'outside':{
-                    'Sun in the frame':None,
-                    'Backlight':None,
-                    'Shadow and direct lighting':None,
-                    'Backlit portrait':None,
-                    'Nature':None,
-                    'Quite LDR':None}},
-                {'lowlight':{
-                    'Portrait':None,
-                    'Outside with bright illuminants':None,
-                    'Cityscape':None,
-                    'Event':None}},
-                {'special':{
-                    'Shiny object  and specular highlights':None,
-                    'Memory colors':None,
-                    'Scene with color checker or gray chart':None,
-                    'Translucent objects and stained glass':None,
-                    'Traditional tone mapping failing cases':None}}],
+            #'hdr-use-case': [
+            #    {'inside': {
+            #        'Window with view on bright outdoor': None,
+            #        'High Contrast and illuminants tend to be over-exposed':None,
+            #        'Backlit portrait':None}},
+            #    {'outside':{
+            #        'Sun in the frame':None,
+            #        'Backlight':None,
+            #        'Shadow and direct lighting':None,
+            #        'Backlit portrait':None,
+            #        'Nature':None,
+            #        'Quite LDR':None}},
+            #    {'lowlight':{
+            #        'Portrait':None,
+            #        'Outside with bright illuminants':None,
+            #        'Cityscape':None,
+            #        'Event':None}},
+            #    {'special':{
+            #        'Shiny object  and specular highlights':None,
+            #        'Memory colors':None,
+            #        'Scene with color checker or gray chart':None,
+            #        'Translucent objects and stained glass':None,
+            #        'Traditional tone mapping failing cases':None}}],
             'processpipe': None,
             'display' : None
             }
+        # other metadta from tags.json
+        self.otherTags = tags()
+        self.metadata[self.otherTags.getTagsRootName()] = copy.deepcopy(self.otherTags.tags[self.otherTags.getTagsRootName()])
+
         self.metadata['filename'] = _image.name
         self.metadata['path'] =     _image.path
         h,w, c = _image.shape
@@ -201,8 +157,18 @@ class metadata:
         JSONfilename = os.path.join(_image.path, filenameMetadata)   
 
         if os.path.isfile(JSONfilename):
-            with open(JSONfilename, "r") as file: 
-                res.metadata = json.load(file)
+            with open(JSONfilename, "r") as file:
+                metaInFile = json.load(file)
+                # copy all metadata from files
+                if pref.keepAllMeta:
+                    for keyInFile in metaInFile.keys():  self.metadata[keyInFile] = copy.deepcopy(metaInFile[keyInFile])
+                else:
+                    for keyInFile in metaInFile.keys():  
+                        if keyInFile in res.metadata :
+                            res.metadata[keyInFile] = copy.deepcopy(metaInFile[keyInFile])
+                        else:
+                            print(f'WARNING[metadata "{keyInFile}" not in "tags.json"  will be deleted! (consider changing "keepAllMeta" to "True" in  preferences.py)]')
+
                 if _image.isHDR(): res.metadata['exif']['Color Space']=   'scRGB'
 
         else:
@@ -211,7 +177,7 @@ class metadata:
             with open(JSONfilename, "w") as file: json.dump(res.metadata,file)
 
         return res
-
+    # ---------------------------------------------------------------------------
     def save(self):
         """
         Save the metadata in a json file. The extension .json is added to the filename of the image.
@@ -221,7 +187,7 @@ class metadata:
         JSONfilename = os.path.join(self.image.path, filenameMetadata)   
 
         with open(JSONfilename, "w") as file: json.dump(self.metadata,file)
-
+    # ---------------------------------------------------------------------------
     @staticmethod
     def readExif(filename):
         """
@@ -257,7 +223,7 @@ class metadata:
         else: print("ERROR[metadata.readExif(",filename,"): file not found]")
 
         return exifDict
-
+    # ---------------------------------------------------------------------------
     def recoverData(self,exif):
         """
         TODO - Documentation de la méthode recoverData
@@ -278,7 +244,7 @@ class metadata:
                 elif exif[exifColorSpace[2]]==2:    self.metadata['exif']['Color Space']=   'Adobe RGB (1998)'
                 else:                               self.metadata['exif']['Color Space']=   metadata.defaultColorSpaceName
             else: 
-                print("WARNING[",self.image.name, "exif does not contain 'Color Space' nor 'Profile Description', color space is set to sRGB !]")
+                print("WARNING[",self.image.name, "exif does not contain 'Color Space' nor 'Profile Description', color space is set to sRGB  or scRGB for HDR images!]")
                 self.metadata['exif']['Color Space'] = metadata.defaultColorSpaceName 
 
             # exposure time: 'Exposure Time', 'ExposureTime' 
@@ -306,8 +272,7 @@ class metadata:
             
             # ISO: 'ISO', 'ISOSpeedRatings'
             exifISO = ['ISO', 'ISOSpeedRatings']
-            if exifISO[0] in exif: 
-                self.metadata['exif']['ISO']= ast.literal_eval(exif[exifISO[0]])
+            if exifISO[0] in exif: self.metadata['exif']['ISO']= ast.literal_eval(exif[exifISO[0]])
             elif exifISO[1] in exif: self.metadata['exif']['ISO']= exif[exifISO[1]]
             else: self.metadata['exif']['ISO'] = None
 
@@ -348,7 +313,7 @@ class metadata:
         
         else: # exif data = {}
             self.metadata['exif']['Color Space'] = metadata.defaultColorSpaceName
-            print(" [META] >> metadata.recoverData(",self.image.name,").colorSpace undefined set it to:", 'sRGB')
+            print(" [META] >> metadata.recoverData(",self.image.name,").colorSpace undefined set it to:", 'sRGB or scRGB for HDR images')
 
         # bit per sample
         if self.metadata['exif']['Bits Per Sample'] == None:
@@ -359,7 +324,7 @@ class metadata:
         # dynamic range
         self.image.colorSpace= image.ColorSpace.sRGB()
         self.metadata['exif']['Dynamic Range (stops)'] = self.image.getDynamicRange(0.5)
-
+    # ---------------------------------------------------------------------------
     def __repr__(self):
         """
         Convert the metadata in string.
@@ -369,7 +334,7 @@ class metadata:
                 The metadata in a string format.
         """
         return json.dumps(self.metadata)
-
+    # ---------------------------------------------------------------------------
     def __str__(self):
         """
         Convert the metadata in string.
@@ -379,3 +344,4 @@ class metadata:
                 The metadata in a string format.
         """
         return self.__repr__()
+# ---------------------------------------------------------------------------

+ 46 - 0
uHDR/hdrCore/net.py

@@ -0,0 +1,46 @@
+# uHDR: HDR image editing software
+#   Copyright (C) 2021  remi cozot 
+#
+#    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.
+#
+# hdrCore project 2020
+# author: remi.cozot@univ-littoral.fr
+
+# -----------------------------------------------------------------------------
+# --- Package hdrCore ---------------------------------------------------------
+# -----------------------------------------------------------------------------
+"""
+package hdrCoreconsists of the core classes for HDR imaging.
+"""
+
+# -----------------------------------------------------------------------------
+# --- Import ------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+import torch
+import torch.nn as nn
+
+# -----------------------------------------------------------------------------
+# --- class Net ---------------------------------------------------------------
+# -----------------------------------------------------------------------------
+class Net(nn.Module):
+    def __init__(self,n_feature, n_output):
+        super(Net, self).__init__()
+        self.layer = nn.Sequential(
+            nn.Linear(n_feature, 5),
+            nn.BatchNorm1d(5),
+            nn.Sigmoid(),
+        )
+    # -----------------------------------------------------------------------------
+    def forward(self, x):
+        """
+        """
+        return self.layer(x)
+    

+ 1 - 70
uHDR/hdrCore/numbafun.py

@@ -14,80 +14,11 @@
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 
-# -----------------------------------------------------------------------------
-# --- VERSION TRACKING --------------------------------------------------------
-# -----------------------------------------------------------------------------
-
-# -----------------------------------------------------------------------------
-# (1): DEBUG
-#      HDRImageViewer doesn't open 
-#      add time.sleep() in HDRviewerController.displayFile(...)
-#      ---- Rémi Cozot, June 2021
-# -----------------------------------------------------------------------------
-# (2): Prevent Freezing
-#      Add multithreading in:
-#      i  - processpipe processing (except for export and display)
-#      ii - loading images
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (3): Prevent Freezing and speed up
-#      Add multithreading in:
-#      i  - processpipe processing for HDR display
-#      ii - processpipe procssing export single image to vesaHDR1000
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (4): Fix ev value and accuracy in color editor 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (5): Fix Y curve plot 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (6): Ability to export SDR (.jpg) image to HDR (tone expansion).
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-
-
 # -----------------------------------------------------------------------------
 # --- Package hdrCore ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
-package hdrCore consists of the core classes for HDR imaging:
-(1) classes:    class imageType
-                class channel
-                class Image
-                class ColorSpace 
-                class Histogram
-(2) class:      class metadata
-(3) processing functions and classes:
-                def XYZ_to_sRGB(XYZ,apply_cctf_encoding=True):
-                def sRGB_to_XYZ(RGB,apply_cctf_decoding=True):  
-                def Lab_to_XYZ(Lab)
-                def XYZ_to_Lab(XYZ)
-                def Lab_to_sRGB(Lab, apply_cctf_encoding=True, clip = False)
-                def sRGB_to_Lab(RGB, apply_cctf_decoding=True)
-                def Lch_to_sRGB(Lch,apply_cctf_encoding=True, clip=False)   
-                class Processing(object)
-                class tmo_cctf(Processing)
-                class exposure(Processing)
-                class contrast(Processing)
-                class clip(Processing)
-                class ColorSpaceTransform(Processing)
-                class resize(Processing)
-                class Ycurve(Processing)
-                class saturation(Processing)
-                class lightnessMask(Processing)
-                class geometry(Processing)
-(4) processing nodes that encapsulate processing objects in process-pipe: 
-                class ProcessNode(object)
-(5) process-pipe that defines the pipeline of processing:
-                class ProcessPipe(object)
-(6) some utils functions:
-                def filenamesplit(filename)
-                def filterlistdir(path,extList)
-                def ndarray2vector(nda)
-                def NPlinearWeightMask(x,xMin,xMax,xTolerance)
-                def croppRotated(h,w,alpha)
-(7) HDRdisplay model
+package hdrCore consists of the core classes for HDR imaging.
 """
 
 # -----------------------------------------------------------------------------

+ 106 - 144
uHDR/hdrCore/processing.py

@@ -14,80 +14,12 @@
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 
-# -----------------------------------------------------------------------------
-# --- VERSION TRACKING --------------------------------------------------------
-# -----------------------------------------------------------------------------
-
-# -----------------------------------------------------------------------------
-# (1): DEBUG
-#      HDRImageViewer doesn't open 
-#      add time.sleep() in HDRviewerController.displayFile(...)
-#      ---- Rémi Cozot, June 2021
-# -----------------------------------------------------------------------------
-# (2): Prevent Freezing
-#      Add multithreading in:
-#      i  - processpipe processing (except for export and display)
-#      ii - loading images
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (3): Prevent Freezing and speed up
-#      Add multithreading in:
-#      i  - processpipe processing for HDR display
-#      ii - processpipe procssing export single image to vesaHDR1000
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (4): Fix ev value and accuracy in color editor 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (5): Fix Y curve plot 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (6): Ability to export SDR (.jpg) image to HDR (tone expansion).
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-
-
 # -----------------------------------------------------------------------------
 # --- Package hdrCore ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
-package hdrCore consists of the core classes for HDR imaging:
-(1) classes:    class imageType
-                class channel
-                class Image
-                class ColorSpace 
-                class Histogram
-(2) class:      class metadata
-(3) processing functions and classes:
-                def XYZ_to_sRGB(XYZ,apply_cctf_encoding=True):
-                def sRGB_to_XYZ(RGB,apply_cctf_decoding=True):  
-                def Lab_to_XYZ(Lab)
-                def XYZ_to_Lab(XYZ)
-                def Lab_to_sRGB(Lab, apply_cctf_encoding=True, clip = False)
-                def sRGB_to_Lab(RGB, apply_cctf_decoding=True)
-                def Lch_to_sRGB(Lch,apply_cctf_encoding=True, clip=False)   
-                class Processing(object)
-                class tmo_cctf(Processing)
-                class exposure(Processing)
-                class contrast(Processing)
-                class clip(Processing)
-                class ColorSpaceTransform(Processing)
-                class resize(Processing)
-                class Ycurve(Processing)
-                class saturation(Processing)
-                class lightnessMask(Processing)
-                class geometry(Processing)
-(4) processing nodes that encapsulate processing objects in process-pipe: 
-                class ProcessNode(object)
-(5) process-pipe that defines the pipeline of processing:
-                class ProcessPipe(object)
-(6) some utils functions:
-                def filenamesplit(filename)
-                def filterlistdir(path,extList)
-                def ndarray2vector(nda)
-                def NPlinearWeightMask(x,xMin,xMax,xTolerance)
-                def croppRotated(h,w,alpha)
-(7) HDRdisplay model
+package hdrCore consists of the core classes for HDR imaging.
+
 """
 
 # -----------------------------------------------------------------------------
@@ -100,11 +32,15 @@ import skimage.transform
 import functools
 from geomdl import BSpline
 from geomdl import utilities
-from . import image, utils, numbafun
+from . import image, utils, aesthetics
+# RCZT 2023
+# from . import image, utils, numbafun, aesthetics
 import guiQt.controller as gc
 import preferences.preferences as pref
 from timeit import default_timer as timer
 
+import hdrCore.coreC
+
 # -----------------------------------------------------------------------------
 # --- package functions -------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -135,7 +71,7 @@ def sRGB_to_XYZ(RGB,apply_cctf_decoding=True):
     """
     return colour.sRGB_to_XYZ(RGB, 
                               illuminant=np.array([ 0.3127, 0.329 ]), 
-                              chromatic_adaptation_method='CAT02', 
+                              chromatic_adaptation_transform='CAT02', 
                               apply_cctf_decoding=apply_cctf_decoding)           
 
 def Lab_to_XYZ(Lab):
@@ -195,7 +131,7 @@ def sRGB_to_Lab(RGB, apply_cctf_decoding=True):
     """
     XYZ = colour.sRGB_to_XYZ(RGB, 
                              illuminant=np.array([ 0.3127, 0.329 ]), 
-                             chromatic_adaptation_method='CAT02', 
+                             chromatic_adaptation_transform='CAT02', 
                              apply_cctf_decoding=apply_cctf_decoding)           
     Lab = colour.XYZ_to_Lab(XYZ, illuminant=np.array([ 0.3127, 0.329 ]))
     return Lab
@@ -227,7 +163,10 @@ def Lch_to_sRGB(Lch,apply_cctf_encoding=True, clip=False):
 # -----------------------------------------------------------------------------
 class Processing(object):
     """
-    TODO - Documentation de la classe Processing
+    class Processing: abstract class for processing object
+
+    Methods:
+        compute
     """
 
     def compute(self,image,**kwargs):
@@ -235,14 +174,13 @@ class Processing(object):
         TODO - Documentation de la méthode compute
 
         Args:
-            image: TODO
-                TODO
-            kwargs: TODO
-                TODO
+            image (hdrCore.image.Image, Required): input image
+                
+            kwargs (dict, Optionnal): parameters of processing
                 
         Returns:
-            TODO
-                TODO
+            (hdrCore.image.Image)
+
         """
         return copy.deepcopy(image)
 # -----------------------------------------------------------------------------
@@ -338,12 +276,11 @@ class exposure(Processing):
                     res.linear =        True
 
                 dt = timer() - start
-                print("############# exposure: cctf_decoding (",pref.computation,"):",dt)
 
             res.colorData =     res.colorData*math.pow(2,EV)
 
         end = timer()
-        if pref.verboseProcessing: print (" [PROCESS-COMPUTE](",end - start,") >> exposure(",img.name,"):", kwargs)
+        if pref.verbose: print (" [PROCESS-PROFILING](",end - start,") >> exposure(",img.name,"):", kwargs)
 
         return res
 
@@ -370,7 +307,7 @@ class exposure(Processing):
 
         bins = np.linspace(0,1,25+1)
 
-        @staticmethod
+        # local method for pool (not static!)
         def evEval(ev):
             """
             TODO - Documentation de la méthode evEval
@@ -399,7 +336,7 @@ class exposure(Processing):
         sumsH  = list(results)
         
         bestEV = evs[np.argmax(sumsH)]
-        if pref.verboseProcessing: print('  [PROCESS] >> exposure.auto(',img.name,'):BEST EV:',bestEV)
+        if pref.verbose: print('  [PROCESS] >> exposure.auto(',img.name,'):BEST EV:',bestEV)
       
         return {'EV':bestEV}
 # -----------------------------------------------------------------------------
@@ -453,7 +390,6 @@ class contrast(Processing):
                     res.linear =        False
 
                 dt = timer() - start
-                print("############# contrast: cctf_encoding (",pref.computation,"):",dt)
 
             # scaling contrast
             contrastValue = contrastValue/100 
@@ -467,7 +403,7 @@ class contrast(Processing):
             res.colorData = scalingFactor*(res.colorData-0.5)+0.5
         
         end=timer()    
-        if pref.verboseProcessing: print(" [PROCESS-COMPUTE] (",end-start,")>> contrast(",img.name,"):", kwargs)
+        if pref.verbose: print(" [PROCESS-PROFILING] (",end-start,")>> contrast(",img.name,"):", kwargs)
 
         return res
 # -----------------------------------------------------------------------------
@@ -512,10 +448,8 @@ class ColorSpaceTransform(Processing):
         Color space transform operator
 
         Args:
-            img: hdrCore.image.Image
-                Required  : hdr image
-            kwargs: dict
-                Optionnal : parameters
+            img (hdrCore.image.Image, Required)  : input image
+            kwargs (dict,Optionnal) : parameters
                 TODO
                 
         Returns:
@@ -535,7 +469,7 @@ class ColorSpaceTransform(Processing):
                         apply_cctf_decoding=True if not img.linear else False
                         
                         RGB = res.colorData
-                        XYZ = colour.sRGB_to_XYZ(RGB, illuminant=np.array([ 0.3127, 0.329 ]), chromatic_adaptation_method='CAT02', apply_cctf_decoding=apply_cctf_decoding)           
+                        XYZ = colour.sRGB_to_XYZ(RGB, illuminant=np.array([ 0.3127, 0.329 ]), chromatic_adaptation_transform='CAT02', apply_cctf_decoding=apply_cctf_decoding)           
                         Lab = colour.XYZ_to_Lab(XYZ, illuminant=np.array([ 0.3127, 0.329 ]))
                         res.colorData, res.linear, res.colorSpace  = Lab, None,image.ColorSpace.Lab()
 
@@ -572,7 +506,7 @@ class ColorSpaceTransform(Processing):
                     if currentCS=="sRGB": # sRGB to XYZ                                                         
                         apply_cctf_decoding=True if  (img.type == image.imageType.SDR) and (not img.linear) else False
                         RGB = res.colorData
-                        XYZ = colour.sRGB_to_XYZ(RGB, illuminant=np.array([ 0.3127, 0.329 ]), chromatic_adaptation_method='CAT02', apply_cctf_decoding=apply_cctf_decoding)           
+                        XYZ = colour.sRGB_to_XYZ(RGB, illuminant=np.array([ 0.3127, 0.329 ]), chromatic_adaptation_transform='CAT02', apply_cctf_decoding=apply_cctf_decoding)           
                         res.colorData,res.linear , res.colorSpace = XYZ, True, image.ColorSpace.XYZ()
                         
                     elif currentCS=="XYZ": # XYZ to XYZ                                                         
@@ -682,9 +616,6 @@ class Ycurve(Processing):
                     res.linear =        False
 
                 dt = timer() - start
-                print("############# Ycurve: cctf_encoding (",pref.computation,"):",dt)
-
-
 
             colorDataY =    sRGB_to_XYZ(res.colorData, apply_cctf_decoding=False)[:,:,1] 
             # change for multi-threading computation
@@ -717,7 +648,7 @@ class Ycurve(Processing):
                 res.colorData[:,:,2] = res.colorData[:,:,2]*colorDataFY/colorDataY
         
         end = timer()        
-        if pref.verboseProcessing: print(" [PROCESS-COMPUTE] (",end - start,")>> Ycurve(",img.name,"):", kwargs)
+        if pref.verbose: print(" [PROCESS-PROFILING] (",end - start,")>> Ycurve(",img.name,"):", kwargs)
 
         return res
 # -----------------------------------------------------------------------------
@@ -775,7 +706,7 @@ class saturation(Processing):
 
 
         end = timer()
-        if pref.verboseProcessing: print(" [PROCESS-COMPUTE] (",end - start,")>> saturation(",img.name,"):", kwargs)
+        if pref.verbose: print(" [PROCESS-PROFILING] (",end - start,")>> saturation(",img.name,"):", kwargs)
 
         return res
 # -----------------------------------------------------------------------------
@@ -835,7 +766,6 @@ class colorEditor(Processing):
                     colorLab = sRGB_to_Lab(res.colorData, apply_cctf_decoding=True)
                     colorLCH = colour.Lab_to_LCHab(colorLab)
                 covnEnd = timer()
-                print("############# colorEditor: sRGB to Lch (",pref.computation,"):",covnEnd - covnStart)
 
             # selection from colorLCH
             colorDataHue =          copy.deepcopy(colorLCH[:,:,2])
@@ -863,8 +793,6 @@ class colorEditor(Processing):
             hueMask =           utils.NPlinearWeightMask(colorDataHue, hMin, hMax, hueTolerance)
 
             maskEnd = timer()
-            print("############# colorEditor: mask (",pref.computation,"):",maskEnd - maskStart)
-
 
             mask = np.minimum(lightnessMask, np.minimum(chromaMask,hueMask))
             compMask = 1.0 - mask
@@ -907,7 +835,6 @@ class colorEditor(Processing):
 
                 pivot = math.pow(2,ev)*(lMin+lMax)/2/100
 
-                # print("contrast::scaling,pivot", scalingFactor,",",pivot)
                 if not isinstance(colorRGB, np.ndarray):    colorRGB = Lch_to_sRGB(colorLCH,apply_cctf_encoding=True, clip=False)
                 else :                                      colorRGB = colour.cctf_encoding(colorRGB, function='sRGB')
                 
@@ -927,7 +854,6 @@ class colorEditor(Processing):
 
         else:
             if res.colorSpace.name == 'Lch':
-                # print("colorEditor: Lch to RGB (no-processing)")
                 colorLCH = res.colorData
                 # return to RGB (linear)
                 colorRGB = Lch_to_sRGB(colorLCH,apply_cctf_encoding=False, clip=False)
@@ -945,7 +871,7 @@ class colorEditor(Processing):
             res.linear = False
 
         end = timer()
-        if pref.verboseProcessing: print(" [PROCESS-COMPUTE](",end - start,") >> colorEditor(",img.name,"):", kwargs)
+        if pref.verbose: print(" [PROCESS-PROFILING](",end - start,") >> colorEditor(",img.name,"):", kwargs)
 
         return res
 # -----------------------------------------------------------------------------
@@ -1004,7 +930,7 @@ class lightnessMask(Processing):
             res.colorData = mask
         
         end = timer()
-        if pref.verboseProcessing: print(" [PROCESS-COMPUTE](",end - start,") >> lightnessMask(",res.name,"):", kwargs)
+        if pref.verbose: print(" [PROCESS-PROFILING](",end - start,") >> lightnessMask(",res.name,"):", kwargs)
 
         return res
 # -----------------------------------------------------------------------------
@@ -1065,7 +991,7 @@ class geometry(Processing):
             res.shape = res.colorData.shape
 
         end = timer()
-        if pref.verboseProcessing: print(" [PROCESS-COMPUTE] (",end-start,")>> geometry(",res.name,"):", kwargs)
+        if pref.verbose: print(" [PROCESS-PROFILING] (",end-start,")>> geometry(",res.name,"):", kwargs)
 
         return res
 # -----------------------------------------------------------------------------
@@ -1075,21 +1001,36 @@ class geometry(Processing):
 # -----------------------------------------------------------------------------
 class ProcessPipe(object):
     """
-    TODO - Documentation de la classe ProcessPipe
+    class ProcessPipe: pipeline of process nodes
+        defines the pipeline of image procesing objects. 
     
     Attributes:
-        originalImage: TODO
-            TODO 
-        __inputImage: TODO
-            TODO
-        __outputImage: TODO
-            TODO
-        processNodes: TODO
-            TODO
-        previewHDR: boolean
-            TODO
-        previewHDR_process: TODO
-            TODO
+        originalImage (hdrCore.image.Image):
+        __inputImage (hdrCore.image.Image):
+        __outputImage (hdrCore.image.Image):
+        processNodes ([ProcessNode]):
+        previewHDR (bool):
+        previewHDR_process ():
+
+    Class Attributes:
+        autoResize (boolean): True resize automatically image for faster computation
+        maxSize (int): 
+        maxWorking (int):       
+
+    Methods:
+        append:                 (int) append a process node (ProcessNode) to process pipe (self)
+        getName:                (str) return image name associated to processpipe
+        setImage:               ()
+        getInputImage           ()
+        compute                 ()
+        setParameters           ()
+        getParameters           ()
+        getProcessNodeByName    ()
+        __repr__                (str)
+        __str__                 (str)
+        updateProcessPipeMetadata ()
+        updateHDRuseCase        ()
+        export                  ()
     """
     
     # autoresizing for fast computation
@@ -1101,6 +1042,23 @@ class ProcessPipe(object):
     # --- Class ProcessNode --------------------------------------------------
     # -------------------------------------------------------------------------
     class ProcessNode(object):
+        """encapsulates a Processing object to create processpipe
+
+        Attributes:
+            name (str): name of ProcessNode
+            process (hdrCore.processing.Processing):
+            params(dict):
+            defaultParams (dict):
+            requireUpdate (bool):
+            outputImage (hdrCore.image.Image):   
+            
+        Methods:
+            compute 
+            condCompute
+            setParameters
+            getParameters
+            toDict
+        """
 
         
         id=0
@@ -1181,12 +1139,10 @@ class ProcessPipe(object):
         return len(self.processNodes)-1 # return index of process (list[index])
 
     def getName(self):
-        """
-        TODO - Documentation de la méthode getName
+        """return name of input image.
         
         Returns:
-            TODO
-                TODO
+            (str)
         """
         return self.__outputImage.name
 
@@ -1231,7 +1187,7 @@ class ProcessPipe(object):
 
             elif pref.computation == 'numba':
                 start = timer()
-                img.colorData =     numbafun.numba_cctf_sRGB_decoding(img.colorData) # encode to prime
+                img.colorData= numbafun.numba_cctf_sRGB_decoding(img.colorData)
                 img.linear =        True
 
             elif pref.computation == 'cuda':
@@ -1240,7 +1196,6 @@ class ProcessPipe(object):
                 img.linear =        True
 
             dt = timer() - start
-            print("############# ProcessPipe.setImage: cctf_decoding (",pref.computation,"):",dt)
 
         # input image is set as __inputImage
         self.__inputImage = img
@@ -1261,13 +1216,17 @@ class ProcessPipe(object):
                     if idProcess != -1:
                         self.setParameters(idProcess,param)
 
-    def getInputImage(self):
+    def setOutput(self, img):
+        """setOuput: set the output image
         """
-        TODO - Documentation de la méthode getInputImage
+        self.__outputImage = copy.deepcopy(img)
+        pass
+
+    def getInputImage(self):
+        """return input image
         
         Returns:
-            TODO
-                TODO
+            (hdrCore.image.Image)
         """
         return self.__inputImage
 
@@ -1290,46 +1249,47 @@ class ProcessPipe(object):
                 self.__outputImage.colorData = colour.cctf_encoding(self.__outputImage.colorData, function='sRGB')
                 self.__outputImage.linear =  False
 
-                if pref.verbose: print(" >> ProcessPipe.getImage(",self.__outputImage.name,", toneMap:",toneMap,"): encode to sRGB !")
+                if pref.verbose: print(" [PROCESS] >> ProcessPipe.getImage(",self.__outputImage.name,", toneMap:",toneMap,"): encode to sRGB !")
 
             elif self.__outputImage.isHDR() and self.__outputImage.linear and toneMap:
                 self.__outputImage.colorData = colour.cctf_encoding(self.__outputImage.colorData, function='sRGB')
                 self.__outputImage.linear =  False
 
-                if pref.verbose: print(" >> ProcessPipe.getImage(",self.__outputImage.name,", ,toneMap:",toneMap,"): tone map using cctf encoding !")
+                if pref.verbose: print(" [PROCESS] >> ProcessPipe.getImage(",self.__outputImage.name,", ,toneMap:",toneMap,"): tone map using cctf encoding !")
 
             elif self.__outputImage.isHDR() and (not self.__outputImage.linear) and (not toneMap):
                 self.__outputImage.colorData = colour.cctf_decoding(self.__outputImage.colorData, function='sRGB')
                 self.__outputImage.linear =  True
 
-                if pref.verbose: print(" >> ProcessPipe.getImage(",self.__outputImage.name,", toneMap:",toneMap,"): decoding to linear colorspace !")
+                if pref.verbose: print(" [PROCESS] >> ProcessPipe.getImage(",self.__outputImage.name,", toneMap:",toneMap,"): decoding to linear colorspace !")
 
             elif (not self.__outputImage.linear) and (not toneMap):
                 self.__outputImage.colorData = colour.cctf_decoding(self.__outputImage.colorData, function='sRGB')
                 self.__outputImage.linear =  True
 
-                if pref.verbose: print(" >> ProcessPipe.getImage(",self.__outputImage.name,", toneMap:",toneMap,"): decoding to linear colorspace !")
+                if pref.verbose: print(" [PROCESS] >> ProcessPipe.getImage(",self.__outputImage.name,", toneMap:",toneMap,"): decoding to linear colorspace !")
 
             else:
-                if pref.verbose: print(">> ProcessPipe.getImage(",self.__outputImage.name,", toneMap:",toneMap,"): just return output !")
+                if pref.verbose: print(" [PROCESS] >> ProcessPipe.getImage(",self.__outputImage.name,", toneMap:",toneMap,"): just return output !")
 
             return self.__outputImage
         else: return None
 
     def compute(self,progress=None):
-        """
-        TODO - Documentation de la méthode compute
+        """compute the processpipe
 
         Args:
-            progress: TODO
-                TODO
+            progress: (object with showMessage and repaint method) object used to display progress
+
+        Returns:
+                
         """
         if self.__inputImage:
 
             if len(self.processNodes)>0: 
                 # first node
                 if progress:
-                    progress.showMessage('comuting: '+self.processNodes[0].name+' start!')
+                    progress.showMessage('computing: '+self.processNodes[0].name+' start!')
                     progress.repaint()
                 self.processNodes[0].condCompute(self.__inputImage)
                 if progress:
@@ -1443,18 +1403,18 @@ class ProcessPipe(object):
         if isinstance(self.__inputImage,image.Image):   self.__inputImage.metadata.metadata['processpipe'] =    copy.deepcopy(ppMeta)
         if isinstance(self.__outputImage,image.Image):  self.__outputImage.metadata.metadata['processpipe'] =   copy.deepcopy(ppMeta)
 
-    def updateHDRuseCase(self,hdrmeta):
+    def updateUserMeta(self,tagRootName,meta):
         """
-        TODO - Documentation de la méthode updateHDRuseCase
+        TODO - Documentation de la méthode updateUserMeta
 
         Args:
             hdrmeta: TODO
                 TODO
         """
-        if pref.verbose: print(" [PROCESS] >> ProcessPipe.updateHDRuseCase(",")")
-        if isinstance(self.originalImage,image.Image):  self.originalImage.metadata.metadata['hdr-use-case'] =   copy.deepcopy(hdrmeta)
-        if isinstance(self.__inputImage,image.Image):   self.__inputImage.metadata.metadata['hdr-use-case'] =    copy.deepcopy(hdrmeta)
-        if isinstance(self.__outputImage,image.Image):  self.__outputImage.metadata.metadata['hdr-use-case'] =   copy.deepcopy(hdrmeta)
+        if pref.verbose: print(" [PROCESS] >> ProcessPipe.updateUserMeta(",")")
+        if isinstance(self.originalImage,image.Image):  self.originalImage.metadata.metadata[tagRootName] =   copy.deepcopy(meta)
+        if isinstance(self.__inputImage,image.Image):   self.__inputImage.metadata.metadata[tagRootName] =    copy.deepcopy(meta)
+        if isinstance(self.__outputImage,image.Image):  self.__outputImage.metadata.metadata[tagRootName] =   copy.deepcopy(meta)
 
     def export(self,dirName,size=None,to=None,progress=None):
         """
@@ -1483,6 +1443,8 @@ class ProcessPipe(object):
         self.setImage(img)
 
         self.compute(progress=progress)
+        ###### res = hdrCore.coreC.coreCcompute(img, self)
+
         res = self.getImage(toneMap=False)
         res = res.process(clip())
 

+ 2 - 70
uHDR/hdrCore/quality.py

@@ -14,80 +14,12 @@
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 
-# -----------------------------------------------------------------------------
-# --- VERSION TRACKING --------------------------------------------------------
-# -----------------------------------------------------------------------------
-
-# -----------------------------------------------------------------------------
-# (1): DEBUG
-#      HDRImageViewer doesn't open 
-#      add time.sleep() in HDRviewerController.displayFile(...)
-#      ---- Rémi Cozot, June 2021
-# -----------------------------------------------------------------------------
-# (2): Prevent Freezing
-#      Add multithreading in:
-#      i  - processpipe processing (except for export and display)
-#      ii - loading images
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (3): Prevent Freezing and speed up
-#      Add multithreading in:
-#      i  - processpipe processing for HDR display
-#      ii - processpipe procssing export single image to vesaHDR1000
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (4): Fix ev value and accuracy in color editor 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (5): Fix Y curve plot 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (6): Ability to export SDR (.jpg) image to HDR (tone expansion).
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-
-
 # -----------------------------------------------------------------------------
 # --- Package hdrCore ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
-package hdrCore consists of the core classes for HDR imaging:
-(1) classes:    class imageType
-                class channel
-                class Image
-                class ColorSpace 
-                class Histogram
-(2) class:      class metadata
-(3) processing functions and classes:
-                def XYZ_to_sRGB(XYZ,apply_cctf_encoding=True):
-                def sRGB_to_XYZ(RGB,apply_cctf_decoding=True):  
-                def Lab_to_XYZ(Lab)
-                def XYZ_to_Lab(XYZ)
-                def Lab_to_sRGB(Lab, apply_cctf_encoding=True, clip = False)
-                def sRGB_to_Lab(RGB, apply_cctf_decoding=True)
-                def Lch_to_sRGB(Lch,apply_cctf_encoding=True, clip=False)   
-                class Processing(object)
-                class tmo_cctf(Processing)
-                class exposure(Processing)
-                class contrast(Processing)
-                class clip(Processing)
-                class ColorSpaceTransform(Processing)
-                class resize(Processing)
-                class Ycurve(Processing)
-                class saturation(Processing)
-                class lightnessMask(Processing)
-                class geometry(Processing)
-(4) processing nodes that encapsulate processing objects in process-pipe: 
-                class ProcessNode(object)
-(5) process-pipe that defines the pipeline of processing:
-                class ProcessPipe(object)
-(6) some utils functions:
-                def filenamesplit(filename)
-                def filterlistdir(path,extList)
-                def ndarray2vector(nda)
-                def NPlinearWeightMask(x,xMin,xMax,xTolerance)
-                def croppRotated(h,w,alpha)
-(7) HDRdisplay model
+package hdrCore consists of the core classes for HDR imaging.
+
 """
 
 

+ 7 - 74
uHDR/hdrCore/utils.py

@@ -14,80 +14,12 @@
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 
-# -----------------------------------------------------------------------------
-# --- VERSION TRACKING --------------------------------------------------------
-# -----------------------------------------------------------------------------
-
-# -----------------------------------------------------------------------------
-# (1): DEBUG
-#      HDRImageViewer doesn't open 
-#      add time.sleep() in HDRviewerController.displayFile(...)
-#      ---- Rémi Cozot, June 2021
-# -----------------------------------------------------------------------------
-# (2): Prevent Freezing
-#      Add multithreading in:
-#      i  - processpipe processing (except for export and display)
-#      ii - loading images
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (3): Prevent Freezing and speed up
-#      Add multithreading in:
-#      i  - processpipe processing for HDR display
-#      ii - processpipe procssing export single image to vesaHDR1000
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (4): Fix ev value and accuracy in color editor 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (5): Fix Y curve plot 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (6): Ability to export SDR (.jpg) image to HDR (tone expansion).
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-
-
 # -----------------------------------------------------------------------------
 # --- Package hdrCore ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
-package hdrCore consists of the core classes for HDR imaging:
-(1) classes:    class imageType
-                class channel
-                class Image
-                class ColorSpace 
-                class Histogram
-(2) class:      class metadata
-(3) processing functions and classes:
-                def XYZ_to_sRGB(XYZ,apply_cctf_encoding=True):
-                def sRGB_to_XYZ(RGB,apply_cctf_decoding=True):  
-                def Lab_to_XYZ(Lab)
-                def XYZ_to_Lab(XYZ)
-                def Lab_to_sRGB(Lab, apply_cctf_encoding=True, clip = False)
-                def sRGB_to_Lab(RGB, apply_cctf_decoding=True)
-                def Lch_to_sRGB(Lch,apply_cctf_encoding=True, clip=False)   
-                class Processing(object)
-                class tmo_cctf(Processing)
-                class exposure(Processing)
-                class contrast(Processing)
-                class clip(Processing)
-                class ColorSpaceTransform(Processing)
-                class resize(Processing)
-                class Ycurve(Processing)
-                class saturation(Processing)
-                class lightnessMask(Processing)
-                class geometry(Processing)
-(4) processing nodes that encapsulate processing objects in process-pipe: 
-                class ProcessNode(object)
-(5) process-pipe that defines the pipeline of processing:
-                class ProcessPipe(object)
-(6) some utils functions:
-                def filenamesplit(filename)
-                def filterlistdir(path,extList)
-                def ndarray2vector(nda)
-                def NPlinearWeightMask(x,xMin,xMax,xTolerance)
-                def croppRotated(h,w,alpha)
-(7) HDRdisplay model
+package hdrCore consists of the core classes for HDR imaging.
+
 """
 
 # -----------------------------------------------------------------------------
@@ -232,8 +164,9 @@ def croppRotated(h,w,alpha):
 # ------------------------------------------------------------------------------------------
 # ---- Constants ---------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
-HDRdisplay = {
-    'none' :                {'scaling':1,  'post':'',                       'tag':None},
-    'vesaDisplayHDR1000' :  {'scaling':12, 'post':'_vesa_DISPLAY_HDR_1000', 'tag':'vesaDisplayHDR1000'}
-    }
+#######################
+#HDRdisplay = {
+#    'none' :                {'scaling':1,  'post':'',                       'tag':None},
+#    'vesaDisplayHDR1000' :  {'scaling':12, 'post':'_vesa_DISPLAY_HDR_1000', 'tag':'vesaDisplayHDR1000'}
+#    }
 # ------------------------------------------------------------------------------------------

+ 126 - 42
uHDR/preferences/preferences.py

@@ -14,62 +14,98 @@
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 
-# -----------------------------------------------------------------------------
-# --- VERSION TRACKING --------------------------------------------------------
-# -----------------------------------------------------------------------------
-
-# -----------------------------------------------------------------------------
-# (1): DEBUG
-#      HDRImageViewer doesn't open 
-#      add time.sleep() in HDRviewerController.displayFile(...)
-#      ---- Rémi Cozot, June 2021
-# -----------------------------------------------------------------------------
-# (2): Prevent Freezing
-#      Add multithreading in:
-#      i  - processpipe processing (except for export and display)
-#      ii - loading images
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (3): Prevent Freezing and speed up
-#      Add multithreading in:
-#      i  - processpipe processing for HDR display
-#      ii - processpipe procssing export single image to vesaHDR1000
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (4): Fix ev value and accuracy in color editor 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (5): Fix Y curve plot 
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-# (6): Ability to export SDR (.jpg) image to HDR (tone expansion).
-#      ---- Rémi Cozot, July 2021
-# -----------------------------------------------------------------------------
-
-
 # -----------------------------------------------------------------------------
 # --- Package preferences -----------------------------------------------------
 # -----------------------------------------------------------------------------
 """
-package preferences contains all global variables that stup the preferences:
-    computation: 'python','numba','cuda'
+package preferences contains all global variables that stup the preferences.
+
 """
 
 # -----------------------------------------------------------------------------
 # --- Import ------------------------------------------------------------------
 # -----------------------------------------------------------------------------
-import numba
-import numpy as np
+# RCZT 2023
+# import numba, json, os, copy
+import numpy as np, json, os
 
 # -----------------------------------------------------------------------------
 # --- Preferences -------------------------------------------------------------
 # -----------------------------------------------------------------------------
 target = ['python','numba','cuda']
-computation = target[1]
-verbose = False
-verboseProcessing = True
-# -----------------------------------------------------------------------------
-# --- Functions ---------------------------------------------------------------
+computation = target[0]
+# verbose mode: print function call 
+#   usefull for debug
+verbose = True
+# list of HDR display takien into account
+#   red from prefs.json file
+#   display info:
+#   "vesaDisplayHDR1000":                           << display tag name
+#       {
+#           "shape": [ 2160, 3840 ],                << display shape (4K)
+#           "scaling": 12,                          << color space scaling to max
+#           "post": "_vesa_DISPLAY_HDR_1000",       << postfix add when exporting file
+#           "tag": "vesaDisplayHDR1000"             << tag name
+#       }
+HDRdisplays = None
+# current HDR display: tag name in above list
+HDRdisplay = None
+# image size when editing image: 
+#   small size = quick computation, no memory issues
+maxWorking = 1200
+# last image directory path
+imagePath ="."
+# keep all metadata
+keepAllMeta = False
+# -----------------------------------------------------------------------------
+# --- Functions preferences --------------------------------------------------
+# -----------------------------------------------------------------------------
+def loadPref(): 
+    """load preferences file: prefs.json
+
+            Args:
+
+            Returns (Dict)
+        
+    """
+    with open('./preferences/prefs.json') as f: return  json.load(f)
+# -----------------------------------------------------------------------------
+def savePref():
+    global HDRdisplays
+    global HDRdisplay
+    global imagePath
+    pUpdate = {
+            "HDRdisplays" : HDRdisplays,
+            "HDRdisplay"  : HDRdisplay,
+            "imagePath"   : imagePath
+        }
+    if verbose: print(" [PREF] >> savePref(",pUpdate,")")
+    with open('./preferences/prefs.json', "w") as f: json.dump(pUpdate,f)
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+# loading pref
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+print("uHDRv6: loading preferences")
+p = loadPref()
+if p :
+    HDRdisplays = p["HDRdisplays"]
+    HDRdisplay = p["HDRdisplay"]
+    imagePath = p["imagePath"]
+else:
+    HDRdisplays = {
+        'none' :                {'shape':(2160,3840), 'scaling':1,   'post':'',                          'tag': "none"},
+        'vesaDisplayHDR1000' :  {'shape':(2160,3840), 'scaling':12,  'post':'_vesa_DISPLAY_HDR_1000',    'tag':'vesaDisplayHDR1000'},
+        'vesaDisplayHDR400' :   {'shape':(2160,3840), 'scaling':4.8, 'post':'_vesa_DISPLAY_HDR_400',     'tag':'vesaDisplayHDR400'},
+        'HLG1' :                {'shape':(2160,3840), 'scaling':1,   'post':'_HLG_1',                    'tag':'HLG1'}
+        }
+    # current display
+    HDRdisplay = 'vesaDisplayHDR1000'
+    imagePath = '.'
+print(f"       target display: {HDRdisplay}")
+print(f"       image path: {imagePath}")
+# -----------------------------------------------------------------------------
+# --- Functions computation ---------------------------------------------------
 # -----------------------------------------------------------------------------
 def getComputationMode():
     """returns the preference computation mode: python, numba, cuda, ...
@@ -79,3 +115,51 @@ def getComputationMode():
         Returns (str)
     """
     return computation
+# -----------------------------------------------------------------------------
+# --- Functions HDR dispaly ---------------------------------------------------
+# -----------------------------------------------------------------------------
+def getHDRdisplays():
+    """returns the current display model
+
+    Args:
+
+    Returns (Dict)
+    """
+    return HDRdisplays
+# -----------------------------------------------------------------------------
+def getHDRdisplay():
+    """returns the current display model
+
+    Args:
+
+    Returns (Dict)
+    """
+    return HDRdisplays[HDRdisplay]
+# -----------------------------------------------------------------------------
+def setHDRdisplay(tag):
+    """set the HDR display
+
+        Args:
+            tag (str): tag of HDR display, must be a key of HDRdisplays
+
+        Returns:
+    """
+    global HDRdisplay
+    if tag in HDRdisplays: HDRdisplay =tag
+    savePref()
+# ----------------------------------------------------------------------------
+def getDisplayScaling():  return getHDRdisplay()['scaling']
+# ----------------------------------------------------------------------------
+def getDisplayShape():  return getHDRdisplay()['shape']
+# -----------------------------------------------------------------------------
+# --- Functions path ---------------------------------------------------
+# -----------------------------------------------------------------------------
+def getImagePath(): return imagePath if os.path.isdir(imagePath) else '.'
+# ----------------------------------------------------------------------------
+def setImagePath(path): 
+    global imagePath
+    imagePath = path
+    if verbose: print(" [PREF] >> setImagePath(",path,"):",imagePath)
+    savePref()
+# ----------------------------------------------------------------------------
+             

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
uHDR/preferences/prefs.json


+ 36 - 0
uHDR/preferences/tags.json

@@ -0,0 +1,36 @@
+{
+  "uHDR TAGS": [
+    {
+      "scoring": {
+        "*     (1)": null,
+        "**    (2)": null,
+        "***   (3)": null,
+        "****  (4)": null,
+        "***** (5)": null
+      }
+    },
+    {
+      "scene": {
+        "landscape": null,
+        "seaside" : null,
+        "cityscape": null,
+        "architecture": null,
+        "interiors" : null,
+        "still life": null,
+        "portrait": null,
+        "event": null
+      }
+    },
+
+    {
+      "light mood": {
+        "night": null,
+        "dark": null,
+        "soft (indoor)": null,
+        "soft (outdoor)": null,
+        "bright": null,
+        "very bright": null
+      }
+    }
+  ]
+}

BIN
uHDR/temp.hdr


+ 3 - 2
uHDR/uHDR.py

@@ -2,6 +2,8 @@
 # author: remi.cozot@univ-littoral.fr
 
 
+
+
 """Only contains main program.
 """
 
@@ -12,11 +14,10 @@ import guiQt.controller
 from multiprocessing import freeze_support
 
 import sys
-
 # ------------------------------------------------------------------------------------------
 if __name__ == '__main__':
     freeze_support()
-    print("uHDR")
+    print("uHDRv6 (C++ core)")
 
     app = QApplication(sys.argv)
 

+ 58 - 22
uHDR/uHDR.pyproj

@@ -2,15 +2,17 @@
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>49b3c98a-5adb-4366-a42b-9182ae5030c9</ProjectGuid>
-    <ProjectHome>.</ProjectHome>
+    <ProjectGuid>9efe56be-845d-4def-8416-b774b13cc63a</ProjectGuid>
+    <ProjectHome>
+    </ProjectHome>
     <StartupFile>uHDR.py</StartupFile>
     <SearchPath>
     </SearchPath>
     <WorkingDirectory>.</WorkingDirectory>
     <OutputPath>.</OutputPath>
     <Name>uHDR</Name>
-    <RootNamespace>uHDR</RootNamespace>
+    <RootNamespace>HwHDR</RootNamespace>
+    <SuppressEnvironmentCreationPrompt>True</SuppressEnvironmentCreationPrompt>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
     <DebugSymbols>true</DebugSymbols>
@@ -21,33 +23,67 @@
     <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
   </PropertyGroup>
   <ItemGroup>
-    <Compile Include="guiQt\controller.py" />
-    <Compile Include="guiQt\model.py" />
-    <Compile Include="guiQt\thread.py" />
-    <Compile Include="guiQt\view.py" />
-    <Compile Include="guiQt\view.useCase.py" />
-    <Compile Include="guiQt\__init__.py" />
-    <Compile Include="hdrCore\image.py" />
-    <Compile Include="hdrCore\metadata.py" />
-    <Compile Include="hdrCore\numbafun.py" />
-    <Compile Include="hdrCore\processing.py" />
-    <Compile Include="hdrCore\quality.py" />
-    <Compile Include="hdrCore\srgb.py" />
-    <Compile Include="hdrCore\utils.py" />
-    <Compile Include="hdrCore\__init__.py" />
-    <Compile Include="preferences\preferences.py" />
-    <Compile Include="preferences\__init__.py" />
+    <Compile Include="guiQt\controller.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="guiQt\model.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="guiQt\thread.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="guiQt\view.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="guiQt\__init__.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="hdrCore\aesthetics.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="hdrCore\coreC.py" />
+    <Compile Include="hdrCore\image.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="hdrCore\metadata.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="hdrCore\net.py" />
+    <Compile Include="hdrCore\numbafun.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="hdrCore\processing.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="hdrCore\quality.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="hdrCore\utils.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="hdrCore\__init__.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="preferences\preferences.py">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="preferences\__init__.py">
+      <SubType>Code</SubType>
+    </Compile>
     <Compile Include="uHDR.py" />
   </ItemGroup>
   <ItemGroup>
     <Folder Include="guiQt\" />
-    <Folder Include="guiQt\__pycache__\" />
     <Folder Include="hdrCore\" />
     <Folder Include="preferences\" />
   </ItemGroup>
   <ItemGroup>
-    <Content Include="guiQt\__pycache__\controller.cpython-39.pyc" />
-    <Content Include="guiQt\__pycache__\__init__.cpython-39.pyc" />
+    <Content Include="preferences\prefs.json">
+      <SubType>Code</SubType>
+    </Content>
+    <Content Include="preferences\tags.json">
+      <SubType>Code</SubType>
+    </Content>
   </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
   <!-- Uncomment the CoreCompile target to enable the Build command in