Rémi Synave il y a 6 mois
Parent
commit
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
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 # 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 ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
 """
-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 ------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 
 
-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
 import numpy as np
 # pyQT5 import
 # pyQT5 import
 from PyQt5.QtWidgets import QFileDialog, QApplication
 from PyQt5.QtWidgets import QFileDialog, QApplication
@@ -116,8 +34,14 @@ from PyQt5.QtWidgets import QMessageBox
 
 
 from . import model, view, thread
 from . import model, view, thread
 import hdrCore.image, hdrCore.processing, hdrCore.utils
 import hdrCore.image, hdrCore.processing, hdrCore.utils
+import hdrCore.coreC
 import preferences.preferences as pref
 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 ---------------------------------------------------------
 # --- package methods ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -301,7 +225,9 @@ class ImageGalleryController():
         return (nb*nbImagePage), ((nb+1)*nbImagePage)
         return (nb*nbImagePage), ((nb+1)*nbImagePage)
 
 
     def getFilenamesOfCurrentPage(self): return self.model.getFilenamesOfCurrentPage()
     def getFilenamesOfCurrentPage(self): return self.model.getFilenamesOfCurrentPage()
+
     def getProcessPipeById(self,i) : return self.model.getProcessPipeById(i)
     def getProcessPipeById(self,i) : return self.model.getProcessPipeById(i)
+
     def getProcessPipes(self): return self.model.processPipes
     def getProcessPipes(self): return self.model.processPipes
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 # --- Class AppController -----------------------------------------------------
 # --- Class AppController -----------------------------------------------------
@@ -316,17 +242,16 @@ class AppController(object):
             model
             model
             
             
         Methods:
         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.hdrDisplay = HDRviewerController(self)
         self.view =  view.AppView(self, HDRcontroller = self.hdrDisplay)                         
         self.view =  view.AppView(self, HDRcontroller = self.hdrDisplay)                         
         self.model = model.AppModel(self)
         self.model = model.AppModel(self)
+
+        self.dirName = None
+        self.imagesName = []
         
         
         self.view.show()
         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):
     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 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 callBackSave(self): self.view.imageGalleryController.save()
-
+    # -----------------------------------------------------------------------------
     def callBackQuit(self):
     def callBackQuit(self):
         if pref.verbose: print(" [CB] >> AppController.callBackQuit()")
         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):
     def callBackDisplayHDR(self):
+
         if pref.verbose:  print(" [CONTROL] >> AppController.callBackDisplayHDR()")
         if pref.verbose:  print(" [CONTROL] >> AppController.callBackDisplayHDR()")
 
 
         selectedProcessPipe = self.view.imageGalleryController.model.getSelectedProcessPipe()
         selectedProcessPipe = self.view.imageGalleryController.model.getSelectedProcessPipe()
@@ -404,17 +316,16 @@ class AppController(object):
             processpipe = copy.deepcopy(selectedProcessPipe)
             processpipe = copy.deepcopy(selectedProcessPipe)
 
 
             # set size to display size
             # set size to display size
-            size = self.hdrDisplay.model.displayModel['shape']
+            size = pref.getDisplayShape()
             img = img.process(hdrCore.processing.resize(),size=(None, size[1]))
             img = img.process(hdrCore.processing.resize(),size=(None, size[1]))
 
 
             # set image to process-pipe
             # set image to process-pipe
             processpipe.setImage(img)
             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()")
         if pref.verbose:  print(" [CONTROL] >> AppController.callBackEndDisplay()")
 
 
         # turn off: autoResize
         # turn off: autoResize
@@ -424,16 +335,15 @@ class AppController(object):
 
 
         # clip, scale
         # clip, scale
         img = img.process(hdrCore.processing.clip())
         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
         colour.write_image(img.colorData,"temp.hdr", method='Imageio') # local copy for display
         self.hdrDisplay.displayFile("temp.hdr")
         self.hdrDisplay.displayFile("temp.hdr")
-
+    # -----------------------------------------------------------------------------
     def callBackCloseDisplayHDR(self):
     def callBackCloseDisplayHDR(self):
         if pref.verbose: print(" [CONTROL] >> AppController.callBackCloseDisplayHDR()")
         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):
     def callBackCompareRawEditedHDR(self):
         """
         """
         Callback of compare raw/edited HDR menu
         Callback of compare raw/edited HDR menu
@@ -443,61 +353,60 @@ class AppController(object):
 
 
         if pref.verbose:  print(" [CONTROL] >> AppController.callBackCompareOriginalInputHDR()")
         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):
     def callBackExportHDR(self):
         """
         """
         Callback of export HDR menu
         Callback of export HDR menu
@@ -509,12 +418,15 @@ class AppController(object):
 
 
         selectedProcessPipe = self.view.imageGalleryController.model.getSelectedProcessPipe()
         selectedProcessPipe = self.view.imageGalleryController.model.getSelectedProcessPipe()
 
 
+
         if selectedProcessPipe:
         if selectedProcessPipe:
             # select dir where to save export
             # select dir where to save export
             self.dirName = QFileDialog.getExistingDirectory(None, 'Select Directory where to export HDR file', self.model.directory)
             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()
             self.view.statusBar().repaint()
+
             # save current processpipe metada
             # save current processpipe metada
             originalImage = copy.deepcopy(selectedProcessPipe.originalImage)
             originalImage = copy.deepcopy(selectedProcessPipe.originalImage)
             originalImage.metadata.metadata['processpipe'] = selectedProcessPipe.toDict()
             originalImage.metadata.metadata['processpipe'] = selectedProcessPipe.toDict()
@@ -531,43 +443,107 @@ class AppController(object):
             # set image to process-pipe
             # set image to process-pipe
             processpipe.setImage(img)
             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
         # turn off: autoResize
         hdrCore.processing.ProcessPipe.autoResize = True  
         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
         # clip, scale
         img = img.process(hdrCore.processing.clip())
         img = img.process(hdrCore.processing.clip())
-        img.colorData = img.colorData*hdrCore.utils.HDRdisplay['vesaDisplayHDR1000']['scaling']
+        img.colorData = img.colorData*pref.getDisplayScaling()
 
 
         if self.dirName:
         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.type = hdrCore.image.imageType.HDR
-            img.metadata = meta
             img.metadata.metadata['processpipe'] = None
             img.metadata.metadata['processpipe'] = None
-            img.metadata.metadata['display'] =hdrCore.utils.HDRdisplay['vesaDisplayHDR1000']['tag']
+            img.metadata.metadata['display'] = pref.getHDRdisplay()['tag']
 
 
             img.write(pathExport)
             img.write(pathExport)
 
 
         colour.write_image(img.colorData,"temp.hdr", method='Imageio') # local copy for display
         colour.write_image(img.colorData,"temp.hdr", method='Imageio') # local copy for display
         self.hdrDisplay.displayFile("temp.hdr")
         self.hdrDisplay.displayFile("temp.hdr")
-
+    # -----------------------------------------------------------------------------
     def callBackExportAllHDR(self):
     def callBackExportAllHDR(self):
         if pref.verbose:  print(" [CONTROL] >> AppController.callBackExportAllHDR()")
         if pref.verbose:  print(" [CONTROL] >> AppController.callBackExportAllHDR()")
 
 
-        processPipes = self.view.imageGalleryController.getProcessPipes()
+        self.processPipes = self.view.imageGalleryController.getProcessPipes()
 
 
         # select dir where to save export
         # 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.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() ----------------------------------------------------------
 # --- class MultiDockController() ----------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
@@ -578,25 +554,20 @@ class MultiDockController():
         self.parent = parent
         self.parent = parent
         self.view = view.MultiDockView(self, HDRcontroller)
         self.view = view.MultiDockView(self, HDRcontroller)
         self.model = None
         self.model = None
-
-
+    # ---------------------------------------------------------------------------------------
     def activateEDIT(self): self.switch(0)
     def activateEDIT(self): self.switch(0)
     def activateINFO(self): self.switch(1)
     def activateINFO(self): self.switch(1)
-    def activateIQA(self):  self.switch(2)
-
+    def activateMIAM(self):  self.switch(2)
+    # ---------------------------------------------------------------------------------------
     def switch(self,nb):
     def switch(self,nb):
         if pref.verbose:  print(" [CONTROL] >> MultiDockController.switch()")
         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): 
     def setProcessPipe(self, processPipe): 
         if pref.verbose: print(" [CONTROL] >> MultiDockController.setProcessPipe(",processPipe.getImage().name,")")
         if pref.verbose: print(" [CONTROL] >> MultiDockController.setProcessPipe(",processPipe.getImage().name,")")
 
 
         return self.view.setProcessPipe(processPipe)
         return self.view.setProcessPipe(processPipe)
-
-    def assessmentRunning(self):
-        return self.view.childControllers[2].assessmentRunning()
+# ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class EditImageController:
 class EditImageController:
@@ -611,7 +582,7 @@ class EditImageController:
 
 
         self.view = view.EditImageView(self)
         self.view = view.EditImageView(self)
         self.model = model.EditImageModel(self)
         self.model = model.EditImageModel(self)
-
+    # -----------------------------------------------------------------------------
     def setProcessPipe(self, processPipe): 
     def setProcessPipe(self, processPipe): 
         if pref.verbose: print(" [CONTROL] >> EditImageController.setProcessPipe(",")")
         if pref.verbose: print(" [CONTROL] >> EditImageController.setProcessPipe(",")")
 
 
@@ -629,14 +600,16 @@ class EditImageController:
             return True
             return True
         else:
         else:
             return False
             return False
-
+    # -----------------------------------------------------------------------------
+    def getProcessPipe(self) : return self.model.getProcessPipe()
+    # -----------------------------------------------------------------------------
     def buildView(self,processPipe=None):
     def buildView(self,processPipe=None):
         if pref.verbose: print(" [CONTROL] >> EditImageController.buildView(",")")
         if pref.verbose: print(" [CONTROL] >> EditImageController.buildView(",")")
 
 
         """ called when MultiDockController recall a controller/view """
         """ called when MultiDockController recall a controller/view """
         self.view = view.EditImageView(self, build=True)
         self.view = view.EditImageView(self, build=True)
         if processPipe: self.setProcessPipe(processPipe)
         if processPipe: self.setProcessPipe(processPipe)
-
+    # -----------------------------------------------------------------------------
     def autoExposure(self): 
     def autoExposure(self): 
         if pref.verbose: print(" [CONTROL] >> EditImageController.autoExposure(",")")
         if pref.verbose: print(" [CONTROL] >> EditImageController.autoExposure(",")")
         if self.model.processpipe:
         if self.model.processpipe:
@@ -649,35 +622,35 @@ class EditImageController:
 
 
             qPixmap =  self.view.setImage(img)
             qPixmap =  self.view.setImage(img)
             self.parent.controller.parent.controller.view.imageGalleryController.setProcessPipeWidgetQPixmap(qPixmap)
             self.parent.controller.parent.controller.view.imageGalleryController.setProcessPipeWidgetQPixmap(qPixmap)
-
+    # -----------------------------------------------------------------------------
     def changeExposure(self,value):
     def changeExposure(self,value):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeExposure(",value,")")
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeExposure(",value,")")
         if self.model.processpipe: self.model.changeExposure(value)
         if self.model.processpipe: self.model.changeExposure(value)
-
+    # -----------------------------------------------------------------------------
     def changeContrast(self,value):
     def changeContrast(self,value):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeContrast(",value,")")
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeContrast(",value,")")
         if self.model.processpipe: self.model.changeContrast(value)
         if self.model.processpipe: self.model.changeContrast(value)
-
+    # -----------------------------------------------------------------------------
     def changeToneCurve(self,controlPoints):
     def changeToneCurve(self,controlPoints):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeToneCurve("")")
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeToneCurve("")")
         if self.model.processpipe: self.model.changeToneCurve(controlPoints)
         if self.model.processpipe: self.model.changeToneCurve(controlPoints)
-
+    # -----------------------------------------------------------------------------
     def changeLightnessMask(self, maskValues):
     def changeLightnessMask(self, maskValues):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeLightnessMask(",maskValues,")")
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeLightnessMask(",maskValues,")")
         if self.model.processpipe: self.model.changeLightnessMask(maskValues)
         if self.model.processpipe: self.model.changeLightnessMask(maskValues)
-
+    # -----------------------------------------------------------------------------
     def changeSaturation(self,value):
     def changeSaturation(self,value):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeSaturation(",value,")")
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeSaturation(",value,")")
         if self.model.processpipe: self.model.changeSaturation(value)
         if self.model.processpipe: self.model.changeSaturation(value)
-
+    # -----------------------------------------------------------------------------
     def changeColorEditor(self,values, idName):
     def changeColorEditor(self,values, idName):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeColorEditor(",values,")")
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeColorEditor(",values,")")
         if self.model.processpipe: self.model.changeColorEditor(values, idName)
         if self.model.processpipe: self.model.changeColorEditor(values, idName)
-
+    # -----------------------------------------------------------------------------
     def changeGeometry(self,values):
     def changeGeometry(self,values):
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeGeometry(",values,")")
         if pref.verbose: print(" [CONTROL] >> EditImageController.changeGeometry(",values,")")
         if self.model.processpipe: self.model.changeGeometry(values)
         if self.model.processpipe: self.model.changeGeometry(values)
-
+    # -----------------------------------------------------------------------------
     def updateImage(self,imgTM):
     def updateImage(self,imgTM):
         """
         """
         updateImage: called when process-pipe computation is done
         updateImage: called when process-pipe computation is done
@@ -689,9 +662,14 @@ class EditImageController:
         self.parent.controller.parent.controller.view.imageGalleryController.setProcessPipeWidgetQPixmap(qPixmap)
         self.parent.controller.parent.controller.view.imageGalleryController.setProcessPipeWidgetQPixmap(qPixmap)
         self.view.plotToneCurve()
         self.view.plotToneCurve()
 
 
+        # if aesthetics model > notify required update
+
+
         if self.previewHDR and self.model.autoPreviewHDR:
         if self.previewHDR and self.model.autoPreviewHDR:
             self.controllerHDR.callBackUpdate()
             self.controllerHDR.callBackUpdate()
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class ImageInfoController:
 class ImageInfoController:
 
 
     def __init__(self, parent=None):
     def __init__(self, parent=None):
@@ -702,133 +680,25 @@ class ImageInfoController:
         self.model = model.ImageInfoModel(self)
         self.model = model.ImageInfoModel(self)
 
 
         self.callBackActive = True
         self.callBackActive = True
-
+    # -----------------------------------------------------------------------------
     def setProcessPipe(self, processPipe): 
     def setProcessPipe(self, processPipe): 
         if pref.verbose: print(" [CONTROL] >> ImageInfoController.setProcessPipe(",processPipe.getImage().name,")")
         if pref.verbose: print(" [CONTROL] >> ImageInfoController.setProcessPipe(",processPipe.getImage().name,")")
         self.model.setProcessPipe(processPipe)
         self.model.setProcessPipe(processPipe)
-        #### self.view.setImage(processPipe.getImage())
         self.view.setProcessPipe(processPipe)
         self.view.setProcessPipe(processPipe)
         return True
         return True
-
+    # -----------------------------------------------------------------------------
     def buildView(self,processPipe=None):
     def buildView(self,processPipe=None):
         if pref.verbose: print(" [CONTROL] >> ImageInfoController.buildView()")
         if pref.verbose: print(" [CONTROL] >> ImageInfoController.buildView()")
 
 
         """ called when MultiDockController recall a controller/view """
         """ called when MultiDockController recall a controller/view """
         self.view = view.ImageInfoView(self)
         self.view = view.ImageInfoView(self)
         if processPipe: self.setProcessPipe(processPipe)
         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():
 class AdvanceSliderController():
     def __init__(self, parent,name, defaultValue, range, step,callBackValueChange=None,callBackAutoPush= None):
     def __init__(self, parent,name, defaultValue, range, step,callBackValueChange=None,callBackAutoPush= None):
@@ -845,7 +715,7 @@ class AdvanceSliderController():
         self.callBackActive = True
         self.callBackActive = True
         self.callBackValueChange = callBackValueChange
         self.callBackValueChange = callBackValueChange
         self.callBackAutoPush = callBackAutoPush
         self.callBackAutoPush = callBackAutoPush
-
+    # -----------------------------------------------------------------------------
     def sliderChange(self):
     def sliderChange(self):
 
 
         value = self.view.slider.value()*self.step
         value = self.view.slider.value()*self.step
@@ -855,29 +725,31 @@ class AdvanceSliderController():
         self.model.value = value
         self.model.value = value
         self.view.editValue.setText(str(value))
         self.view.editValue.setText(str(value))
         if self.callBackActive and self.callBackValueChange: self.callBackValueChange(value)
         if self.callBackActive and self.callBackValueChange: self.callBackValueChange(value)
-
+    # -----------------------------------------------------------------------------
     def setValue(self, value, callBackActive = True):
     def setValue(self, value, callBackActive = True):
         if pref.verbose: print(" [CONTROL] >> AdvanceSliderController.setValue(",value,") ")
         if pref.verbose: print(" [CONTROL] >> AdvanceSliderController.setValue(",value,") ")
 
 
         """ set value value in 'model' range"""
         """ set value value in 'model' range"""
         self.callBackActive = callBackActive
         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.view.editValue.setText(str(value))
-        self.model.setValue(value)
+        self.model.setValue(int(value))
 
 
         self.callBackActive = True
         self.callBackActive = True
-
+    # -----------------------------------------------------------------------------
     def reset(self):
     def reset(self):
         if pref.verbose : print(" [CB] >> AdvanceSliderController.reset(",") ")
         if pref.verbose : print(" [CB] >> AdvanceSliderController.reset(",") ")
 
 
         self.setValue(self.defaultValue,callBackActive = False)
         self.setValue(self.defaultValue,callBackActive = False)
         if self.callBackValueChange: self.callBackValueChange(self.defaultValue)
         if self.callBackValueChange: self.callBackValueChange(self.defaultValue)
-
+    # -----------------------------------------------------------------------------
     def auto(self):
     def auto(self):
         if pref.verbose: print(" [CB] >> AdvanceSliderController.auto(",") ")
         if pref.verbose: print(" [CB] >> AdvanceSliderController.auto(",") ")
 
 
         if self.callBackAutoPush: self.callBackAutoPush()
         if self.callBackAutoPush: self.callBackAutoPush()
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
+# --- class AdvanceSliderController --------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class ToneCurveController():
 class ToneCurveController():
     def __init__(self, parent):
     def __init__(self, parent):
 
 
@@ -889,8 +761,14 @@ class ToneCurveController():
         # tone curve display control
         # tone curve display control
         self.showInput =False
         self.showInput =False
         self.showbefore = 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):
     def sliderChange(self, key, value):
         if pref.verbose: print(" [CB] >> ToneCurveController.sliderChange(",key,",",value,")[callBackActive:",self.callBackActive,"] ")
         if pref.verbose: print(" [CB] >> ToneCurveController.sliderChange(",key,",",value,")[callBackActive:",self.callBackActive,"] ")
 
 
@@ -902,23 +780,23 @@ class ToneCurveController():
 
 
             self.callBackActive =  False
             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.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.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.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.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.view.editHighlights.setText(str(newValues["highlights"][1]))
 
 
             self.callBackActive =  True
             self.callBackActive =  True
-
+    # -----------------------------------------------------------------------------
     def setValues(self, valuesDict,callBackActive = False):
     def setValues(self, valuesDict,callBackActive = False):
         if pref.verbose: print(" [CONTROL] >> ToneCurveController.setValue(",valuesDict,") ")
         if pref.verbose: print(" [CONTROL] >> ToneCurveController.setValue(",valuesDict,") ")
 
 
@@ -927,23 +805,58 @@ class ToneCurveController():
         self.model.setValues(valuesDict)
         self.model.setValues(valuesDict)
         points = self.model.evaluate()
         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.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.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.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.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.view.editHighlights.setText(str(valuesDict["highlights"][1]))
 
 
         self.callBackActive = True
         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):
     def reset(self, key):
         if pref.verbose: print(" [CONTROL] >> ToneCurveController.reset(",key,") ")
         if pref.verbose: print(" [CONTROL] >> ToneCurveController.reset(",key,") ")
 
 
@@ -952,50 +865,64 @@ class ToneCurveController():
         self.setValues(controls,callBackActive = False)
         self.setValues(controls,callBackActive = False)
 
 
         self.parent.controller.changeToneCurve(controls) 
         self.parent.controller.changeToneCurve(controls) 
-
+    # -----------------------------------------------------------------------------
     def plotCurve(self):
     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():
 class LightnessMaskController():
     def __init__(self, parent):
     def __init__(self, parent):
@@ -1006,13 +933,13 @@ class LightnessMaskController():
         self.view = view.LightnessMaskView(self)
         self.view = view.LightnessMaskView(self)
 
 
         self.callBackActive = True
         self.callBackActive = True
-
+    # -----------------------------------------------------------------------------
     def maskChange(self,key, on_off):
     def maskChange(self,key, on_off):
         if pref.verbose: print(" [CB] >> MaskLightnessController.maskChange(",key,",",on_off,")[callBackActive:",self.callBackActive,"] ")
         if pref.verbose: print(" [CB] >> MaskLightnessController.maskChange(",key,",",on_off,")[callBackActive:",self.callBackActive,"] ")
 
 
         maskState = self.model.maskChange(key, on_off)  
         maskState = self.model.maskChange(key, on_off)  
         self.parent.controller.changeLightnessMask(maskState) 
         self.parent.controller.changeLightnessMask(maskState) 
-
+    # -----------------------------------------------------------------------------
     def setValues(self, values,callBackActive = False):
     def setValues(self, values,callBackActive = False):
         if pref.verbose: print(" [CONTROL] >> LightnessMaskController.setValue(",values,") ")
         if pref.verbose: print(" [CONTROL] >> LightnessMaskController.setValue(",values,") ")
 
 
@@ -1028,6 +955,8 @@ class LightnessMaskController():
 
 
         self.callBackActive = True
         self.callBackActive = True
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class HDRviewerController():
 class HDRviewerController():
     def __init__(self, parent):
     def __init__(self, parent):
         if pref.verbose: print(" [CONTROL] >> HDRviewerController.__init__(",")")
         if pref.verbose: print(" [CONTROL] >> HDRviewerController.__init__(",")")
@@ -1132,6 +1061,8 @@ class HDRviewerController():
             subprocess.run(['taskkill', '/F', '/T', '/IM', "HDRImageViewer*"],capture_output=False)
             subprocess.run(['taskkill', '/F', '/T', '/IM', "HDRImageViewer*"],capture_output=False)
             self.viewerProcess = None
             self.viewerProcess = None
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
+# ---- Class LchColorSelectorController ----------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class LchColorSelectorController:
 class LchColorSelectorController:
     def __init__(self, parent, idName = None):
     def __init__(self, parent, idName = None):
         if pref.verbose: print(" [CONTROL] >> LchColorSelectorController.__init__(",") ")
         if pref.verbose: print(" [CONTROL] >> LchColorSelectorController.__init__(",") ")
@@ -1181,8 +1112,8 @@ class LchColorSelectorController:
         self.callBackActive = callBackActive
         self.callBackActive = callBackActive
         # slider hue selection
         # slider hue selection
         v = values['selection']['hue'] if 'hue' in values['selection'].keys() else (0,360)
         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
         # slider chroma selection
         v = values['selection']['chroma'] if 'chroma' in values['selection'].keys() else (0,100)
         v = values['selection']['chroma'] if 'chroma' in values['selection'].keys() else (0,100)
@@ -1191,39 +1122,64 @@ class LchColorSelectorController:
 
 
         # slider lightness
         # slider lightness
         v = values['selection']['lightness'] if 'lightness' in values['selection'].keys() else (0,100)
         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
         # hue shift editor
         v = values['edit']['hue'] if 'hue' in values['edit'].keys() else 0
         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)) 
         self.view.valueHueShift.setText(str(v)) 
 
 
         # exposure editor
         # 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)) 
         self.view.valueExposure.setText(str(v)) 
 
 
         # contrast editor
         # 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))  
         self.view.valueContrast.setText(str(v))  
 
 
         # saturation editor
         # 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))  
         self.view.valueSaturation.setText(str(v))  
 
 
         # mask
         # 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.view.checkboxMask.setChecked(values['mask'])             
 
 
         self.model.setValues(values)
         self.model.setValues(values)
 
 
         self.callBackActive = True
         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:
 class GeometryController:
-    def __init__(self, parent, ):
+    def __init__(self, parent ):
         if pref.verbose: print(" [CONTROL] >> GeometryController.__init__(",") ")
         if pref.verbose: print(" [CONTROL] >> GeometryController.__init__(",") ")
         self.parent = parent
         self.parent = parent
         self.model =    model.GeometryModel(self)
         self.model =    model.GeometryModel(self)
@@ -1247,19 +1203,42 @@ class GeometryController:
 
 
         self.callBackActive = callBackActive
         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.model.setValues(values)
 
 
         self.callBackActive = True
         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):
 def messageBox(title, text):
         msg = QMessageBox()
         msg = QMessageBox()
         msg.setText(text)
         msg.setText(text)
@@ -1268,6 +1247,7 @@ def messageBox(title, text):
         msg.setStandardButtons(QMessageBox.Ok)
         msg.setStandardButtons(QMessageBox.Ok)
         msg.setEscapeButton(QMessageBox.Close)
         msg.setEscapeButton(QMessageBox.Close)
         msg.exec_()
         msg.exec_()
+# -----------------------------------------------------------------------------
 def okCancelBox(title, text):
 def okCancelBox(title, text):
         msg = QMessageBox()
         msg = QMessageBox()
         msg.setText(text)
         msg.setText(text)
@@ -1276,4 +1256,26 @@ def okCancelBox(title, text):
         msg.setEscapeButton(QMessageBox.Close)
         msg.setEscapeButton(QMessageBox.Close)
         return msg.exec_()
         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
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 # 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 ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
 """
