view.useCase.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. # ------------------------------------------------------------------------------------------
  2. # uHDR project 2020
  3. # ------------------------------------------------------------------------------------------
  4. # author: remi.cozot@univ-littoral.fr
  5. # ------------------------------------------------------------------------------------------
  6. # Qt import
  7. from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QMainWindow, QSplitter, QFrame, QDockWidget
  8. from PyQt5.QtWidgets import QSplitter, QFrame, QSlider, QRadioButton
  9. from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QGridLayout, QLayout, QScrollArea, QFormLayout
  10. from PyQt5.QtWidgets import QPushButton, QTextEdit,QLineEdit, QRadioButton
  11. from PyQt5.QtWidgets import QAction
  12. from PyQt5.QtGui import QPixmap, QImage, QDoubleValidator
  13. from PyQt5.QtCore import Qt
  14. from PyQt5 import QtCore, QtWidgets
  15. from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
  16. from matplotlib.figure import Figure
  17. from datetime import datetime
  18. import numpy as np
  19. import hdrCore.image
  20. import math, enum
  21. from . import controller
  22. # ------------------------------------------------------------------------------------------
  23. # ------------------------------------------------------------------------------------------
  24. class ImageWidgetView(QWidget):
  25. """description of class"""
  26. def __init__(self,controller,colorData = None):
  27. super().__init__()
  28. self.controller = controller
  29. self.label = QLabel(self) # create a QtLabel for pixmap
  30. if not isinstance(colorData, np.ndarray): colorData = ImageWidgetView.emptyImageColorData()
  31. # self.colorData = colorData # image content attributes
  32. self.setPixmap(colorData)
  33. def resize(self):
  34. self.label.resize(self.size())
  35. self.label.setPixmap(self.imagePixmap.scaled(self.size(),Qt.KeepAspectRatio))
  36. def resizeEvent(self,event):
  37. self.resize()
  38. super().resizeEvent(event)
  39. def setPixmap(self,colorData):
  40. if not isinstance(colorData, np.ndarray):
  41. colorData = ImageWidgetView.emptyImageColorData()
  42. # self.colorData = colorData
  43. height, width, channel = colorData.shape # compute pixmap
  44. bytesPerLine = channel * width
  45. # clip
  46. colorData[colorData>1.0] = 1.0
  47. colorData[colorData<0.0] = 0.0
  48. ##
  49. #t_begin = datetime.now()
  50. ##
  51. qImg = QImage((colorData*255).astype(np.uint8), width, height, bytesPerLine, QImage.Format_RGB888) # QImage
  52. self.imagePixmap = QPixmap.fromImage(qImg)
  53. self.resize()
  54. ###
  55. ##t_end = datetime.now()
  56. ##print(" >> ImageWidgetView.setPixmap()","[",t_end-t_begin,"ms]")
  57. ###
  58. return self.imagePixmap
  59. def setQPixmap(self, qPixmap):
  60. self.imagePixmap = qPixmap
  61. self.resize()
  62. def emptyImageColorData(): return np.ones((90,160,3))*(220/255) # create default image (backgorund GUI 240)
  63. # ------------------------------------------------------------------------------------------
  64. class FigureWidget(FigureCanvas):
  65. """ Matplotlib Figure Widget """
  66. def __init__(self, parent=None, width=5, height=5, dpi=100):
  67. # create Figure
  68. fig = Figure(figsize=(width, height), dpi=dpi)
  69. self.axes = fig.add_subplot(111)
  70. FigureCanvas.__init__(self, fig) # explicite call of super constructor
  71. self.setParent(parent)
  72. FigureCanvas.updateGeometry(self)
  73. # ------------------------------------------------------------------------------------------
  74. # ------------------------------------------------------------------------------------------
  75. class ImageGalleryView(QSplitter):
  76. """
  77. ImageGallery(QSplitter)
  78. +-------------------------------------------+
  79. | +----+ +----+ +----+ +----+ +----+ +----+ | \
  80. | |ImgW| |ImgW| |ImgW| |ImgW| |ImgW| |ImgW| | |
  81. | +----+ +----+ +----+ +----+ +----+ +----+ | |
  82. | +----+ +----+ +----+ +----+ +----+ +----+ | |
  83. | |ImgW| |ImgW| |ImgW| |ImgW| |ImgW| |ImgW| | |
  84. | +----+ +----+ +----+ +----+ +----+ +----+ | |
  85. | +----+ +----+ +----+ +----+ +----+ +----+ | > GridLayout
  86. | |ImgW| |ImgW| |ImgW| |ImgW| |ImgW| |ImgW| | |
  87. | +----+ +----+ +----+ +----+ +----+ +----+ | |
  88. | +----+ +----+ +----+ +----+ +----+ +----+ | |
  89. | |ImgW| |ImgW| |ImgW| |ImgW| |ImgW| |ImgW| | |
  90. | +----+ +----+ +----+ +----+ +----+ +----+ | /
  91. +-------------------------------------------+ < splitter
  92. | [<] [1x1][3x2][6x4][9x6][page number] [>] | [pushButton] HorizontalLayout
  93. +-------------------------------------------+
  94. """
  95. def __init__(self,controller_=None,shapeMode=None):
  96. print(" [VIEW] >> ImageGalleryView.__init__(",")")
  97. super().__init__(Qt.Vertical)
  98. self.controller= controller_
  99. self.shapeMode = controller.GalleryMode._3x2 if not shapeMode else shapeMode # default display mode
  100. self.pageNumber =0
  101. self.imagesControllers = []
  102. self.images = QFrame()
  103. self.images.setFrameShape(QFrame.StyledPanel)
  104. self.imagesLayout = QGridLayout()
  105. self.images.setLayout(self.imagesLayout)
  106. self.buildGridLayoutWidgets()
  107. self.previousPageButton = QPushButton('<')
  108. self.previousPageButton.clicked.connect(self.controller.callBackButton_previousPage)
  109. self._1x1Button = QPushButton('1x1')
  110. self._1x1Button.clicked.connect(self.controller.callBackButton_1x1)
  111. self._2x1Button = QPushButton('2x1')
  112. self._2x1Button.clicked.connect(self.controller.callBackButton_2x1)
  113. self._3x2Button = QPushButton('3x2')
  114. self._3x2Button.clicked.connect(self.controller.callBackButton_3x2)
  115. self._6x4Button = QPushButton('6x4')
  116. self._6x4Button.clicked.connect(self.controller.callBackButton_6x4)
  117. self._9x6Button = QPushButton('9x6')
  118. self._9x6Button.clicked.connect(self.controller.callBackButton_9x6)
  119. self.nextPageButton = QPushButton('>')
  120. self.nextPageButton.clicked.connect(self.controller.callBackButton_nextPage)
  121. self.pageNumberLabel = QLabel(str(self.pageNumber)+"/ ...")
  122. self.buttons = QWidget()
  123. self.buttonsLayout = QHBoxLayout()
  124. self.buttons.setLayout(self.buttonsLayout)
  125. self.buttonsLayout.addWidget(self.previousPageButton)
  126. self.buttonsLayout.addWidget(self._1x1Button)
  127. self.buttonsLayout.addWidget(self._2x1Button)
  128. self.buttonsLayout.addWidget(self._3x2Button)
  129. self.buttonsLayout.addWidget(self._6x4Button)
  130. self.buttonsLayout.addWidget(self._9x6Button)
  131. self.buttonsLayout.addWidget(self.nextPageButton)
  132. self.buttonsLayout.addWidget(self.pageNumberLabel)
  133. self.addWidget(self.images)
  134. self.addWidget(self.buttons)
  135. self.setSizes([1525,82])
  136. def changePageNumber(self,step):
  137. print(" [VIEW] >> ImageGalleryView.changePageNumber(",step,")")
  138. nbImagePerPage = controller.GalleryMode.nbRow(self.shapeMode)*controller.GalleryMode.nbCol(self.shapeMode)
  139. maxPage = ((len(self.controller.model.processPipes)-1)//nbImagePerPage) + 1
  140. oldPageNumber = self.pageNumber
  141. if (self.pageNumber+step) > maxPage-1:
  142. self.pageNumber = 0
  143. elif (self.pageNumber+step) <0:
  144. self.pageNumber = maxPage-1
  145. else:
  146. self.pageNumber = self.pageNumber+step
  147. self.controller.model.loadPage(self.pageNumber)
  148. self.updateImages()
  149. print(" [VIEW] >> ImageGalleryView.changePageNumber(currentPage:",self.pageNumber," | max page:",maxPage,")")
  150. def updateImages(self):
  151. print(" [VIEW] >> ImageGalleryView.updateImages(",")")
  152. """ update images content """
  153. nbImagePerPage = controller.GalleryMode.nbRow(self.shapeMode)*controller.GalleryMode.nbCol(self.shapeMode)
  154. maxPage = ((len(self.controller.model.processPipes)-1)//nbImagePerPage) + 1
  155. index=0
  156. for i in range(controller.GalleryMode.nbRow(self.shapeMode)):
  157. for j in range(controller.GalleryMode.nbCol(self.shapeMode)):
  158. # get image controllers
  159. iwc = self.imagesControllers[index]
  160. idxImage = index+nbImagePerPage*self.pageNumber # image index
  161. if (idxImage) <len(self.controller.model.processPipes):
  162. iwc.setImage(self.controller.model.processPipes[idxImage].getImage())
  163. else: iwc.view.setPixmap(ImageWidgetView.emptyImageColorData())
  164. index +=1
  165. self.pageNumberLabel.setText(str(self.pageNumber)+"/"+str(maxPage-1))
  166. def resetGridLayoutWidgets(self):
  167. print(" [VIEW] >> ImageGalleryView.resetGridLayoutWidgets(",")")
  168. for w in self.imagesControllers:
  169. self.imagesLayout.removeWidget(w.view)
  170. w.view.deleteLater()
  171. self.imagesControllers = []
  172. def buildGridLayoutWidgets(self):
  173. print(" [VIEW] >> ImageGalleryView.buildGridLayoutWidgets(",")")
  174. imageIndex = 0
  175. for i in range(controller.GalleryMode.nbRow(self.shapeMode)):
  176. for j in range(controller.GalleryMode.nbCol(self.shapeMode)):
  177. iwc = controller.ImageWidgetController(id=imageIndex)
  178. self.imagesControllers.append(iwc)
  179. self.imagesLayout.addWidget(iwc.view,i,j)
  180. imageIndex +=1
  181. def wheelEvent(self, event):
  182. print(" [EVENT] >> ImageGalleryView.wheelEvent(",")")
  183. if event.angleDelta().y() < 0 :
  184. self.changePageNumber(+1)
  185. if self.shapeMode == controller.GalleryMode._1x1 :
  186. self.controller.selectImage(0)
  187. else :
  188. self.changePageNumber(-1)
  189. if self.shapeMode == controller.GalleryMode._1x1 :
  190. self.controller.selectImage(0)
  191. event.accept()
  192. def mousePressEvent(self,event):
  193. print(" [EVENT] >> ImageGalleryView.mousePressEvent(",")")
  194. # self.childAt(event.pos()) return QLabel .parent() should be ImageWidget object
  195. if isinstance(self.childAt(event.pos()).parent(),ImageWidgetView):
  196. id = self.childAt(event.pos()).parent().controller.id()
  197. else:
  198. id = -1
  199. if id != -1: # an image is clicked select it!
  200. self.controller.selectImage(id)
  201. event.accept()
  202. # ------------------------------------------------------------------------------------------
  203. class AppView(QMainWindow):
  204. """
  205. MainWindow(Vue)
  206. """
  207. def __init__(self, appViewController = None, shapeMode=None):
  208. super().__init__()
  209. # --------------------
  210. scale = 0.8
  211. # --------------------
  212. # attributes
  213. self.controller = appViewController
  214. self.setWindowGeometry(scale=scale)
  215. self.setWindowTitle('uHDR - Rémi Cozot (c) 2020') # title
  216. self.statusBar().showMessage('Welcome to hdrCore!') # status bar
  217. self.topContainer = QWidget()
  218. self.topLayout = QHBoxLayout()
  219. self.imageGalleryController = controller.ImageGalleryController(self)
  220. self.topLayout.addWidget(self.imageGalleryController.view)
  221. self.topContainer.setLayout(self.topLayout)
  222. self.setCentralWidget(self.topContainer)
  223. # ----------------------------------
  224. #self.dock = MultiDockView(None)
  225. self.dock = controller.MultiDockController(self)
  226. self.addDockWidget(Qt.RightDockWidgetArea,self.dock.view)
  227. self.resizeDocks([self.dock.view],[self.controller.screenSize[0].width()*scale//4],Qt.Horizontal)
  228. # ----------------------------------
  229. # build menu
  230. self.buildFileMenu()
  231. self.buildDockMenu()
  232. self.buildDisplayHDR()
  233. self.buildInfoMenu()
  234. # ------------------------------------------------------------------------------------------
  235. def resizeEvent(self, event): super().resizeEvent(event)
  236. # ------------------------------------------------------------------------------------------
  237. def setWindowGeometry(self, scale=0.8):
  238. width, height = self.controller.screenSize[0].width(), self.controller.screenSize[0].height()
  239. self.setGeometry(0, 0, math.floor(width*scale), math.floor(height*scale))
  240. # ------------------------------------------------------------------------------------------
  241. def buildFileMenu(self):
  242. menubar = self.menuBar()# get menubar
  243. fileMenu = menubar.addMenu('&File')# file menu
  244. selectDir = QAction('&Select directory', self)
  245. selectDir.setShortcut('Ctrl+O')
  246. selectDir.setStatusTip('Select a directory')
  247. selectDir.triggered.connect(self.controller.callBackSelectDir)
  248. fileMenu.addAction(selectDir)
  249. # ------------------------------------------------------------------------------------------
  250. def buildInfoMenu(self):
  251. menubar = self.menuBar()# get menubar
  252. helpMenu = menubar.addMenu('&Help')# file menu
  253. debug = QAction('&Debug', self)
  254. debug.setShortcut('Ctrl+D')
  255. debug.setStatusTip('[DEBUG] info.')
  256. debug.triggered.connect(self.controller.callBackInfo)
  257. helpMenu.addAction(debug)
  258. # ------------------------------------------------------------------------------------------
  259. def buildDisplayHDR(self):
  260. menubar = self.menuBar()# get menubar
  261. displayHDRmenu = menubar.addMenu('&Display HDR')# file menu
  262. displayHDR = QAction('&HDR display', self)
  263. displayHDR.setShortcut('Ctrl+H')
  264. displayHDR.setStatusTip('[Display HDR] opening HDR windows')
  265. displayHDR.triggered.connect(self.controller.callBackDisplayHDR)
  266. displayHDRmenu.addAction(displayHDR)
  267. # ------------------------------------
  268. closeHDR = QAction('&HDR close', self)
  269. closeHDR.setShortcut('Ctrl+K')
  270. closeHDR.setStatusTip('[Display HDR] close HDR windows')
  271. closeHDR.triggered.connect(self.controller.callBackCloseDisplayHDR)
  272. displayHDRmenu.addAction(closeHDR)
  273. # ------------------------------------------------------------------------------------------
  274. def buildDockMenu(self):
  275. menubar = self.menuBar()# get menubar
  276. dockMenu = menubar.addMenu('&Dock')# file menu
  277. info = QAction('&Info/Metadata', self)
  278. info.setShortcut('Ctrl+I')
  279. info.setStatusTip('Image info.')
  280. info.triggered.connect(self.dock.switch)
  281. dockMenu.addAction(info)
  282. edit = QAction('&Edit', self)
  283. edit.setShortcut('Ctrl+E')
  284. edit.setStatusTip('Edit image')
  285. edit.triggered.connect(self.dock.switch)
  286. dockMenu.addAction(edit)
  287. # ------------------------------------------------------------------------------------------
  288. def closeEvent(self, event):
  289. print(" [CB] >> AppView.closeEvent()>> ... closing")
  290. self.imageGalleryController.save()
  291. # ------------------------------------------------------------------------------------------
  292. class ImageInfoView(QSplitter):
  293. def __init__(self, _controller):
  294. print(" [VIEW] >> ImageInfoView.__init__(",")")
  295. super().__init__(Qt.Vertical)
  296. self.controller = _controller
  297. self.imageWidgetController = controller.ImageWidgetController()
  298. self.layout = QFormLayout()
  299. # ---------------------------------------------------
  300. self.imageName = AdvanceLineEdit(" name:", " ........ ", self.layout, callBack=None)
  301. self.imagePath = AdvanceLineEdit(" path:", " ........ ", self.layout, callBack=None)
  302. self.imageSize = AdvanceLineEdit(" size (pixel):", ".... x .... ", self.layout, callBack=None)
  303. self.imageDynamicRange = AdvanceLineEdit(" dynamic range (f-stops)", " ........ ", self.layout, callBack=None)
  304. self.colorSpace = AdvanceLineEdit(" color space:", " ........ ", self.layout, callBack=None)
  305. self.imageType = AdvanceLineEdit(" type:", " ........ ", self.layout, callBack=None)
  306. self.imageBPS = AdvanceLineEdit(" bits per sample:", " ........ ", self.layout, callBack=None)
  307. self.imageExpoTime = AdvanceLineEdit(" exposure time:", " ........ ", self.layout, callBack=None)
  308. self.imageFNumber = AdvanceLineEdit("f-number:", " ........ ", self.layout, callBack=None)
  309. self.imageISO = AdvanceLineEdit(" ISO:", " ........ ", self.layout, callBack=None)
  310. self.imageCamera = AdvanceLineEdit(" camera:", " ........ ", self.layout, callBack=None)
  311. self.imageSoftware = AdvanceLineEdit(" software:", " ........ ", self.layout, callBack=None)
  312. self.imageLens = AdvanceLineEdit(" lens:", " ........ ", self.layout, callBack=None)
  313. self.imageFocalLength = AdvanceLineEdit(" focal length:", " ........ ", self.layout, callBack=None)
  314. # ---------------------------------------------------
  315. # ---------------------------------------------------
  316. line = QFrame()
  317. line.setFrameShape(QFrame.HLine)
  318. self.layout.addRow(line)
  319. # ---------------------------------------------------
  320. self.layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
  321. self.scroll = QScrollArea()
  322. self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
  323. self.container = QLabel()
  324. self.container.setLayout(self.layout)
  325. self.scroll.setWidget(self.container)
  326. self.scroll.setWidgetResizable(True)
  327. self.addWidget(self.imageWidgetController.view)
  328. self.addWidget(self.scroll)
  329. self.setSizes([60,40])
  330. def setImage(self,image):
  331. print(" [VIEW] >> ImageInfoView.setImage(",image.name,")")
  332. if image.metadata.metadata['filename'] != None: self.imageName.setText(image.metadata.metadata['filename'])
  333. else: self.imageName.setText(" ........ ")
  334. if image.metadata.metadata['path'] != None: self.imagePath.setText(image.metadata.metadata['path'])
  335. else: self.imagePath.setText(" ........ ")
  336. if image.metadata.metadata['exif']['Image Width'] != None: self.imageSize.setText(str(image.metadata.metadata['exif']['Image Width'])+" x "+ str(image.metadata.metadata['exif']['Image Height']))
  337. else: self.imageSize.setText(" ........ ")
  338. if image.metadata.metadata['exif']['Dynamic Range (stops)'] != None: self.imageDynamicRange.setText(str(image.metadata.metadata['exif']['Dynamic Range (stops)']))
  339. else: self.imageDynamicRange.setText(" ........ ")
  340. if image.metadata.metadata['exif']['Color Space'] != None: self.colorSpace.setText(image.metadata.metadata['exif']['Color Space'])
  341. else: self.imageName.setText(" ........ ")
  342. if image.type != None: self.imageType.setText(str(image.type))
  343. else: self.colorSpace.setText(" ........ ")
  344. if image.metadata.metadata['exif']['Bits Per Sample'] != None: self.imageBPS.setText(str(image.metadata.metadata['exif']['Bits Per Sample']))
  345. else: self.imageBPS.setText(" ........ ")
  346. if image.metadata.metadata['exif']['Exposure Time'] != None: self.imageExpoTime.setText(str(image.metadata.metadata['exif']['Exposure Time'][0])+" / " + str(image.metadata.metadata['exif']['Exposure Time'][1]))
  347. else: self.imageExpoTime.setText(" ........ ")
  348. if image.metadata.metadata['exif']['F Number'] != None: self.imageFNumber.setText(str(image.metadata.metadata['exif']['F Number'][0]))
  349. else: self.imageFNumber.setText(" ........ ")
  350. if image.metadata.metadata['exif']['ISO'] != None: self.imageISO.setText(str(image.metadata.metadata['exif']['ISO']))
  351. else: self.imageISO.setText(" ........ ")
  352. if image.metadata.metadata['exif']['Camera'] != None: self.imageCamera.setText(image.metadata.metadata['exif']['Camera'])
  353. else: self.imageCamera.setText(" ........ ")
  354. if image.metadata.metadata['exif']['Software'] != None: self.imageSoftware.setText(image.metadata.metadata['exif']['Software'])
  355. else: self.imageSoftware.setText(" ........ ")
  356. if image.metadata.metadata['exif']['Lens'] != None: self.imageLens.setText(image.metadata.metadata['exif']['Lens'])
  357. else: self.imageLens.setText(" ........ ")
  358. if image.metadata.metadata['exif']['Focal Length'] != None: self.imageFocalLength.setText(str(image.metadata.metadata['exif']['Focal Length'][0]))
  359. else: self.imageFocalLength.setText(" ........ ")
  360. # ---------------------------------------------------
  361. return self.imageWidgetController.setImage(image)
  362. # ------------------------------------------------------------------------------------------
  363. class AdvanceLineEdit(object):
  364. def __init__(self, labelName, defaultText, layout, callBack=None):
  365. self.label = QLabel(labelName)
  366. self.lineEdit =QLineEdit(defaultText)
  367. if callBack: self.lineEdit.textChanged.connect(callBack)
  368. layout.addRow(self.label,self.lineEdit)
  369. def setText(self, txt): self.lineEdit.setText(txt)
  370. # ------------------------------------------------------------------------------------------
  371. class EditImageView(QSplitter):
  372. def __init__(self, _controller):
  373. print(" [VIEW] >> EditImageView.__init__(",")")
  374. super().__init__(Qt.Vertical)
  375. self.controller = _controller
  376. self.imageWidgetController = controller.ImageWidgetController()
  377. self.layout = QVBoxLayout()
  378. self.exposure = controller.AdvanceSliderController(self, "exposure",0,(-10,+10),0.25)
  379. # call back functions
  380. self.exposure.callBackAutoPush = self.autoExposure
  381. self.exposure.callBackValueChange = self.changeExposure
  382. self.layout.addWidget(self.exposure.view)
  383. self.contrast = controller.AdvanceSliderController(self, "contrast",0,(-100,+100),1)
  384. # call back functions
  385. self.contrast.callBackAutoPush = self.autoContrast
  386. self.contrast.callBackValueChange = self.changeContrast
  387. self.layout.addWidget(self.contrast.view)
  388. self.layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
  389. self.scroll = QScrollArea()
  390. self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
  391. self.container = QLabel()
  392. self.container.setLayout(self.layout)
  393. self.scroll.setWidget(self.container)
  394. self.scroll.setWidgetResizable(True)
  395. self.addWidget(self.imageWidgetController.view)
  396. self.addWidget(self.scroll)
  397. self.setSizes([60,40])
  398. def setImage(self,image):
  399. print(" [VIEW] >> EditImageView.setImage(",image.name,")")
  400. return self.imageWidgetController.setImage(image)
  401. def autoExposure(self):
  402. print(" [CB] >> EditImageView.autoExposure(",")")
  403. self.controller.autoExposure()
  404. pass
  405. def changeExposure(self, value):
  406. print(" [CB] >> EditImageView.changeExposure(",")")
  407. self.controller.changeExposure(value)
  408. pass
  409. def autoContrast(self):
  410. print(" [CB] >> EditImageView.autoContrast(",")")
  411. pass
  412. def changeContrast(self, value):
  413. print(" [CB] >> EditImageView.changeContrast(",")")
  414. self.controller.changeContrast(value)
  415. pass
  416. def setProcessPipe(self, processPipe):
  417. print(" [VIEW] >> EditImageView.setProcessPipe(",")")
  418. # exposure
  419. # recover value in pipe and restore it
  420. id = processPipe.getProcessNodeByName("exposure")
  421. value = processPipe.getParameters(id)
  422. self.exposure.setValue(value['EV'], callBackActive = False)
  423. # contrast
  424. # recover value in pipe and restore it
  425. id = processPipe.getProcessNodeByName("contrast")
  426. value = processPipe.getParameters(id)
  427. self.contrast.setValue(value['contrast'], callBackActive = False)
  428. # ------------------------------------------------------------------------------------------
  429. class MultiDockView(QDockWidget):
  430. def __init__(self, _controller):
  431. print(" [VIEW] >> MultiDockView.__init__(",")")
  432. super().__init__("Image Edit/Info")
  433. self.controller = _controller
  434. self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
  435. self.childControllers = [controller.EditImageController(self), controller.ImageInfoController(self)]
  436. self.childController = self.childControllers[0]
  437. self.active = 0
  438. self.setWidget(self.childController.view)
  439. self.repaint()
  440. def switch(self):
  441. print(" [VIEW] >> MultiDockView.switch(",")")
  442. self.active = (self.active+1)%len(self.childControllers)
  443. self.childController.view.deleteLater()
  444. self.childController = self.childControllers[self.active]
  445. # rebuild view
  446. processPipe = self.controller.parent.imageGalleryController.getSelectedProcessPipe()
  447. self.childController.buildView(processPipe)
  448. self.setWidget(self.childController.view)
  449. self.repaint()
  450. def setProcessPipe(self, processPipe):
  451. print(" [VIEW] >> MultiDockView.setProcessPipe(",processPipe.getImage().name,")")
  452. self.childController.setProcessPipe(processPipe)
  453. # ------------------------------------------------------------------------------------------
  454. class AdvanceSliderView(QFrame):
  455. def __init__(self, controller, name,defaultValue, range, step):
  456. super().__init__()
  457. self.controller = controller
  458. self.firstrow = QFrame()
  459. self.vbox = QVBoxLayout()
  460. self.hbox = QHBoxLayout()
  461. self.firstrow.setLayout(self.hbox)
  462. self.label= QLabel(name)
  463. self.auto = QPushButton("auto")
  464. self.editValue = QLineEdit()
  465. self.editValue.setValidator(QDoubleValidator())
  466. self.editValue.setText(str(defaultValue))
  467. self.reset = QPushButton("reset")
  468. self.hbox.addWidget(self.label)
  469. self.hbox.addWidget(self.auto)
  470. self.hbox.addWidget(self.editValue)
  471. self.hbox.addWidget(self.reset)
  472. self.slider = QSlider(Qt.Horizontal)
  473. self.slider.setRange(range[0]/step,range[1]/step)
  474. self.slider.setValue(defaultValue/step)
  475. self.slider.setSingleStep(1)
  476. self.vbox.addWidget(self.firstrow)
  477. self.vbox.addWidget(self.slider)
  478. self.setLayout(self.vbox)
  479. # callBackFunctions slider/reset/auto
  480. self.slider.valueChanged.connect(self.controller.sliderChange)
  481. self.reset.clicked.connect(self.controller.reset)
  482. self.auto.clicked.connect(self.controller.auto)
  483. # ------------------------------------------------------------------------------------------
  484. class ImageUseCaseView(QSplitter):
  485. def __init__(self, _controller):
  486. print(" [VIEW] >> ImageUseCaseView.__init__(",")")
  487. super().__init__(Qt.Vertical)
  488. self.controller = _controller
  489. self.imageWidgetController = controller.ImageWidgetController()
  490. self.layout = QFormLayout()
  491. # ---------------------------------------------------
  492. #1 Inside Window with view on bright outdoor
  493. self.useCase01 = AdvanceRadioButton("[I] Window with view on bright outdoor", defaultState, layout, callBack=None)
  494. #2 Inside High Contrast and Illuminants tend to be over-exposed
  495. self.useCase02 = AdvanceRadioButton("[I] High Contrast", False, self.layout, callBack=None)
  496. #3 Inside Backlit portrait
  497. self.useCase03 = AdvanceRadioButton("[I] Backlit portrait", False, self.layout, callBack=None)
  498. #4 Outside Sun in the frame
  499. self.useCase04 = AdvanceRadioButton("[I] Sun in the frame", False, self.layout, callBack=None)
  500. #5 Outside Backlight
  501. self.useCase05 = AdvanceRadioButton("[O] Backlight", False, self.layout, callBack=None)
  502. #6 Outside Shadow and direct lighting
  503. self.useCase06 = AdvanceRadioButton("[O] Shadow and direct lighting", False, self.layout, callBack=None)
  504. #7 Outside Backlit portrait
  505. self.useCase07 = AdvanceRadioButton("[O] Backlit portrait", False, self.layout, callBack=None)
  506. #8 Outside Nature
  507. self.useCase08 = AdvanceRadioButton("[O] Nature", False, self.layout, callBack=None)
  508. #9 Outside Quite LDR
  509. self.useCase09 = AdvanceRadioButton("[O] Quite LDR", False, self.layout, callBack=None)
  510. #10 Lowlight Portrait
  511. self.useCase10 = AdvanceRadioButton("[L] Portrait", False, self.layout, callBack=None)
  512. #11 Lowlight Outside with bright illuminants
  513. self.useCase11 = AdvanceRadioButton("[L] Outside with bright illuminants", False, self.layout, callBack=None)
  514. #12 Lowlight Cityscape
  515. self.useCase12 = AdvanceRadioButton("[L] Cityscape", False, self.layout, callBack=None)
  516. #13 Lowlight Event (Concerts/night clubs/bar/restaurants)
  517. self.useCase13 = AdvanceRadioButton("[L] Event", False, self.layout, callBack=None)
  518. #14 Special cases Shiny object and Specular highlights
  519. self.useCase14 = AdvanceRadioButton("[S] Shiny object", False, self.layout, callBack=None)
  520. #15 Special cases Memory colors
  521. self.useCase15 = AdvanceRadioButton("[S] Memory colors", False, self.layout, callBack=None)
  522. #16 Special cases Scene with color checker / gray chart
  523. self.useCase16 = AdvanceRadioButton("[S] Color checker", False, self.layout, callBack=None)
  524. #17 Special cases Translucent objects and Stained glass
  525. self.useCase17 = AdvanceRadioButton("[S] Translucent objects", False, self.layout, callBack=None)
  526. #18 Special cases Traditional tone mapping failing cases
  527. self.useCase18 = AdvanceRadioButton("[S] TM failing cases", False, self.layout, callBack=None)
  528. # ---------------------------------------------------
  529. # ---------------------------------------------------
  530. line = QFrame()
  531. line.setFrameShape(QFrame.HLine)
  532. self.layout.addRow(line)
  533. # ---------------------------------------------------
  534. self.layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
  535. self.scroll = QScrollArea()
  536. self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
  537. self.container = QLabel()
  538. self.container.setLayout(self.layout)
  539. self.scroll.setWidget(self.container)
  540. self.scroll.setWidgetResizable(True)
  541. self.addWidget(self.imageWidgetController.view)
  542. self.addWidget(self.scroll)
  543. self.setSizes([60,40])
  544. def setImage(self,image):
  545. print(" [VIEW] >> ImageInfoView.setImage(",image.name,")")
  546. # ---------------------------------------------------
  547. return self.imageWidgetController.setImage(image)
  548. # ------------------------------------------------------------------------------------------
  549. class AdvanceRadioButton(object):
  550. ### https://www.tutorialspoint.com/pyqt/pyqt_qradiobutton_widget.htm
  551. def __init__(self, labelName, defaultState, layout, callBack=None):
  552. self.label = QLabel(labelName)
  553. self.radioButton =QRadioButton("test")
  554. if callBack: self.radioButton.toggled.connect(callBack)
  555. layout.addRow(self.label,self.radioButton)
  556. def setState(self, state): self.radioButton.setChecked(state)
  557. # ------------------------------------------------------------------------------------------