WFWorkflow.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. # import
  2. # ------------------------------------------------------------------------------------------
  3. import os, sys, math, json
  4. import matplotlib.pyplot as plt
  5. import numpy as np
  6. import easygui
  7. # miam import
  8. from . import WFNode, WFProcess, WFConnector
  9. import miam.image.Image as MIMG
  10. import miam.histogram.Histogram as MHIST
  11. import miam.image.channel
  12. import miam.utils
  13. from miam.processing import (ColorSpaceTransform, ContrastControl, Duplicate, ExposureControl, NoOp,
  14. TMO_CCTF, TMO_Lightness, TMO_Linear, Ymap, Blend, MaskSegmentPercentile,
  15. Fuse
  16. )
  17. # ------------------------------------------------------------------------------------------
  18. # MIAM project 2020
  19. # ------------------------------------------------------------------------------------------
  20. # author: remi.cozot@univ-littoral.fr
  21. # ------------------------------------------------------------------------------------------
  22. class WFWorkflow(object):
  23. """container of workflow"""
  24. def __init__(self, name='workflow:noname'):
  25. # attributes
  26. self.name = name
  27. # list of processes
  28. self.processes = []
  29. # list of images
  30. self.connectors = []
  31. # root
  32. self.root = None
  33. # end
  34. self.leafs = []
  35. # methods
  36. # ---------------------------------------------
  37. # add process, connect, root and leaf
  38. # ---------------------------------------------
  39. def addProcess(self,p):
  40. self.processes.append(p)
  41. return p
  42. # connecting processes
  43. def connect(self, outputProcess=None, inputProcess=None, name=None):
  44. if (not outputProcess) or (not inputProcess):
  45. if not outputProcess: print("[ERROR] WFWorkflow.connect(): unknown output !")
  46. if not inputProcess: print("[ERROR] WFWorkflow.connect(): unknown input !")
  47. return None
  48. else:
  49. # outputProcess -- WFImage --> inputProcess
  50. outputProcess = self.getByName(outputProcess) if isinstance(outputProcess,str) else outputProcess
  51. inputProcess = self.getByName(inputProcess) if isinstance(inputProcess,str) else inputProcess
  52. if not outputProcess: print("[ERROR] WFWorkflow.connect(): unknown output !")
  53. if not inputProcess: print("[ERROR] WFWorkflow.connect(): unknown input !")
  54. if not name:
  55. name = name=outputProcess.name+"->"+inputProcess.name
  56. connector = WFConnector.WFConnector(name=name)
  57. # link components
  58. outputProcess.outputs.append(connector)
  59. inputProcess.inputs.append(connector)
  60. connector.outputOf = outputProcess
  61. connector.inputOf = inputProcess
  62. self.connectors.append(connector)
  63. return connector
  64. def setRoot(self,p):
  65. p = self.getByName(p) if isinstance(p,str) else p
  66. if not p: print("[ERROR] WFWorkflow.setRoot(): unknown process !")
  67. connector = WFConnector.WFConnector(name="root"+"->"+p.name)
  68. connector.isRoot = True
  69. self.connectors.append(connector)
  70. self.root = connector
  71. # link components
  72. p.inputs.append(connector)
  73. connector.inputOf = p
  74. return connector
  75. def setLeaf(self,p):
  76. p = self.getByName(p) if isinstance(p,str) else p
  77. if not p: print("[ERROR] WFWorkflow.setLeaf(): unknown process !")
  78. connector = WFConnector.WFConnector(name=p.name+"->"+"leaf")
  79. connector.isLeaf = True
  80. self.connectors.append(connector)
  81. self.leafs.append(connector)
  82. # link components
  83. p.outputs.append(connector)
  84. connector.outputOf = p
  85. return connector
  86. # ---------------------------------------------
  87. # utils method
  88. # ---------------------------------------------
  89. def checkName(self, name):
  90. res = []
  91. for p in self.processes:
  92. if p.name == name: res.append(p)
  93. for i in self.connectors:
  94. if i.name == name: res.append(i)
  95. return res
  96. def getByName(self, name):
  97. res = None
  98. asName = self.checkName(name)
  99. if len(asName)>0: res = asName[0]
  100. return res
  101. # ---------------------------------------------
  102. # computing
  103. # ---------------------------------------------
  104. def compute(self, input=None):
  105. #
  106. #
  107. if not input:
  108. filename = easygui.fileopenbox(msg="select image.")
  109. print("selected image:", filename)
  110. # reading image
  111. input= MIMG.Image.readImage(filename)
  112. if input.isHDR(): input = input.removeZeros(0.5)
  113. self.root.image=input
  114. # main loop:
  115. # -------------------------------------------------
  116. awaitingProcess = []
  117. # reset ready for compute
  118. for con in self.connectors:
  119. con.ready = False
  120. # start with root
  121. self.root.ready = True
  122. # put all processes in awaiting Process
  123. awaitingProcess = self.processes
  124. # -------------------------------------------------
  125. while len(awaitingProcess)>0 :
  126. # ---------------------------------------------
  127. # looking for first process in list that all inputs are ready
  128. pReady = None
  129. pNotReady = []
  130. for p in awaitingProcess:
  131. if p.isReady() and (not pReady):
  132. # take first ready for computation
  133. pReady = p
  134. else: # not ready or firest ready already find
  135. pNotReady.append(p)
  136. awaitingProcess = pNotReady
  137. pReady.compute()
  138. def compile(self):
  139. # check process input and output
  140. print("+-----------------------------------")
  141. print("| COMPILE WORKFLOW")
  142. print("+-----------------------------------")
  143. print("| check process input and output")
  144. print("+-----------------------------------")
  145. noInput =[]
  146. noOutput =[]
  147. for p in self.processes:
  148. print("| process:",p.name)
  149. print("|-----------------------------------")
  150. print("| input(s) -> ")
  151. if len(p.inputs) == 0:
  152. # no input
  153. noInput.append(p)
  154. print("| ", "no input -> root")
  155. for input in p.inputs:
  156. print("| ",input.name)
  157. print("| output(s) -> ")
  158. if len(p.outputs) == 0:
  159. # no output
  160. noOutput.append(p)
  161. print("| ", "no output -> leaf")
  162. for output in p.outputs:
  163. print("| ",output.name)
  164. print("+-----------------------------------")
  165. # root
  166. if self.root and (len(noInput)== 0):
  167. print("| root:",self.root.name, ": OK")
  168. print("+-----------------------------------")
  169. if (not self.root) and (len(noInput)== 1):
  170. # add Root
  171. self.setRoot(noInput[0])
  172. print("| root -> pocess:",noInput[0].name)
  173. print("| root:",self.root.name, ": OK")
  174. print("+-----------------------------------")
  175. noInput.pop()
  176. if not self.root or (len(noInput)>1):
  177. print("[ERROR] root error !")
  178. # leaf
  179. for p in noOutput:
  180. self.setLeaf(p)
  181. print("| process:",p.name, "- > leaf")
  182. if len(self.leafs)>0:
  183. for c in self.leafs:
  184. print("+-----------------------------------")
  185. print("| leaf:",c.name)
  186. print("| leaf: OK")
  187. print("+-----------------------------------")
  188. else:
  189. print("[ERROR] leaf error !")
  190. # simulate processing
  191. # -------------------------------------------------
  192. print("| check workflow")
  193. print("+-----------------------------------")
  194. awaitingProcess = []
  195. # reset ready for compute
  196. for con in self.connectors:
  197. con.ready = False
  198. # start with root
  199. self.root.ready = True
  200. # put all processes in awaiting Process
  201. awaitingProcess = self.processes
  202. # -------------------------------------------------
  203. while len(awaitingProcess)>0 :
  204. # ---------------------------------------------
  205. # looking for first process in list that all inputs are ready
  206. pReady = None
  207. pNotReady = []
  208. for p in awaitingProcess:
  209. if p.isReady() and (not pReady):
  210. # take first ready for computation
  211. pReady = p
  212. else: # not ready or firest ready already find
  213. pNotReady.append(p)
  214. awaitingProcess = pNotReady
  215. # 'compute'
  216. print("+-----------------------------------")
  217. print("| computing:", pReady.name)
  218. # set ouputs to ready
  219. for con in pReady.outputs:
  220. con.ready = True
  221. print("| +---->", con.name, "ready")
  222. print("+-----------------------------------")
  223. print("| check workflow: end reached: OK")
  224. print("+-----------------------------------")
  225. return self
  226. # ---------------------------------------------
  227. # read
  228. # ---------------------------------------------
  229. def readWorkflow(filename):
  230. # read filename
  231. with open(filename) as json_file:
  232. jsonData = json.load(json_file)
  233. # recover data
  234. # name
  235. wf_name = jsonData['name'] if 'name' in jsonData else filename
  236. # processes and connectors
  237. processesJSON = jsonData['processes'] if 'processes' in jsonData else []
  238. connectorsJSON = jsonData['connectors'] if 'connectors' in jsonData else []
  239. # build process and connectors
  240. error = False
  241. # create wf
  242. wf = WFWorkflow(name=wf_name)
  243. # build process
  244. for processJSON in processesJSON:
  245. # type, name, params
  246. if 'type' in processJSON :
  247. type= processJSON['type']
  248. else:
  249. print("ERROR[miam.workflow.WFWorflow.readWorkflow(",processJSON,"): no type found !]")
  250. error = True
  251. if 'name' in processJSON:
  252. process_name= processJSON['name']
  253. else:
  254. print("ERROR[miam.workflow.WFWorflow.readWorkflow(",processJSON,"): no name found !]")
  255. error = True
  256. if 'parameters' in processJSON:
  257. parameters= processJSON['parameters']
  258. else:
  259. print("ERROR[miam.workflow.WFWorflow.readWorkflow(",processJSON,"): no parameters found !]")
  260. error = True
  261. # build process
  262. if not error:
  263. # add here all new processing class
  264. # --------------------------------------------
  265. # ColorSpaceTransform
  266. # ContrastControl
  267. # Duplicate
  268. # ExposureControl
  269. # NoOp
  270. # TMO_CCTF
  271. # TMO_Lightness
  272. # TMO_Linear
  273. # Ymap
  274. # Blend
  275. # MaskSegmentPercentile
  276. # Fuse
  277. # --------------------------------------------
  278. if type =='ColorSpaceTransform':
  279. wf.addProcess(WFProcess.WFProcess(
  280. name = process_name,
  281. process=ColorSpaceTransform.ColorSpaceTransform()).setParameters(eval(parameters))
  282. )
  283. elif type == 'ContrastControl':
  284. wf.addProcess(WFProcess.WFProcess(
  285. name = process_name,
  286. process=ContrastControl.ContrastControl()).setParameters(eval(parameters))
  287. )
  288. elif type == 'Duplicate':
  289. wf.addProcess(WFProcess.WFProcess(
  290. name = process_name,
  291. process=Duplicate.Duplicate()).setParameters(eval(parameters))
  292. )
  293. elif type == 'ExposureControl':
  294. wf.addProcess(WFProcess.WFProcess(
  295. name = process_name,
  296. process=ExposureControl.ExposureControl()).setParameters(eval(parameters))
  297. )
  298. elif type == 'NoOp':
  299. wf.addProcess(WFProcess.WFProcess(
  300. name = process_name,
  301. process=NoOp.NoOp())
  302. )
  303. elif type == 'TMO_CCTF':
  304. wf.addProcess(WFProcess.WFProcess(
  305. name = process_name,
  306. process=TMO_CCTF.TMO_CCTF()).setParameters(eval(parameters))
  307. )
  308. elif type == 'TMO_Lightness':
  309. wf.addProcess(WFProcess.WFProcess(
  310. name = process_name,
  311. process=TMO_Lightness.TMO_Lightness()).setParameters(eval(parameters))
  312. )
  313. elif type == 'TMO_Linear':
  314. wf.addProcess(WFProcess.WFProcess(
  315. name = process_name,
  316. process=TMO_Linear.TMO_Linear()).setParameters(eval(parameters))
  317. )
  318. elif type == 'Ymap':
  319. wf.addProcess(WFProcess.WFProcess(
  320. name = process_name,
  321. process=Ymap.Ymap()).setParameters(eval(parameters))
  322. )
  323. elif type == 'Blend':
  324. wf.addProcess(WFProcess.WFProcess(
  325. name = process_name,
  326. process=Blend.Blend()).setParameters(eval(parameters))
  327. )
  328. elif type == 'MaskSegmentPercentile':
  329. wf.addProcess(WFProcess.WFProcess(
  330. name = process_name,
  331. process=MaskSegmentPercentile.MaskSegmentPercentile()).setParameters(eval(parameters))
  332. )
  333. elif type == 'Fuse':
  334. wf.addProcess(WFProcess.WFProcess(
  335. name = process_name,
  336. process=Fuse.Fuse()).setParameters(eval(parameters))
  337. )
  338. else:
  339. print("ERROR[miam.workflow.WFWorflow.readWorkflow(",filename,"): unkown process type: ",type," !]")
  340. error = True
  341. # build connector
  342. for connectorJSON in connectorsJSON:
  343. # outputProcess -> inputProcess
  344. if 'outputProcess' in connectorJSON :
  345. outputProcess = connectorJSON['outputProcess']
  346. else:
  347. print("ERROR[miam.workflow.WFWorflow.readWorkflow(",connectorJSON,"): no outputProcess found !]")
  348. error = True
  349. if 'inputProcess' in connectorJSON:
  350. inputProcess= connectorJSON['inputProcess']
  351. else:
  352. print("ERROR[miam.workflow.WFWorflow.readWorkflow(",connectorJSON,"): no inputProcess found !]")
  353. error = True
  354. # find output and input
  355. pout = wf.getByName(outputProcess)
  356. pin = wf.getByName(inputProcess)
  357. wf.connect(outputProcess=pout, inputProcess=pin, name=outputProcess+"->"+inputProcess)
  358. # compile and return
  359. return wf.compile()
  360. # ---------------------------------------------
  361. # display
  362. # ---------------------------------------------
  363. def display(self, withHistogram=True):
  364. # number of leaf image
  365. nbRes = len(self.leafs)
  366. # dual display
  367. dd = 2 if withHistogram else 1
  368. fig, ax = plt.subplots(2,nbRes*dd)
  369. # display input
  370. self.root.image.plot(ax[0,0])
  371. if withHistogram:
  372. if self.root.image.isHDR():
  373. ch = miam.image.channel.channel.Y
  374. else:
  375. ch = miam.image.channel.channel.L
  376. MHIST.Histogram.build(self.root.image, ch).plot(ax[0,1])
  377. # display results
  378. for i, leaf in enumerate(self.leafs):
  379. leaf.image.plot(ax[1,i*dd])
  380. if withHistogram:
  381. if leaf.image.isHDR():
  382. ch = miam.image.channel.channel.Y
  383. else:
  384. ch = miam.image.channel.channel.L
  385. MHIST.Histogram.build(leaf.image, ch).plot(ax[1,i*dd+1])
  386. plt.show(block=True)