-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 ------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 
 
-import os, colour, copy, json, time
+import os, colour, copy, json, time, sklearn.cluster, math
 import pathos.multiprocessing, multiprocessing, functools
 import pathos.multiprocessing, multiprocessing, functools
 import numpy as np
 import numpy as np
 from geomdl import BSpline
 from geomdl import BSpline
@@ -116,7 +34,7 @@ from geomdl import utilities
 
 
 from datetime import datetime
 from datetime import datetime
 
 
-import hdrCore.image, hdrCore.utils
+import hdrCore.image, hdrCore.utils, hdrCore.aesthetics, hdrCore.image
 from . import controller, thread
 from . import controller, thread
 import hdrCore.processing, hdrCore.quality
 import hdrCore.processing, hdrCore.quality
 import preferences.preferences as pref
 import preferences.preferences as pref
@@ -152,6 +70,8 @@ class ImageGalleryModel:
             processPipes (list[hdrCore.porocessing.ProcessPipe]): list of process-pipes associated to images
             processPipes (list[hdrCore.porocessing.ProcessPipe]): list of process-pipes associated to images
             _selectedImage (int): index of current (selected) process-pipe
             _selectedImage (int): index of current (selected) process-pipe
 
 
+            aestheticsModels (list[hdrCore.aesthetics.MultidimensionalImageAestheticsModel])
+
         Methods:
         Methods:
             setSelectedImage
             setSelectedImage
             selectedImage
             selectedImage
