|
@@ -0,0 +1,450 @@
|
|
|
|
+# import
|
|
|
|
+# ------------------------------------------------------------------------------------------
|
|
|
|
+import os, sys, math, json
|
|
|
|
+import matplotlib.pyplot as plt
|
|
|
|
+import numpy as np
|
|
|
|
+import easygui
|
|
|
|
+
|
|
|
|
+# miam import
|
|
|
|
+from . import WFNode, WFProcess, WFConnector
|
|
|
|
+import miam.image.Image as MIMG
|
|
|
|
+import miam.histogram.Histogram as MHIST
|
|
|
|
+import miam.image.channel
|
|
|
|
+import miam.utils
|
|
|
|
+
|
|
|
|
+from miam.processing import (ColorSpaceTransform, ContrastControl, Duplicate, ExposureControl, NoOp,
|
|
|
|
+ TMO_CCTF, TMO_Lightness, TMO_Linear, Ymap, Blend, MaskSegmentPercentile,
|
|
|
|
+ Fuse
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+# ------------------------------------------------------------------------------------------
|
|
|
|
+# MIAM project 2020
|
|
|
|
+# ------------------------------------------------------------------------------------------
|
|
|
|
+# author: remi.cozot@univ-littoral.fr
|
|
|
|
+# ------------------------------------------------------------------------------------------
|
|
|
|
+class WFWorkflow(object):
|
|
|
|
+ """container of workflow"""
|
|
|
|
+
|
|
|
|
+ def __init__(self, name='workflow:noname'):
|
|
|
|
+ # attributes
|
|
|
|
+ self.name = name
|
|
|
|
+
|
|
|
|
+ # list of processes
|
|
|
|
+ self.processes = []
|
|
|
|
+ # list of images
|
|
|
|
+ self.connectors = []
|
|
|
|
+ # root
|
|
|
|
+ self.root = None
|
|
|
|
+ # end
|
|
|
|
+ self.leafs = []
|
|
|
|
+
|
|
|
|
+ # methods
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ # add process, connect, root and leaf
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ def addProcess(self,p):
|
|
|
|
+ self.processes.append(p)
|
|
|
|
+
|
|
|
|
+ return p
|
|
|
|
+
|
|
|
|
+ # connecting processes
|
|
|
|
+ def connect(self, outputProcess=None, inputProcess=None, name=None):
|
|
|
|
+ if (not outputProcess) or (not inputProcess):
|
|
|
|
+ if not outputProcess: print("[ERROR] WFWorkflow.connect(): unknown output !")
|
|
|
|
+ if not inputProcess: print("[ERROR] WFWorkflow.connect(): unknown input !")
|
|
|
|
+ return None
|
|
|
|
+ else:
|
|
|
|
+ # outputProcess -- WFImage --> inputProcess
|
|
|
|
+ outputProcess = self.getByName(outputProcess) if isinstance(outputProcess,str) else outputProcess
|
|
|
|
+ inputProcess = self.getByName(inputProcess) if isinstance(inputProcess,str) else inputProcess
|
|
|
|
+
|
|
|
|
+ if not outputProcess: print("[ERROR] WFWorkflow.connect(): unknown output !")
|
|
|
|
+ if not inputProcess: print("[ERROR] WFWorkflow.connect(): unknown input !")
|
|
|
|
+
|
|
|
|
+ if not name:
|
|
|
|
+ name = name=outputProcess.name+"->"+inputProcess.name
|
|
|
|
+ connector = WFConnector.WFConnector(name=name)
|
|
|
|
+ # link components
|
|
|
|
+ outputProcess.outputs.append(connector)
|
|
|
|
+ inputProcess.inputs.append(connector)
|
|
|
|
+ connector.outputOf = outputProcess
|
|
|
|
+ connector.inputOf = inputProcess
|
|
|
|
+
|
|
|
|
+ self.connectors.append(connector)
|
|
|
|
+
|
|
|
|
+ return connector
|
|
|
|
+
|
|
|
|
+ def setRoot(self,p):
|
|
|
|
+ p = self.getByName(p) if isinstance(p,str) else p
|
|
|
|
+
|
|
|
|
+ if not p: print("[ERROR] WFWorkflow.setRoot(): unknown process !")
|
|
|
|
+
|
|
|
|
+ connector = WFConnector.WFConnector(name="root"+"->"+p.name)
|
|
|
|
+ connector.isRoot = True
|
|
|
|
+ self.connectors.append(connector)
|
|
|
|
+ self.root = connector
|
|
|
|
+ # link components
|
|
|
|
+ p.inputs.append(connector)
|
|
|
|
+ connector.inputOf = p
|
|
|
|
+
|
|
|
|
+ return connector
|
|
|
|
+
|
|
|
|
+ def setLeaf(self,p):
|
|
|
|
+ p = self.getByName(p) if isinstance(p,str) else p
|
|
|
|
+
|
|
|
|
+ if not p: print("[ERROR] WFWorkflow.setLeaf(): unknown process !")
|
|
|
|
+
|
|
|
|
+ connector = WFConnector.WFConnector(name=p.name+"->"+"leaf")
|
|
|
|
+ connector.isLeaf = True
|
|
|
|
+ self.connectors.append(connector)
|
|
|
|
+ self.leafs.append(connector)
|
|
|
|
+ # link components
|
|
|
|
+ p.outputs.append(connector)
|
|
|
|
+ connector.outputOf = p
|
|
|
|
+
|
|
|
|
+ return connector
|
|
|
|
+
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ # utils method
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ def checkName(self, name):
|
|
|
|
+
|
|
|
|
+ res = []
|
|
|
|
+
|
|
|
|
+ for p in self.processes:
|
|
|
|
+ if p.name == name: res.append(p)
|
|
|
|
+
|
|
|
|
+ for i in self.connectors:
|
|
|
|
+ if i.name == name: res.append(i)
|
|
|
|
+
|
|
|
|
+ return res
|
|
|
|
+
|
|
|
|
+ def getByName(self, name):
|
|
|
|
+ res = None
|
|
|
|
+
|
|
|
|
+ asName = self.checkName(name)
|
|
|
|
+
|
|
|
|
+ if len(asName)>0: res = asName[0]
|
|
|
|
+
|
|
|
|
+ return res
|
|
|
|
+
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ # computing
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ def compute(self, input=None):
|
|
|
|
+ #
|
|
|
|
+ #
|
|
|
|
+ if not input:
|
|
|
|
+ filename = easygui.fileopenbox(msg="select image.")
|
|
|
|
+ print("selected image:", filename)
|
|
|
|
+ # reading image
|
|
|
|
+ input= MIMG.Image.readImage(filename)
|
|
|
|
+ if input.isHDR(): input = input.removeZeros(0.5)
|
|
|
|
+ self.root.image=input
|
|
|
|
+
|
|
|
|
+ # main loop:
|
|
|
|
+ # -------------------------------------------------
|
|
|
|
+ awaitingProcess = []
|
|
|
|
+ # reset ready for compute
|
|
|
|
+ for con in self.connectors:
|
|
|
|
+ con.ready = False
|
|
|
|
+ # start with root
|
|
|
|
+ self.root.ready = True
|
|
|
|
+ # put all processes in awaiting Process
|
|
|
|
+ awaitingProcess = self.processes
|
|
|
|
+ # -------------------------------------------------
|
|
|
|
+ while len(awaitingProcess)>0 :
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ # looking for first process in list that all inputs are ready
|
|
|
|
+ pReady = None
|
|
|
|
+ pNotReady = []
|
|
|
|
+ for p in awaitingProcess:
|
|
|
|
+ if p.isReady() and (not pReady):
|
|
|
|
+ # take first ready for computation
|
|
|
|
+ pReady = p
|
|
|
|
+ else: # not ready or firest ready already find
|
|
|
|
+ pNotReady.append(p)
|
|
|
|
+ awaitingProcess = pNotReady
|
|
|
|
+ pReady.compute()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ def compile(self):
|
|
|
|
+ # check process input and output
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+ print("| COMPILE WORKFLOW")
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+ print("| check process input and output")
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+
|
|
|
|
+ noInput =[]
|
|
|
|
+ noOutput =[]
|
|
|
|
+
|
|
|
|
+ for p in self.processes:
|
|
|
|
+ print("| process:",p.name)
|
|
|
|
+ print("|-----------------------------------")
|
|
|
|
+ print("| input(s) -> ")
|
|
|
|
+
|
|
|
|
+ if len(p.inputs) == 0:
|
|
|
|
+ # no input
|
|
|
|
+ noInput.append(p)
|
|
|
|
+ print("| ", "no input -> root")
|
|
|
|
+
|
|
|
|
+ for input in p.inputs:
|
|
|
|
+ print("| ",input.name)
|
|
|
|
+ print("| output(s) -> ")
|
|
|
|
+
|
|
|
|
+ if len(p.outputs) == 0:
|
|
|
|
+ # no output
|
|
|
|
+ noOutput.append(p)
|
|
|
|
+ print("| ", "no output -> leaf")
|
|
|
|
+
|
|
|
|
+ for output in p.outputs:
|
|
|
|
+ print("| ",output.name)
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+
|
|
|
|
+ # root
|
|
|
|
+ if self.root and (len(noInput)== 0):
|
|
|
|
+ print("| root:",self.root.name, ": OK")
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+ if (not self.root) and (len(noInput)== 1):
|
|
|
|
+ # add Root
|
|
|
|
+ self.setRoot(noInput[0])
|
|
|
|
+ print("| root -> pocess:",noInput[0].name)
|
|
|
|
+ print("| root:",self.root.name, ": OK")
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+ noInput.pop()
|
|
|
|
+
|
|
|
|
+ if not self.root or (len(noInput)>1):
|
|
|
|
+ print("[ERROR] root error !")
|
|
|
|
+
|
|
|
|
+ # leaf
|
|
|
|
+ for p in noOutput:
|
|
|
|
+ self.setLeaf(p)
|
|
|
|
+ print("| process:",p.name, "- > leaf")
|
|
|
|
+ if len(self.leafs)>0:
|
|
|
|
+ for c in self.leafs:
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+ print("| leaf:",c.name)
|
|
|
|
+ print("| leaf: OK")
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+ else:
|
|
|
|
+ print("[ERROR] leaf error !")
|
|
|
|
+
|
|
|
|
+ # simulate processing
|
|
|
|
+ # -------------------------------------------------
|
|
|
|
+ print("| check workflow")
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+ awaitingProcess = []
|
|
|
|
+ # reset ready for compute
|
|
|
|
+ for con in self.connectors:
|
|
|
|
+ con.ready = False
|
|
|
|
+ # start with root
|
|
|
|
+ self.root.ready = True
|
|
|
|
+ # put all processes in awaiting Process
|
|
|
|
+ awaitingProcess = self.processes
|
|
|
|
+ # -------------------------------------------------
|
|
|
|
+ while len(awaitingProcess)>0 :
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ # looking for first process in list that all inputs are ready
|
|
|
|
+ pReady = None
|
|
|
|
+ pNotReady = []
|
|
|
|
+ for p in awaitingProcess:
|
|
|
|
+ if p.isReady() and (not pReady):
|
|
|
|
+ # take first ready for computation
|
|
|
|
+ pReady = p
|
|
|
|
+ else: # not ready or firest ready already find
|
|
|
|
+ pNotReady.append(p)
|
|
|
|
+ awaitingProcess = pNotReady
|
|
|
|
+
|
|
|
|
+ # 'compute'
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+ print("| computing:", pReady.name)
|
|
|
|
+ # set ouputs to ready
|
|
|
|
+ for con in pReady.outputs:
|
|
|
|
+ con.ready = True
|
|
|
|
+ print("| +---->", con.name, "ready")
|
|
|
|
+
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+ print("| check workflow: end reached: OK")
|
|
|
|
+ print("+-----------------------------------")
|
|
|
|
+
|
|
|
|
+ return self
|
|
|
|
+
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ # read
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ def readWorkflow(filename):
|
|
|
|
+
|
|
|
|
+ # read filename
|
|
|
|
+ with open(filename) as json_file:
|
|
|
|
+ jsonData = json.load(json_file)
|
|
|
|
+
|
|
|
|
+ # recover data
|
|
|
|
+ # name
|
|
|
|
+ wf_name = jsonData['name'] if 'name' in jsonData else filename
|
|
|
|
+ # processes and connectors
|
|
|
|
+ processesJSON = jsonData['processes'] if 'processes' in jsonData else []
|
|
|
|
+ connectorsJSON = jsonData['connectors'] if 'connectors' in jsonData else []
|
|
|
|
+
|
|
|
|
+ # build process and connectors
|
|
|
|
+ error = False
|
|
|
|
+
|
|
|
|
+ # create wf
|
|
|
|
+ wf = WFWorkflow(name=wf_name)
|
|
|
|
+
|
|
|
|
+ # build process
|
|
|
|
+ for processJSON in processesJSON:
|
|
|
|
+ # type, name, params
|
|
|
|
+ if 'type' in processJSON :
|
|
|
|
+ type= processJSON['type']
|
|
|
|
+ else:
|
|
|
|
+ print("ERROR[miam.workflow.WFWorflow.readWorkflow(",processJSON,"): no type found !]")
|
|
|
|
+ error = True
|
|
|
|
+ if 'name' in processJSON:
|
|
|
|
+ process_name= processJSON['name']
|
|
|
|
+ else:
|
|
|
|
+ print("ERROR[miam.workflow.WFWorflow.readWorkflow(",processJSON,"): no name found !]")
|
|
|
|
+ error = True
|
|
|
|
+ if 'parameters' in processJSON:
|
|
|
|
+ parameters= processJSON['parameters']
|
|
|
|
+ else:
|
|
|
|
+ print("ERROR[miam.workflow.WFWorflow.readWorkflow(",processJSON,"): no parameters found !]")
|
|
|
|
+ error = True
|
|
|
|
+ # build process
|
|
|
|
+ if not error:
|
|
|
|
+
|
|
|
|
+ # add here all new processing class
|
|
|
|
+ # --------------------------------------------
|
|
|
|
+ # ColorSpaceTransform
|
|
|
|
+ # ContrastControl
|
|
|
|
+ # Duplicate
|
|
|
|
+ # ExposureControl
|
|
|
|
+ # NoOp
|
|
|
|
+ # TMO_CCTF
|
|
|
|
+ # TMO_Lightness
|
|
|
|
+ # TMO_Linear
|
|
|
|
+ # Ymap
|
|
|
|
+ # Blend
|
|
|
|
+ # MaskSegmentPercentile
|
|
|
|
+ # Fuse
|
|
|
|
+ # --------------------------------------------
|
|
|
|
+
|
|
|
|
+ if type =='ColorSpaceTransform':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=ColorSpaceTransform.ColorSpaceTransform()).setParameters(eval(parameters))
|
|
|
|
+ )
|
|
|
|
+ elif type == 'ContrastControl':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=ContrastControl.ContrastControl()).setParameters(eval(parameters))
|
|
|
|
+ )
|
|
|
|
+ elif type == 'Duplicate':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=Duplicate.Duplicate()).setParameters(eval(parameters))
|
|
|
|
+ )
|
|
|
|
+ elif type == 'ExposureControl':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=ExposureControl.ExposureControl()).setParameters(eval(parameters))
|
|
|
|
+ )
|
|
|
|
+ elif type == 'NoOp':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=NoOp.NoOp())
|
|
|
|
+ )
|
|
|
|
+ elif type == 'TMO_CCTF':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=TMO_CCTF.TMO_CCTF()).setParameters(eval(parameters))
|
|
|
|
+ )
|
|
|
|
+ elif type == 'TMO_Lightness':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=TMO_Lightness.TMO_Lightness()).setParameters(eval(parameters))
|
|
|
|
+ )
|
|
|
|
+ elif type == 'TMO_Linear':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=TMO_Linear.TMO_Linear()).setParameters(eval(parameters))
|
|
|
|
+ )
|
|
|
|
+ elif type == 'Ymap':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=Ymap.Ymap()).setParameters(eval(parameters))
|
|
|
|
+ )
|
|
|
|
+ elif type == 'Blend':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=Blend.Blend()).setParameters(eval(parameters))
|
|
|
|
+ )
|
|
|
|
+ elif type == 'MaskSegmentPercentile':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=MaskSegmentPercentile.MaskSegmentPercentile()).setParameters(eval(parameters))
|
|
|
|
+ )
|
|
|
|
+ elif type == 'Fuse':
|
|
|
|
+ wf.addProcess(WFProcess.WFProcess(
|
|
|
|
+ name = process_name,
|
|
|
|
+ process=Fuse.Fuse()).setParameters(eval(parameters))
|
|
|
|
+ )
|
|
|
|
+ else:
|
|
|
|
+ print("ERROR[miam.workflow.WFWorflow.readWorkflow(",filename,"): unkown process type: ",type," !]")
|
|
|
|
+ error = True
|
|
|
|
+
|
|
|
|
+ # build connector
|
|
|
|
+ for connectorJSON in connectorsJSON:
|
|
|
|
+ # outputProcess -> inputProcess
|
|
|
|
+ if 'outputProcess' in connectorJSON :
|
|
|
|
+ outputProcess = connectorJSON['outputProcess']
|
|
|
|
+ else:
|
|
|
|
+ print("ERROR[miam.workflow.WFWorflow.readWorkflow(",connectorJSON,"): no outputProcess found !]")
|
|
|
|
+ error = True
|
|
|
|
+
|
|
|
|
+ if 'inputProcess' in connectorJSON:
|
|
|
|
+ inputProcess= connectorJSON['inputProcess']
|
|
|
|
+ else:
|
|
|
|
+ print("ERROR[miam.workflow.WFWorflow.readWorkflow(",connectorJSON,"): no inputProcess found !]")
|
|
|
|
+ error = True
|
|
|
|
+
|
|
|
|
+ # find output and input
|
|
|
|
+ pout = wf.getByName(outputProcess)
|
|
|
|
+ pin = wf.getByName(inputProcess)
|
|
|
|
+
|
|
|
|
+ wf.connect(outputProcess=pout, inputProcess=pin, name=outputProcess+"->"+inputProcess)
|
|
|
|
+
|
|
|
|
+ # compile and return
|
|
|
|
+ return wf.compile()
|
|
|
|
+
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ # display
|
|
|
|
+ # ---------------------------------------------
|
|
|
|
+ def display(self, withHistogram=True):
|
|
|
|
+ # number of leaf image
|
|
|
|
+ nbRes = len(self.leafs)
|
|
|
|
+ # dual display
|
|
|
|
+ dd = 2 if withHistogram else 1
|
|
|
|
+
|
|
|
|
+ fig, ax = plt.subplots(2,nbRes*dd)
|
|
|
|
+
|
|
|
|
+ # display input
|
|
|
|
+ self.root.image.plot(ax[0,0])
|
|
|
|
+ if withHistogram:
|
|
|
|
+ if self.root.image.isHDR():
|
|
|
|
+ ch = miam.image.channel.channel.Y
|
|
|
|
+ else:
|
|
|
|
+ ch = miam.image.channel.channel.L
|
|
|
|
+ MHIST.Histogram.build(self.root.image, ch).plot(ax[0,1])
|
|
|
|
+
|
|
|
|
+ # display results
|
|
|
|
+ for i, leaf in enumerate(self.leafs):
|
|
|
|
+ leaf.image.plot(ax[1,i*dd])
|
|
|
|
+ if withHistogram:
|
|
|
|
+ if leaf.image.isHDR():
|
|
|
|
+ ch = miam.image.channel.channel.Y
|
|
|
|
+ else:
|
|
|
|
+ ch = miam.image.channel.channel.L
|
|
|
|
+ MHIST.Histogram.build(leaf.image, ch).plot(ax[1,i*dd+1])
|
|
|
|
+
|
|
|
|
+ plt.show(block=True)
|