@@ -171,11 +91,15 @@ class ImageGalleryModel:
         self.processPipes = []
         self.processPipes = []
         self._selectedImage= -1
         self._selectedImage= -1
 
 
+        self.aesthetics = []
+
     def setSelectedImage(self,id): self._selectedImage = id
     def setSelectedImage(self,id): self._selectedImage = id
 
 
     def selectedImage(self): return self._selectedImage
     def selectedImage(self): return self._selectedImage
 
 
     def getSelectedProcessPipe(self):
     def getSelectedProcessPipe(self):
+        if pref.verbose: print(" [MODEL] >> ImageGalleryModel.getSelectedProcessPipe(",  ")")
+
         res = None
         res = None
         if self._selectedImage != -1: res= self.processPipes[self._selectedImage]
         if self._selectedImage != -1: res= self.processPipes[self._selectedImage]
         return res
         return res
@@ -185,6 +109,9 @@ class ImageGalleryModel:
 
 
         self.imageFilenames = list(filenames)
         self.imageFilenames = list(filenames)
         self.imagesMetadata, self.processPipes =  [], [] # reset metadata and processPipes
         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)
         nbImagePage = controller.GalleryMode.nbRow(self.controller.view.shapeMode)*controller.GalleryMode.nbCol(self.controller.view.shapeMode)
         for f in self.imageFilenames: # load only first page
         for f in self.imageFilenames: # load only first page
             self.processPipes.append(None)
             self.processPipes.append(None)
@@ -225,6 +152,9 @@ class ImageGalleryModel:
 
 
     def getProcessPipeById(self,i):
     def getProcessPipeById(self,i):
         return self.processPipes[i]
         return self.processPipes[i]
+
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class AppModel(object):
 class AppModel(object):
     """ class for main window model """
     """ class for main window model """
@@ -232,22 +162,23 @@ class AppModel(object):
     def __init__(self, controller):
     def __init__(self, controller):
         # attributes
         # attributes
         self.controller = controller
         self.controller = controller
-        self.directory = '.'
+        self.directory = pref.getImagePath()
         self.imageFilenames = []
         self.imageFilenames = []
         self.displayHDRProcess = None
         self.displayHDRProcess = None
+        #V5
         self.displayModel = {'scaling':12, 'shape':(2160,3840)}
         self.displayModel = {'scaling':12, 'shape':(2160,3840)}
 
 
     def setDirectory(self,path):
     def setDirectory(self,path):
         # read directory and return image filename list
         # read directory and return image filename list
         self.directory =path
         self.directory =path
+        pref.setImagePath(path)
         self.imageFilenames = map(lambda x: os.path.join(self.directory,x),
         self.imageFilenames = map(lambda x: os.path.join(self.directory,x),
                                   hdrCore.utils.filterlistdir(self.directory,('.jpg','.JPG','.hdr','.HDR')))
                                   hdrCore.utils.filterlistdir(self.directory,('.jpg','.JPG','.hdr','.HDR')))
 
 
         return self.imageFilenames
         return self.imageFilenames
-
-# -----------------------------------------------------------------------------
-# --- Class EditImageModel ----------------------------------------------------
-# -----------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# --- Class EditImageModel -----------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class EditImageModel(object):
 class EditImageModel(object):
     """
     """
     xxxx xxxx xxxx.
     xxxx xxxx xxxx.
@@ -301,6 +232,7 @@ class EditImageModel(object):
         else:
         else:
             return False
             return False
 
 
+    @staticmethod
     def buildProcessPipe():
     def buildProcessPipe():
         """
         """
         WARNING: 
         WARNING: 
@@ -341,24 +273,44 @@ class EditImageModel(object):
         processPipe.setParameters(idLightnessMaskProcessNode, defaultMask)  
         processPipe.setParameters(idLightnessMaskProcessNode, defaultMask)  
 
 
         # saturation ---------------------------------------------------------------------------------------------------------
         # saturation ---------------------------------------------------------------------------------------------------------
-        defaultValue = {'saturation': 0.0, 
-                        'method': 'gamma'}
+        defaultValue = {'saturation': 0.0,  'method': 'gamma'}
         idSaturationProcessNode = processPipe.append(hdrCore.processing.saturation(), paramDict=None, name="saturation")    
         idSaturationProcessNode = processPipe.append(hdrCore.processing.saturation(), paramDict=None, name="saturation")    
         processPipe.setParameters(idSaturationProcessNode, defaultValue)                     
         processPipe.setParameters(idSaturationProcessNode, defaultValue)                     
 
 
         # colorEditor0 ---------------------------------------------------------------------------------------------------------
         # colorEditor0 ---------------------------------------------------------------------------------------------------------
         defaultParameterColorEditor0= {'selection': {'lightness': (0,100),'chroma': (0,100),'hue':(0,360)},  
         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}        
                                        'mask': False}        
         idColorEditor0ProcessNode = processPipe.append(hdrCore.processing.colorEditor(), paramDict=None, name="colorEditor0")  
         idColorEditor0ProcessNode = processPipe.append(hdrCore.processing.colorEditor(), paramDict=None, name="colorEditor0")  
         processPipe.setParameters(idColorEditor0ProcessNode, defaultParameterColorEditor0)
         processPipe.setParameters(idColorEditor0ProcessNode, defaultParameterColorEditor0)
 
 
         # colorEditor1 ---------------------------------------------------------------------------------------------------------
         # colorEditor1 ---------------------------------------------------------------------------------------------------------
         defaultParameterColorEditor1= {'selection': {'lightness': (0,100),'chroma': (0,100),'hue':(0,360)},  
         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}        
                                        'mask': False}        
         idColorEditor1ProcessNode = processPipe.append(hdrCore.processing.colorEditor(), paramDict=None, name="colorEditor1")  
         idColorEditor1ProcessNode = processPipe.append(hdrCore.processing.colorEditor(), paramDict=None, name="colorEditor1")  
         processPipe.setParameters(idColorEditor1ProcessNode, defaultParameterColorEditor1)
         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 ---------------------------------------------------------------------------------------------------------
         # geometry ---------------------------------------------------------------------------------------------------------
         defaultValue = { 'ratio': (16,9), 'up': 0,'rotation': 0.0}
         defaultValue = { 'ratio': (16,9), 'up': 0,'rotation': 0.0}
@@ -444,6 +396,8 @@ class EditImageModel(object):
     def updateImage(self, imgTM):
     def updateImage(self, imgTM):
         self.controller.updateImage(imgTM)
         self.controller.updateImage(imgTM)
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class AdvanceSliderModel():
 class AdvanceSliderModel():
     def __init__(self, controller, value):
     def __init__(self, controller, value):
         self.controller = controller
         self.controller = controller
@@ -451,6 +405,8 @@ class AdvanceSliderModel():
     def setValue(self, value): self.value =  value
     def setValue(self, value): self.value =  value
     def toDict(self): return {'value': self.value}
     def toDict(self): return {'value': self.value}
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class ToneCurveModel():
 class ToneCurveModel():
     """
     """
 
 
@@ -475,7 +431,7 @@ class ToneCurveModel():
         |   o   |       |       |       |       |                              |
         |   o   |       |       |       |       |                              |
         |       |       |       |       |       |                              |
         |       |       |       |       |       |                              |
        [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):
     def setValues(self, controlPointsDict):
         self.control = copy.deepcopy(controlPointsDict)
         self.control = copy.deepcopy(controlPointsDict)
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class LightnessMaskModel():
 class LightnessMaskModel():
     def __init__(self, _controller):
     def __init__(self, _controller):
         self.controller = _controller
         self.controller = _controller
@@ -564,6 +522,8 @@ class LightnessMaskModel():
 
 
     def setValues(self, values): self.masks = copy.deepcopy(values)
     def setValues(self, values): self.masks = copy.deepcopy(values)
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class ImageInfoModel(object):
 class ImageInfoModel(object):
 
 
     def __init__(self,controller):
     def __init__(self,controller):
@@ -576,103 +536,50 @@ class ImageInfoModel(object):
 
 
     def setProcessPipe(self, processPipe):  self.processPipe = processPipe
     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):
         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
                         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: 
             if pref.verbose: 
                 print(" [MODEL] >> ImageInfoModel.changeUseCase(",")")
                 print(" [MODEL] >> ImageInfoModel.changeUseCase(",")")
-                for useCC in updatedHdrUseCases: print(useCC)     
+                for tt in updatedMeta: print(tt)     
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class CurveControlModel(object): pass
 class CurveControlModel(object): pass
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
+# ---- HDRviewerModel ----------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class HDRviewerModel(object):
 class HDRviewerModel(object):
     def __init__(self,_controller):
     def __init__(self,_controller):
+        if pref.verbose: print(" [MODEL] >> HDRviewerModel.__init__(",")")
+
         self.controller = _controller
         self.controller = _controller
 
 
         # current image
         # current image
         self.currentIMG = None
         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):
 class LchColorSelectorModel(object):
     def __init__(self, _controller):
     def __init__(self, _controller):
@@ -689,6 +596,14 @@ class LchColorSelectorModel(object):
 
 
         self.mask =         False
         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):
     def setHueSelection(self, hMin,hMax):
         self.hueSelection = hMin,hMax
         self.hueSelection = hMin,hMax
         return self.getValues()
         return self.getValues()
@@ -740,6 +655,8 @@ class LchColorSelectorModel(object):
 
 
         self.mask =         values['mask']  if 'mask' in values.keys() else False
         self.mask =         values['mask']  if 'mask' in values.keys() else False
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------------------
 class GeometryModel(object):
 class GeometryModel(object):
     def __init__(self, _controller):
     def __init__(self, _controller):
         self.controller = _controller
         self.controller = _controller
@@ -764,11 +681,137 @@ class GeometryModel(object):
         self.up =       values['up']        if 'up'         in values.keys() else 0.0
         self.up =       values['up']        if 'up'         in values.keys() else 0.0
         self.rotation = values['rotation']  if 'rotation'   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
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 # 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 ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
 """
-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 ------------------------------------------------------------------
@@ -172,6 +88,7 @@ class RequestCompute(object):
         """
         """
         self.requestDict[id] = copy.deepcopy(params)
         self.requestDict[id] = copy.deepcopy(params)
 
 
+
         if self.readyToRun:
         if self.readyToRun:
             # start processing processpipe
             # start processing processpipe
             self.pool.start(RunCompute(self))
             self.pool.start(RunCompute(self))
@@ -221,12 +138,19 @@ class RunCompute(QRunnable):
         """
         """
         self.parent.readyToRun = False
         self.parent.readyToRun = False
         for k in self.parent.requestDict.keys(): self.parent.processpipe.setParameters(k,self.parent.requestDict[k])
         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 --------------------------------------------------
 # --- Class RequestLoadImage --------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -316,7 +240,6 @@ class RunLoadImage(QRunnable):
             self.parent.endLoadImage(False, self.minIdxInPage, self.imgIdxInPage, processPipe, self.filename)
             self.parent.endLoadImage(False, self.minIdxInPage, self.imgIdxInPage, processPipe, self.filename)
         except(IOError, ValueError) as e:
         except(IOError, ValueError) as e:
             self.parent.endLoadImage(True, self.minIdxInPage, self.imgIdxInPage, None, self.filename)
             self.parent.endLoadImage(True, self.minIdxInPage, self.imgIdxInPage, None, self.filename)
-
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 # --- Class pCompute ----------------------------------------------------------
 # --- Class pCompute ----------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -416,4 +339,180 @@ class pRun(QRunnable):
         """
         """
         self.processpipe.compute()
         self.processpipe.compute()
         pRes = self.processpipe.getImage(toneMap=self.toneMap)
         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
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 # 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 ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
 """
-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 QWidget, QLabel, QApplication, QMainWindow, QSplitter, QFrame, QDockWidget, QDesktopWidget
 from PyQt5.QtWidgets import QSplitter, QFrame, QSlider, QCheckBox, QGroupBox
 from PyQt5.QtWidgets import QSplitter, QFrame, QSlider, QCheckBox, QGroupBox
 from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QGridLayout, QLayout, QScrollArea, QFormLayout
 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.QtWidgets import QAction, QProgressBar, QDialog
 from PyQt5.QtGui import QPixmap, QImage, QDoubleValidator
 from PyQt5.QtGui import QPixmap, QImage, QDoubleValidator
 from PyQt5.QtCore import Qt
 from PyQt5.QtCore import Qt
@@ -121,12 +39,15 @@ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
 from matplotlib.figure import Figure
 from matplotlib.figure import Figure
 
 
 from datetime import datetime
 from datetime import datetime
+import time
 
 
 import numpy as np
 import numpy as np
 import hdrCore.image, hdrCore.processing
 import hdrCore.image, hdrCore.processing
 import math, enum
 import math, enum
+import functools
 
 
-from . import controller
+from . import controller, model
+import hdrCore.metadata
 import preferences.preferences as pref
 import preferences.preferences as pref
 
 
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
@@ -190,7 +111,12 @@ class FigureWidget(FigureCanvas):
     def plot(self,X,Y,mode, clear=False):
     def plot(self,X,Y,mode, clear=False):
         if clear: self.axes.clear()
         if clear: self.axes.clear()
         self.axes.plot(X,Y,mode)
         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) ----------------------------------------------------
 # --- class ImageGalleryView(QSplitter) ----------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
@@ -386,7 +312,7 @@ class AppView(QMainWindow):
         # ----------------------------------
         # ----------------------------------
         self.dock = controller.MultiDockController(self, HDRcontroller)
         self.dock = controller.MultiDockController(self, HDRcontroller)
         self.addDockWidget(Qt.RightDockWidgetArea,self.dock.view)
         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
         # build menu
@@ -396,6 +322,8 @@ class AppView(QMainWindow):
         self.buildExport()
         self.buildExport()
         self.buildPreferences()
         self.buildPreferences()
     # ------------------------------------------------------------------------------------------
     # ------------------------------------------------------------------------------------------
+    def getImageGalleryController(self): return self.imageGalleryController
+    # ------------------------------------------------------------------------------------------
     def resizeEvent(self, event): super().resizeEvent(event)
     def resizeEvent(self, event): super().resizeEvent(event)
     # ------------------------------------------------------------------------------------------
     # ------------------------------------------------------------------------------------------
     def setWindowGeometry(self, scale=0.8):
     def setWindowGeometry(self, scale=0.8):
@@ -442,20 +370,22 @@ class AppView(QMainWindow):
         menubar = self.menuBar()# get menubar
         menubar = self.menuBar()# get menubar
         prefMenu = menubar.addMenu('&Preferences')# file menu
         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):
     def buildDisplayHDR(self):
@@ -484,17 +414,17 @@ class AppView(QMainWindow):
         menubar = self.menuBar()# get menubar
         menubar = self.menuBar()# get menubar
         exportHDR = menubar.addMenu('&Export HDR image')# file menu
         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):
     def buildDockMenu(self):
         menubar = self.menuBar()# get menubar
         menubar = self.menuBar()# get menubar
@@ -512,18 +442,16 @@ class AppView(QMainWindow):
         edit.triggered.connect(self.dock.activateEDIT)
         edit.triggered.connect(self.dock.activateEDIT)
         dockMenu.addAction(edit)
         dockMenu.addAction(edit)
 
 
-        iqa = QAction('&Image Quality Assessment', self)        
+        iqa = QAction('&Image Aesthetics', self)        
         iqa.setShortcut('Ctrl+A')
         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)
         dockMenu.addAction(iqa)
     # ------------------------------------------------------------------------------------------
     # ------------------------------------------------------------------------------------------
     def closeEvent(self, event):
     def closeEvent(self, event):
         if pref.verbose: print(" [CB] >> AppView.closeEvent()>> ... closing")
         if pref.verbose: print(" [CB] >> AppView.closeEvent()>> ... closing")
         self.imageGalleryController.save()
         self.imageGalleryController.save()
         self.controller.hdrDisplay.close()
         self.controller.hdrDisplay.close()
-    # ------------------------------------------------------------------------------------------
-    def assessmentRunning(self): return self.dock.assessmentRunning()
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # --- class ImageInfoView(QSplitter) -------------------------------------------------------
 # --- class ImageInfoView(QSplitter) -------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
@@ -560,62 +488,23 @@ class ImageInfoView(QSplitter):
         line.setFrameShape(QFrame.HLine)
         line.setFrameShape(QFrame.HLine)
         self.layout.addRow(line)
         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.layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
 
 
         self.scroll = QScrollArea()
         self.scroll = QScrollArea()
@@ -664,88 +553,27 @@ class ImageInfoView(QSplitter):
         # ---------------------------------------------------
         # ---------------------------------------------------
         self.controller.callBackActive = False
         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
         self.controller.callBackActive = True
         # ---------------------------------------------------
         # ---------------------------------------------------
         return self.imageWidgetController.setImage(image_)
         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) --------------------------------------------------------
 # --- class AdvanceLineEdit(object) --------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
@@ -774,7 +602,7 @@ class AdvanceCheckBox(object):
 
 
     def setState(self, on_off): self.checkbox.setChecked(on_off)
     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) -------------------------------------------------------
 # --- class EditImageView(QSplitter) -------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
@@ -827,7 +655,31 @@ class EditImageView(QSplitter):
         self.colorEditor1 = controller.LchColorSelectorController(self, idName = "colorEditor1")
         self.colorEditor1 = controller.LchColorSelectorController(self, idName = "colorEditor1")
         self.layout.addWidget(self.colorEditor1.view)
         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 ----------------------
         # 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.geometry = controller.GeometryController(self)
         self.layout.addWidget(self.geometry.view)
         self.layout.addWidget(self.geometry.view)
 
 
@@ -937,6 +789,24 @@ class EditImageView(QSplitter):
         values = processPipe.getParameters(id)
         values = processPipe.getParameters(id)
         self.colorEditor1.setValues(values, callBackActive = False)
         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
         # geometry
         # recover data in pipe and restore it
         # recover data in pipe and restore it
         id = processPipe.getProcessNodeByName("geometry")
         id = processPipe.getProcessNodeByName("geometry")
@@ -957,18 +827,19 @@ class MultiDockView(QDockWidget):
         self.childControllers = [
         self.childControllers = [
             controller.EditImageController(self, HDRcontroller), 
             controller.EditImageController(self, HDRcontroller), 
             controller.ImageInfoController(self), 
             controller.ImageInfoController(self), 
-            controller.ImageQualityController(self, HDRcontroller)]
+            controller.ImageAestheticsController(self)]
+            #controller.ImageQualityController(self, HDRcontroller)]
         self.childController = self.childControllers[0]
         self.childController = self.childControllers[0]
         self.active = 0
         self.active = 0
         self.setWidget(self.childController.view)
         self.setWidget(self.childController.view)
         self.repaint()
         self.repaint()
-
+    # ------------------------------------------------------------------------------------------
     def switch(self,nb):
     def switch(self,nb):
         """
         """
             change active dock
             change active dock
             nb = 0 > editing imag dock
             nb = 0 > editing imag dock
             nb = 1 > image info and metadata 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,")")
         if pref.verbose:  print(" [VIEW] >> MultiDockView.switch(",nb,")")
 
 
@@ -982,11 +853,13 @@ class MultiDockView(QDockWidget):
             self.childController.buildView(processPipe)
             self.childController.buildView(processPipe)
             self.setWidget(self.childController.view)
             self.setWidget(self.childController.view)
             self.repaint()
             self.repaint()
-
+    # ------------------------------------------------------------------------------------------
     def setProcessPipe(self, processPipe):
     def setProcessPipe(self, processPipe):
         if pref.verbose:  print(" [VIEW] >> MultiDockView.setProcessPipe(",processPipe.getImage().name,")")
         if pref.verbose:  print(" [VIEW] >> MultiDockView.setProcessPipe(",processPipe.getImage().name,")")
         return self.childController.setProcessPipe(processPipe)
         return self.childController.setProcessPipe(processPipe)
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------------------
 # --- class AdvanceSliderView(QFrame) ------------------------------------------------------
 # --- class AdvanceSliderView(QFrame) ------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class AdvanceSliderView(QFrame):
 class AdvanceSliderView(QFrame):
@@ -1014,8 +887,8 @@ class AdvanceSliderView(QFrame):
         self.hbox.addWidget(self.reset)
         self.hbox.addWidget(self.reset)
 
 
         self.slider = QSlider(Qt.Horizontal)
         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.slider.setSingleStep(1) 
 
 
         self.vbox.addWidget(self.firstrow)
         self.vbox.addWidget(self.firstrow)
@@ -1047,6 +920,12 @@ class ToneCurveView(QFrame):
         self.curve.plot([0.0,100],[0.0,100.0],'r--')
         self.curve.plot([0.0,100],[0.0,100.0],'r--')
 
 
         #containers
         #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.containerShadows = QFrame()
         self.hboxShadows = QHBoxLayout()
         self.hboxShadows = QHBoxLayout()
         self.containerShadows.setLayout(self.hboxShadows)
         self.containerShadows.setLayout(self.hboxShadows)
@@ -1068,17 +947,24 @@ class ToneCurveView(QFrame):
         self.containerHighlights.setLayout(self.hboxHighlights)
         self.containerHighlights.setLayout(self.hboxHighlights)
 
 
         self.vbox.addWidget(self.curve)
         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.containerHighlights)
         self.vbox.addWidget(self.containerWhites)
         self.vbox.addWidget(self.containerWhites)
         self.vbox.addWidget(self.containerMediums)
         self.vbox.addWidget(self.containerMediums)
         self.vbox.addWidget(self.containerBlacks)
         self.vbox.addWidget(self.containerBlacks)
         self.vbox.addWidget(self.containerShadows)
         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
         # shadows
         self.labelShadows = QLabel("shadows")
         self.labelShadows = QLabel("shadows")
         self.sliderShadows = QSlider(Qt.Horizontal)
         self.sliderShadows = QSlider(Qt.Horizontal)
         self.sliderShadows.setRange(0,100)
         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 = QLineEdit()
         self.editShadows.setText(str(self.controller.model.default['shadows'][1]))
         self.editShadows.setText(str(self.controller.model.default['shadows'][1]))
         self.resetShadows = QPushButton("reset")
         self.resetShadows = QPushButton("reset")
@@ -1091,7 +977,7 @@ class ToneCurveView(QFrame):
         self.labelBlacks = QLabel("  blacks  ")
         self.labelBlacks = QLabel("  blacks  ")
         self.sliderBlacks = QSlider(Qt.Horizontal)
         self.sliderBlacks = QSlider(Qt.Horizontal)
         self.sliderBlacks.setRange(0,100)
         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 = QLineEdit()
         self.editBlacks.setText(str(self.controller.model.default['blacks'][1]))
         self.editBlacks.setText(str(self.controller.model.default['blacks'][1]))
         self.resetBlacks = QPushButton("reset")
         self.resetBlacks = QPushButton("reset")
@@ -1104,7 +990,7 @@ class ToneCurveView(QFrame):
         self.labelMediums = QLabel("mediums")
         self.labelMediums = QLabel("mediums")
         self.sliderMediums = QSlider(Qt.Horizontal)
         self.sliderMediums = QSlider(Qt.Horizontal)
         self.sliderMediums.setRange(0,100)
         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 = QLineEdit()
         self.editMediums.setText(str(self.controller.model.default['mediums'][1]))
         self.editMediums.setText(str(self.controller.model.default['mediums'][1]))
         self.resetMediums = QPushButton("reset")
         self.resetMediums = QPushButton("reset")
@@ -1117,7 +1003,7 @@ class ToneCurveView(QFrame):
         self.labelWhites = QLabel("  whites  ")
         self.labelWhites = QLabel("  whites  ")
         self.sliderWhites = QSlider(Qt.Horizontal)
         self.sliderWhites = QSlider(Qt.Horizontal)
         self.sliderWhites.setRange(0,100)
         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 = QLineEdit()
         self.editWhites.setText(str(self.controller.model.default['whites'][1]))
         self.editWhites.setText(str(self.controller.model.default['whites'][1]))
         self.resetWhites = QPushButton("reset")
         self.resetWhites = QPushButton("reset")
@@ -1130,7 +1016,7 @@ class ToneCurveView(QFrame):
         self.labelHighlights = QLabel("highlights")
         self.labelHighlights = QLabel("highlights")
         self.sliderHighlights = QSlider(Qt.Horizontal)
         self.sliderHighlights = QSlider(Qt.Horizontal)
         self.sliderHighlights.setRange(0,100)
         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 = QLineEdit()
         self.editHighlights.setText(str(self.controller.model.default['highlights'][1]))
         self.editHighlights.setText(str(self.controller.model.default['highlights'][1]))
         self.resetHighlights = QPushButton("reset")
         self.resetHighlights = QPushButton("reset")
@@ -1153,6 +1039,8 @@ class ToneCurveView(QFrame):
         self.resetMediums.clicked.connect(self.resetMediumsCB)
         self.resetMediums.clicked.connect(self.resetMediumsCB)
         self.resetWhites.clicked.connect(self.resetWhitesCB)
         self.resetWhites.clicked.connect(self.resetWhitesCB)
         self.resetHighlights.clicked.connect(self.resetHighlightsCB)
         self.resetHighlights.clicked.connect(self.resetHighlightsCB)
+        self.autoCurve.clicked.connect(self.controller.autoCurve)  #  zj add for semi-auto curve 
+                                                                                         
 
 
     def sliderShadowsChange(self):
     def sliderShadowsChange(self):
         if self.controller.callBackActive:
         if self.controller.callBackActive:
@@ -1299,244 +1187,6 @@ class HDRviewerView(QFrame):
 
 
     def auto(self): self.controller.callBackAuto(self.autoCheckBox.isChecked())
     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) ---------------------------------------------------
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 class LchColorSelectorView(QFrame):
 class LchColorSelectorView(QFrame):
@@ -1561,14 +1211,14 @@ class LchColorSelectorView(QFrame):
         self.imageHueRangeController.setImage(hueBarRGB)
         self.imageHueRangeController.setImage(hueBarRGB)
         # slider min
         # slider min
         self.sliderHueMin = QSlider(Qt.Horizontal)
         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
         # slider max
         self.sliderHueMax = QSlider(Qt.Horizontal)
         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
         # procedural image: Saturation bar
         saturationBarLch = hdrCore.image.Image.buildLchColorData((75,75), (0,100), (180,180), (20,720), width='c', height='L')
         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)
         self.imageSaturationController.setImage(saturationBarRGB)
         # slider min
         # slider min
         self.sliderChromaMin = QSlider(Qt.Horizontal)
         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
         # slider max
         self.sliderChromaMax = QSlider(Qt.Horizontal)
         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
         # procedural image:lightness bar
         lightnessBarLch = hdrCore.image.Image.buildLchColorData((0,100), (0,0), (180,180), (20,720), width='L', height='c')
         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)
         self.imageLightnessController.setImage(lightnessBarRGB)
         # slider min
         # slider min
         self.sliderLightMin = QSlider(Qt.Horizontal)
         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
         # slider max
         self.sliderLightMax = QSlider(Qt.Horizontal)
         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
         # editor
         self.labelEditor =      QLabel("color editor: hue shift, exposure, contrast, saturation")
         self.labelEditor =      QLabel("color editor: hue shift, exposure, contrast, saturation")
@@ -1612,9 +1262,9 @@ class LchColorSelectorView(QFrame):
         self.layoutHueShift = QHBoxLayout()
         self.layoutHueShift = QHBoxLayout()
         self.frameHueShift.setLayout(self.layoutHueShift)
         self.frameHueShift.setLayout(self.layoutHueShift)
         self.sliderHueShift =   QSlider(Qt.Horizontal)
         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 = QLineEdit()
         self.valueHueShift.setText(str(0.0))  
         self.valueHueShift.setText(str(0.0))  
         self.layoutHueShift.addWidget(QLabel("hue shift"))
         self.layoutHueShift.addWidget(QLabel("hue shift"))
@@ -1626,9 +1276,9 @@ class LchColorSelectorView(QFrame):
         self.layoutExposure = QHBoxLayout()
         self.layoutExposure = QHBoxLayout()
         self.frameExposure.setLayout(self.layoutExposure)
         self.frameExposure.setLayout(self.layoutExposure)
         self.sliderExposure =   QSlider(Qt.Horizontal)
         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 = QLineEdit()
         self.valueExposure.setText(str(0.0))  
         self.valueExposure.setText(str(0.0))  
         self.layoutExposure.addWidget(QLabel("exposure"))
         self.layoutExposure.addWidget(QLabel("exposure"))
@@ -1640,9 +1290,9 @@ class LchColorSelectorView(QFrame):
         self.layoutContrast = QHBoxLayout()
         self.layoutContrast = QHBoxLayout()
         self.frameContrast.setLayout(self.layoutContrast)
         self.frameContrast.setLayout(self.layoutContrast)
         self.sliderContrast =   QSlider(Qt.Horizontal)
         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 = QLineEdit()
         self.valueContrast.setText(str(0.0))  
         self.valueContrast.setText(str(0.0))  
         self.layoutContrast.addWidget(QLabel("contrast"))
         self.layoutContrast.addWidget(QLabel("contrast"))
@@ -1654,15 +1304,20 @@ class LchColorSelectorView(QFrame):
         self.layoutSaturation = QHBoxLayout()
         self.layoutSaturation = QHBoxLayout()
         self.frameSaturation.setLayout(self.layoutSaturation)
         self.frameSaturation.setLayout(self.layoutSaturation)
         self.sliderSaturation =   QSlider(Qt.Horizontal)
         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 = QLineEdit()
         self.valueSaturation.setText(str(0.0))  
         self.valueSaturation.setText(str(0.0))  
         self.layoutSaturation.addWidget(QLabel("saturation"))
         self.layoutSaturation.addWidget(QLabel("saturation"))
         self.layoutSaturation.addWidget(self.sliderSaturation)
         self.layoutSaturation.addWidget(self.sliderSaturation)
         self.layoutSaturation.addWidget(self.valueSaturation)
         self.layoutSaturation.addWidget(self.valueSaturation)
 
 
+        # -----
+        self.resetSelection  = QPushButton("reset selection")
+        self.resetEdit  = QPushButton("reset edit")
+        # -----
+
         # mask
         # mask
         self.checkboxMask = QCheckBox("show selection")
         self.checkboxMask = QCheckBox("show selection")
         self.checkboxMask.setChecked(False)
         self.checkboxMask.setChecked(False)
@@ -1683,6 +1338,11 @@ class LchColorSelectorView(QFrame):
         self.vbox.addWidget(self.sliderLightMin)
         self.vbox.addWidget(self.sliderLightMin)
         self.vbox.addWidget(self.sliderLightMax)
         self.vbox.addWidget(self.sliderLightMax)
 
 
+        # -----
+        self.vbox.addWidget(self.resetSelection)
+        # -----
+
+
         self.vbox.addWidget(self.labelEditor)
         self.vbox.addWidget(self.labelEditor)
         self.vbox.addWidget(self.frameHueShift)
         self.vbox.addWidget(self.frameHueShift)
         self.vbox.addWidget(self.frameSaturation)
         self.vbox.addWidget(self.frameSaturation)
@@ -1690,7 +1350,9 @@ class LchColorSelectorView(QFrame):
         self.vbox.addWidget(self.frameContrast)
         self.vbox.addWidget(self.frameContrast)
 
 
         self.vbox.addWidget(self.checkboxMask)
         self.vbox.addWidget(self.checkboxMask)
-
+        # -----
+        self.vbox.addWidget(self.resetEdit)
+        # -----
         self.setLayout(self.vbox)
         self.setLayout(self.vbox)
 
 
         # callbacks  
         # callbacks  
@@ -1705,6 +1367,10 @@ class LchColorSelectorView(QFrame):
         self.sliderContrast.valueChanged.connect(self.sliderContrastChange)
         self.sliderContrast.valueChanged.connect(self.sliderContrastChange)
         self.sliderHueShift.valueChanged.connect(self.sliderHueShiftChange)
         self.sliderHueShift.valueChanged.connect(self.sliderHueShiftChange)
         self.checkboxMask.toggled.connect(self.checkboxMaskChange)
         self.checkboxMask.toggled.connect(self.checkboxMaskChange)
+        # -----
+        self.resetSelection.clicked.connect(self.controller.resetSelection)
+        self.resetEdit.clicked.connect(self.controller.resetEdit)
+
 
 
     # callbacks
     # callbacks
     def sliderHueChange(self):
     def sliderHueChange(self):
@@ -1773,9 +1439,9 @@ class GeometryView(QFrame):
         self.layoutCroppingVerticalAdjustement = QHBoxLayout()
         self.layoutCroppingVerticalAdjustement = QHBoxLayout()
         self.frameCroppingVerticalAdjustement.setLayout(self.layoutCroppingVerticalAdjustement)
         self.frameCroppingVerticalAdjustement.setLayout(self.layoutCroppingVerticalAdjustement)
         self.sliderCroppingVerticalAdjustement =   QSlider(Qt.Horizontal)
         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 = QLineEdit()
         self.valueCroppingVerticalAdjustement.setText(str(0.0))  
         self.valueCroppingVerticalAdjustement.setText(str(0.0))  
         self.layoutCroppingVerticalAdjustement.addWidget(QLabel("cropping adj."))
         self.layoutCroppingVerticalAdjustement.addWidget(QLabel("cropping adj."))
@@ -1787,9 +1453,9 @@ class GeometryView(QFrame):
         self.layoutRotation = QHBoxLayout()
         self.layoutRotation = QHBoxLayout()
         self.frameRotation.setLayout(self.layoutRotation)
         self.frameRotation.setLayout(self.layoutRotation)
         self.sliderRotation =   QSlider(Qt.Horizontal)
         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 = QLineEdit()
         self.valueRotation.setText(str(0.0))  
         self.valueRotation.setText(str(0.0))  
         self.layoutRotation.addWidget(QLabel("rotation"))
         self.layoutRotation.addWidget(QLabel("rotation"))
@@ -1817,4 +1483,82 @@ class GeometryView(QFrame):
         # call controller
         # call controller
         self.controller.sliderRotationChange(v)
         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
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 # 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 ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
 """
-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 enum, rawpy, colour, imageio, copy, os, functools, skimage.transform
 import numpy as np
 import numpy as np
 from . import utils, processing, metadata
 from . import utils, processing, metadata
+import preferences.preferences as pref
 
 
 imageio.plugins.freeimage.download()
 imageio.plugins.freeimage.download()
 
 
@@ -375,7 +307,7 @@ class Image(object):
 
 
         # create 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 =  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
         # update path
         res.metadata.metadata['path'] = copy.deepcopy(path)
         res.metadata.metadata['path'] = copy.deepcopy(path)
@@ -392,11 +324,12 @@ class Image(object):
         else: # 'sRGB' in color space <from exif>
         else: # 'sRGB' in color space <from exif>
             res.colorSpace = RGBcolorspace 
             res.colorSpace = RGBcolorspace 
 
 
-        # vesa display DISPLAY
+        # display ready image
         if 'display' in res.metadata.metadata.keys():
         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
         return res
 
 
@@ -741,20 +674,14 @@ class ColorSpace(object):
         """
         """
         TODO - Documentation de la méthode Lab
         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                                 
     @staticmethod                                 
     def Lch():
     def Lch():
         """
         """
         TODO - Documentation de la méthode 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
     @staticmethod
     def sRGB():
     def sRGB():
@@ -777,10 +704,7 @@ class ColorSpace(object):
         """
         """
         TODO - Documentation de la méthode XYZ
         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
     @staticmethod
     def build(name='sRGB'):
     def build(name='sRGB'):

+ 72 - 106
uHDR/hdrCore/metadata.py

@@ -14,92 +14,44 @@
 # hdrCore project 2020
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 # 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 ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
 """
-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 ------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
-import enum, rawpy, colour, imageio, json, os, subprocess, ast
+import enum, rawpy, colour, imageio, json, os, subprocess, ast, copy
 import numpy as np
 import numpy as np
 from . import utils, processing, image
 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:
 class metadata:
     """
     """
@@ -144,32 +96,36 @@ class metadata:
                      'Software': None,
                      'Software': None,
                      'Lens': None,
                      'Lens': None,
                      'Focal Length': 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,
             'processpipe': None,
             'display' : 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['filename'] = _image.name
         self.metadata['path'] =     _image.path
         self.metadata['path'] =     _image.path
         h,w, c = _image.shape
         h,w, c = _image.shape
@@ -201,8 +157,18 @@ class metadata:
         JSONfilename = os.path.join(_image.path, filenameMetadata)   
         JSONfilename = os.path.join(_image.path, filenameMetadata)   
 
 
         if os.path.isfile(JSONfilename):
         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'
                 if _image.isHDR(): res.metadata['exif']['Color Space']=   'scRGB'
 
 
         else:
         else:
@@ -211,7 +177,7 @@ class metadata:
             with open(JSONfilename, "w") as file: json.dump(res.metadata,file)
             with open(JSONfilename, "w") as file: json.dump(res.metadata,file)
 
 
         return res
         return res
-
+    # ---------------------------------------------------------------------------
     def save(self):
     def save(self):
         """
         """
         Save the metadata in a json file. The extension .json is added to the filename of the image.
         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)   
         JSONfilename = os.path.join(self.image.path, filenameMetadata)   
 
 
         with open(JSONfilename, "w") as file: json.dump(self.metadata,file)
         with open(JSONfilename, "w") as file: json.dump(self.metadata,file)
-
+    # ---------------------------------------------------------------------------
     @staticmethod
     @staticmethod
     def readExif(filename):
     def readExif(filename):
         """
         """
@@ -257,7 +223,7 @@ class metadata:
         else: print("ERROR[metadata.readExif(",filename,"): file not found]")
         else: print("ERROR[metadata.readExif(",filename,"): file not found]")
 
 
         return exifDict
         return exifDict
-
+    # ---------------------------------------------------------------------------
     def recoverData(self,exif):
     def recoverData(self,exif):
         """
         """
         TODO - Documentation de la méthode recoverData
         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)'
                 elif exif[exifColorSpace[2]]==2:    self.metadata['exif']['Color Space']=   'Adobe RGB (1998)'
                 else:                               self.metadata['exif']['Color Space']=   metadata.defaultColorSpaceName
                 else:                               self.metadata['exif']['Color Space']=   metadata.defaultColorSpaceName
             else: 
             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 
                 self.metadata['exif']['Color Space'] = metadata.defaultColorSpaceName 
 
 
             # exposure time: 'Exposure Time', 'ExposureTime' 
             # exposure time: 'Exposure Time', 'ExposureTime' 
@@ -306,8 +272,7 @@ class metadata:
             
             
             # ISO: 'ISO', 'ISOSpeedRatings'
             # ISO: 'ISO', 'ISOSpeedRatings'
             exifISO = ['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]]
             elif exifISO[1] in exif: self.metadata['exif']['ISO']= exif[exifISO[1]]
             else: self.metadata['exif']['ISO'] = None
             else: self.metadata['exif']['ISO'] = None
 
 
@@ -348,7 +313,7 @@ class metadata:
         
         
         else: # exif data = {}
         else: # exif data = {}
             self.metadata['exif']['Color Space'] = metadata.defaultColorSpaceName
             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
         # bit per sample
         if self.metadata['exif']['Bits Per Sample'] == None:
         if self.metadata['exif']['Bits Per Sample'] == None:
@@ -359,7 +324,7 @@ class metadata:
         # dynamic range
         # dynamic range
         self.image.colorSpace= image.ColorSpace.sRGB()
         self.image.colorSpace= image.ColorSpace.sRGB()
         self.metadata['exif']['Dynamic Range (stops)'] = self.image.getDynamicRange(0.5)
         self.metadata['exif']['Dynamic Range (stops)'] = self.image.getDynamicRange(0.5)
-
+    # ---------------------------------------------------------------------------
     def __repr__(self):
     def __repr__(self):
         """
         """
         Convert the metadata in string.
         Convert the metadata in string.
@@ -369,7 +334,7 @@ class metadata:
                 The metadata in a string format.
                 The metadata in a string format.
         """
         """
         return json.dumps(self.metadata)
         return json.dumps(self.metadata)
-
+    # ---------------------------------------------------------------------------
     def __str__(self):
     def __str__(self):
         """
         """
         Convert the metadata in string.
         Convert the metadata in string.
@@ -379,3 +344,4 @@ class metadata:
                 The metadata in a string format.
                 The metadata in a string format.
         """
         """
         return self.__repr__()
         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
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 # 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 ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
 """
-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
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 # 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 ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
 """
-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
 import functools
 from geomdl import BSpline
 from geomdl import BSpline
 from geomdl import utilities
 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 guiQt.controller as gc
 import preferences.preferences as pref
 import preferences.preferences as pref
 from timeit import default_timer as timer
 from timeit import default_timer as timer
 
 
+import hdrCore.coreC
+
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 # --- package functions -------------------------------------------------------
 # --- package functions -------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -135,7 +71,7 @@ def sRGB_to_XYZ(RGB,apply_cctf_decoding=True):
     """
     """
     return colour.sRGB_to_XYZ(RGB, 
     return colour.sRGB_to_XYZ(RGB, 
                               illuminant=np.array([ 0.3127, 0.329 ]), 
                               illuminant=np.array([ 0.3127, 0.329 ]), 
-                              chromatic_adaptation_method='CAT02', 
+                              chromatic_adaptation_transform='CAT02', 
                               apply_cctf_decoding=apply_cctf_decoding)           
                               apply_cctf_decoding=apply_cctf_decoding)           
 
 
 def Lab_to_XYZ(Lab):
 def Lab_to_XYZ(Lab):
@@ -195,7 +131,7 @@ def sRGB_to_Lab(RGB, apply_cctf_decoding=True):
     """
     """
     XYZ = colour.sRGB_to_XYZ(RGB, 
     XYZ = colour.sRGB_to_XYZ(RGB, 
                              illuminant=np.array([ 0.3127, 0.329 ]), 
                              illuminant=np.array([ 0.3127, 0.329 ]), 
-                             chromatic_adaptation_method='CAT02', 
+                             chromatic_adaptation_transform='CAT02', 
                              apply_cctf_decoding=apply_cctf_decoding)           
                              apply_cctf_decoding=apply_cctf_decoding)           
     Lab = colour.XYZ_to_Lab(XYZ, illuminant=np.array([ 0.3127, 0.329 ]))
     Lab = colour.XYZ_to_Lab(XYZ, illuminant=np.array([ 0.3127, 0.329 ]))
     return Lab
     return Lab
@@ -227,7 +163,10 @@ def Lch_to_sRGB(Lch,apply_cctf_encoding=True, clip=False):
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 class Processing(object):
 class Processing(object):
     """
     """
-    TODO - Documentation de la classe Processing
+    class Processing: abstract class for processing object
+
+    Methods:
+        compute
     """
     """
 
 
     def compute(self,image,**kwargs):
     def compute(self,image,**kwargs):
@@ -235,14 +174,13 @@ class Processing(object):
         TODO - Documentation de la méthode compute
         TODO - Documentation de la méthode compute
 
 
         Args:
         Args:
-            image: TODO
-                TODO
-            kwargs: TODO
-                TODO
+            image (hdrCore.image.Image, Required): input image
+                
+            kwargs (dict, Optionnal): parameters of processing
                 
                 
         Returns:
         Returns:
-            TODO
-                TODO
+            (hdrCore.image.Image)
+
         """
         """
         return copy.deepcopy(image)
         return copy.deepcopy(image)
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -338,12 +276,11 @@ class exposure(Processing):
                     res.linear =        True
                     res.linear =        True
 
 
                 dt = timer() - start
                 dt = timer() - start
-                print("############# exposure: cctf_decoding (",pref.computation,"):",dt)
 
 
             res.colorData =     res.colorData*math.pow(2,EV)
             res.colorData =     res.colorData*math.pow(2,EV)
 
 
         end = timer()
         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
         return res
 
 
@@ -370,7 +307,7 @@ class exposure(Processing):
 
 
         bins = np.linspace(0,1,25+1)
         bins = np.linspace(0,1,25+1)
 
 
-        @staticmethod
+        # local method for pool (not static!)
         def evEval(ev):
         def evEval(ev):
             """
             """
             TODO - Documentation de la méthode evEval
             TODO - Documentation de la méthode evEval
@@ -399,7 +336,7 @@ class exposure(Processing):
         sumsH  = list(results)
         sumsH  = list(results)
         
         
         bestEV = evs[np.argmax(sumsH)]
         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}
         return {'EV':bestEV}
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -453,7 +390,6 @@ class contrast(Processing):
                     res.linear =        False
                     res.linear =        False
 
 
                 dt = timer() - start
                 dt = timer() - start
-                print("############# contrast: cctf_encoding (",pref.computation,"):",dt)
 
 
             # scaling contrast
             # scaling contrast
             contrastValue = contrastValue/100 
             contrastValue = contrastValue/100 
@@ -467,7 +403,7 @@ class contrast(Processing):
             res.colorData = scalingFactor*(res.colorData-0.5)+0.5
             res.colorData = scalingFactor*(res.colorData-0.5)+0.5
         
         
         end=timer()    
         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
         return res
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -512,10 +448,8 @@ class ColorSpaceTransform(Processing):
         Color space transform operator
         Color space transform operator
 
 
         Args:
         Args:
-            img: hdrCore.image.Image
-                Required  : hdr image
-            kwargs: dict
-                Optionnal : parameters
+            img (hdrCore.image.Image, Required)  : input image
+            kwargs (dict,Optionnal) : parameters
                 TODO
                 TODO
                 
                 
         Returns:
         Returns:
@@ -535,7 +469,7 @@ class ColorSpaceTransform(Processing):
                         apply_cctf_decoding=True if not img.linear else False
                         apply_cctf_decoding=True if not img.linear else False
                         
                         
                         RGB = res.colorData
                         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 ]))
                         Lab = colour.XYZ_to_Lab(XYZ, illuminant=np.array([ 0.3127, 0.329 ]))
                         res.colorData, res.linear, res.colorSpace  = Lab, None,image.ColorSpace.Lab()
                         res.colorData, res.linear, res.colorSpace  = Lab, None,image.ColorSpace.Lab()
 
 
@@ -572,7 +506,7 @@ class ColorSpaceTransform(Processing):
                     if currentCS=="sRGB": # sRGB to XYZ                                                         
                     if currentCS=="sRGB": # sRGB to XYZ                                                         
                         apply_cctf_decoding=True if  (img.type == image.imageType.SDR) and (not img.linear) else False
                         apply_cctf_decoding=True if  (img.type == image.imageType.SDR) and (not img.linear) else False
                         RGB = res.colorData
                         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()
                         res.colorData,res.linear , res.colorSpace = XYZ, True, image.ColorSpace.XYZ()
                         
                         
                     elif currentCS=="XYZ": # XYZ to XYZ                                                         
                     elif currentCS=="XYZ": # XYZ to XYZ                                                         
@@ -682,9 +616,6 @@ class Ycurve(Processing):
                     res.linear =        False
                     res.linear =        False
 
 
                 dt = timer() - start
                 dt = timer() - start
-                print("############# Ycurve: cctf_encoding (",pref.computation,"):",dt)
-
-
 
 
             colorDataY =    sRGB_to_XYZ(res.colorData, apply_cctf_decoding=False)[:,:,1] 
             colorDataY =    sRGB_to_XYZ(res.colorData, apply_cctf_decoding=False)[:,:,1] 
             # change for multi-threading computation
             # change for multi-threading computation
@@ -717,7 +648,7 @@ class Ycurve(Processing):
                 res.colorData[:,:,2] = res.colorData[:,:,2]*colorDataFY/colorDataY
                 res.colorData[:,:,2] = res.colorData[:,:,2]*colorDataFY/colorDataY
         
         
         end = timer()        
         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
         return res
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -775,7 +706,7 @@ class saturation(Processing):
 
 
 
 
         end = timer()
         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
         return res
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -835,7 +766,6 @@ class colorEditor(Processing):
                     colorLab = sRGB_to_Lab(res.colorData, apply_cctf_decoding=True)
                     colorLab = sRGB_to_Lab(res.colorData, apply_cctf_decoding=True)
                     colorLCH = colour.Lab_to_LCHab(colorLab)
                     colorLCH = colour.Lab_to_LCHab(colorLab)
                 covnEnd = timer()
                 covnEnd = timer()
-                print("############# colorEditor: sRGB to Lch (",pref.computation,"):",covnEnd - covnStart)
 
 
             # selection from colorLCH
             # selection from colorLCH
             colorDataHue =          copy.deepcopy(colorLCH[:,:,2])
             colorDataHue =          copy.deepcopy(colorLCH[:,:,2])
@@ -863,8 +793,6 @@ class colorEditor(Processing):
             hueMask =           utils.NPlinearWeightMask(colorDataHue, hMin, hMax, hueTolerance)
             hueMask =           utils.NPlinearWeightMask(colorDataHue, hMin, hMax, hueTolerance)
 
 
             maskEnd = timer()
             maskEnd = timer()
-            print("############# colorEditor: mask (",pref.computation,"):",maskEnd - maskStart)
-
 
 
             mask = np.minimum(lightnessMask, np.minimum(chromaMask,hueMask))
             mask = np.minimum(lightnessMask, np.minimum(chromaMask,hueMask))
             compMask = 1.0 - mask
             compMask = 1.0 - mask
@@ -907,7 +835,6 @@ class colorEditor(Processing):
 
 
                 pivot = math.pow(2,ev)*(lMin+lMax)/2/100
                 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)
                 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')
                 else :                                      colorRGB = colour.cctf_encoding(colorRGB, function='sRGB')
                 
                 
@@ -927,7 +854,6 @@ class colorEditor(Processing):
 
 
         else:
         else:
             if res.colorSpace.name == 'Lch':
             if res.colorSpace.name == 'Lch':
-                # print("colorEditor: Lch to RGB (no-processing)")
                 colorLCH = res.colorData
                 colorLCH = res.colorData
                 # return to RGB (linear)
                 # return to RGB (linear)
                 colorRGB = Lch_to_sRGB(colorLCH,apply_cctf_encoding=False, clip=False)
                 colorRGB = Lch_to_sRGB(colorLCH,apply_cctf_encoding=False, clip=False)
@@ -945,7 +871,7 @@ class colorEditor(Processing):
             res.linear = False
             res.linear = False
 
 
         end = timer()
         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
         return res
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -1004,7 +930,7 @@ class lightnessMask(Processing):
             res.colorData = mask
             res.colorData = mask
         
         
         end = timer()
         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
         return res
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -1065,7 +991,7 @@ class geometry(Processing):
             res.shape = res.colorData.shape
             res.shape = res.colorData.shape
 
 
         end = timer()
         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
         return res
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
@@ -1075,21 +1001,36 @@ class geometry(Processing):
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 class ProcessPipe(object):
 class ProcessPipe(object):
     """
     """
-    TODO - Documentation de la classe ProcessPipe
+    class ProcessPipe: pipeline of process nodes
+        defines the pipeline of image procesing objects. 
     
     
     Attributes:
     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
     # autoresizing for fast computation
@@ -1101,6 +1042,23 @@ class ProcessPipe(object):
     # --- Class ProcessNode --------------------------------------------------
     # --- Class ProcessNode --------------------------------------------------
     # -------------------------------------------------------------------------
     # -------------------------------------------------------------------------
     class ProcessNode(object):
     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
         id=0
@@ -1181,12 +1139,10 @@ class ProcessPipe(object):
         return len(self.processNodes)-1 # return index of process (list[index])
         return len(self.processNodes)-1 # return index of process (list[index])
 
 
     def getName(self):
     def getName(self):
-        """
-        TODO - Documentation de la méthode getName
+        """return name of input image.
         
         
         Returns:
         Returns:
-            TODO
-                TODO
+            (str)
         """
         """
         return self.__outputImage.name
         return self.__outputImage.name
 
 
@@ -1231,7 +1187,7 @@ class ProcessPipe(object):
 
 
             elif pref.computation == 'numba':
             elif pref.computation == 'numba':
                 start = timer()
                 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
                 img.linear =        True
 
 
             elif pref.computation == 'cuda':
             elif pref.computation == 'cuda':
@@ -1240,7 +1196,6 @@ class ProcessPipe(object):
                 img.linear =        True
                 img.linear =        True
 
 
             dt = timer() - start
             dt = timer() - start
-            print("############# ProcessPipe.setImage: cctf_decoding (",pref.computation,"):",dt)
 
 
         # input image is set as __inputImage
         # input image is set as __inputImage
         self.__inputImage = img
         self.__inputImage = img
@@ -1261,13 +1216,17 @@ class ProcessPipe(object):
                     if idProcess != -1:
                     if idProcess != -1:
                         self.setParameters(idProcess,param)
                         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:
         Returns:
-            TODO
-                TODO
+            (hdrCore.image.Image)
         """
         """
         return self.__inputImage
         return self.__inputImage
 
 
@@ -1290,46 +1249,47 @@ class ProcessPipe(object):
                 self.__outputImage.colorData = colour.cctf_encoding(self.__outputImage.colorData, function='sRGB')
                 self.__outputImage.colorData = colour.cctf_encoding(self.__outputImage.colorData, function='sRGB')
                 self.__outputImage.linear =  False
                 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:
             elif self.__outputImage.isHDR() and self.__outputImage.linear and toneMap:
                 self.__outputImage.colorData = colour.cctf_encoding(self.__outputImage.colorData, function='sRGB')
                 self.__outputImage.colorData = colour.cctf_encoding(self.__outputImage.colorData, function='sRGB')
                 self.__outputImage.linear =  False
                 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):
             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.colorData = colour.cctf_decoding(self.__outputImage.colorData, function='sRGB')
                 self.__outputImage.linear =  True
                 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):
             elif (not self.__outputImage.linear) and (not toneMap):
                 self.__outputImage.colorData = colour.cctf_decoding(self.__outputImage.colorData, function='sRGB')
                 self.__outputImage.colorData = colour.cctf_decoding(self.__outputImage.colorData, function='sRGB')
                 self.__outputImage.linear =  True
                 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:
             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
             return self.__outputImage
         else: return None
         else: return None
 
 
     def compute(self,progress=None):
     def compute(self,progress=None):
-        """
-        TODO - Documentation de la méthode compute
+        """compute the processpipe
 
 
         Args:
         Args:
-            progress: TODO
-                TODO
+            progress: (object with showMessage and repaint method) object used to display progress
+
+        Returns:
+                
         """
         """
         if self.__inputImage:
         if self.__inputImage:
 
 
             if len(self.processNodes)>0: 
             if len(self.processNodes)>0: 
                 # first node
                 # first node
                 if progress:
                 if progress:
-                    progress.showMessage('comuting: '+self.processNodes[0].name+' start!')
+                    progress.showMessage('computing: '+self.processNodes[0].name+' start!')
                     progress.repaint()
                     progress.repaint()
                 self.processNodes[0].condCompute(self.__inputImage)
                 self.processNodes[0].condCompute(self.__inputImage)
                 if progress:
                 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.__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)
         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:
         Args:
             hdrmeta: TODO
             hdrmeta: TODO
                 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):
     def export(self,dirName,size=None,to=None,progress=None):
         """
         """
@@ -1483,6 +1443,8 @@ class ProcessPipe(object):
         self.setImage(img)
         self.setImage(img)
 
 
         self.compute(progress=progress)
         self.compute(progress=progress)
+        ###### res = hdrCore.coreC.coreCcompute(img, self)
+
         res = self.getImage(toneMap=False)
         res = self.getImage(toneMap=False)
         res = res.process(clip())
         res = res.process(clip())
 
 

+ 2 - 70
uHDR/hdrCore/quality.py

@@ -14,80 +14,12 @@
 # hdrCore project 2020
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 # 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 ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
 """
-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
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 # 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 ---------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
 """
-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 ---------------------------------------------------------------------------
 # ---- 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
 # hdrCore project 2020
 # author: remi.cozot@univ-littoral.fr
 # 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 -----------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 """
 """
-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 ------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
-import numba
-import numpy as np
+# RCZT 2023
+# import numba, json, os, copy
+import numpy as np, json, os
 
 
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 # --- Preferences -------------------------------------------------------------
 # --- Preferences -------------------------------------------------------------
 # -----------------------------------------------------------------------------
 # -----------------------------------------------------------------------------
 target = ['python','numba','cuda']
 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():
 def getComputationMode():
     """returns the preference computation mode: python, numba, cuda, ...
     """returns the preference computation mode: python, numba, cuda, ...
@@ -79,3 +115,51 @@ def getComputationMode():
         Returns (str)
         Returns (str)
     """
     """
     return computation
     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()
+# ----------------------------------------------------------------------------
+             

Fichier diff supprimé car celui-ci est trop grand
+ 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
 # author: remi.cozot@univ-littoral.fr
 
 
 
 
+
+
 """Only contains main program.
 """Only contains main program.
 """
 """
 
 
@@ -12,11 +14,10 @@ import guiQt.controller
 from multiprocessing import freeze_support
 from multiprocessing import freeze_support
 
 
 import sys
 import sys
-
 # ------------------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------------------
 if __name__ == '__main__':
 if __name__ == '__main__':
     freeze_support()
     freeze_support()
-    print("uHDR")
+    print("uHDRv6 (C++ core)")
 
 
     app = QApplication(sys.argv)
     app = QApplication(sys.argv)
 
 

+ 58 - 22
uHDR/uHDR.pyproj

@@ -2,15 +2,17 @@
   <PropertyGroup>
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <SchemaVersion>2.0</SchemaVersion>
     <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>
     <StartupFile>uHDR.py</StartupFile>
     <SearchPath>
     <SearchPath>
     </SearchPath>
     </SearchPath>
     <WorkingDirectory>.</WorkingDirectory>
     <WorkingDirectory>.</WorkingDirectory>
     <OutputPath>.</OutputPath>
     <OutputPath>.</OutputPath>
     <Name>uHDR</Name>
     <Name>uHDR</Name>
-    <RootNamespace>uHDR</RootNamespace>
+    <RootNamespace>HwHDR</RootNamespace>
+    <SuppressEnvironmentCreationPrompt>True</SuppressEnvironmentCreationPrompt>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
     <DebugSymbols>true</DebugSymbols>
     <DebugSymbols>true</DebugSymbols>
@@ -21,33 +23,67 @@
     <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
     <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <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" />
     <Compile Include="uHDR.py" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <Folder Include="guiQt\" />
     <Folder Include="guiQt\" />
-    <Folder Include="guiQt\__pycache__\" />
     <Folder Include="hdrCore\" />
     <Folder Include="hdrCore\" />
     <Folder Include="preferences\" />
     <Folder Include="preferences\" />
   </ItemGroup>
   </ItemGroup>
   <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>
   </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
   <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
   <!-- Uncomment the CoreCompile target to enable the Build command in
   <!-- Uncomment the CoreCompile target to enable the Build command in