__init__.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. import getpass
  2. import glob
  3. import os
  4. import subprocess
  5. import shutil
  6. import threading
  7. import cv2
  8. import Activity
  9. from gi.repository import GLib, Gtk, GdkPixbuf, Pango
  10. import Export
  11. class FocusStack(Activity.Activity):
  12. def on_init(self):
  13. self.id = "FocusStack"
  14. self.name = "Focus Stack"
  15. self.subtitle = "Stack Many Images at Different Focus Points"
  16. UI_FILE = "ui/FocusStack_Activity.glade"
  17. self.builder.add_from_file(UI_FILE)
  18. self.widget = self.builder.get_object("FocusStack_main")
  19. self.stack.add_titled(self.widget, self.id, self.name)
  20. self.header_widget = self.builder.get_object("FocusStack_header")
  21. self.header_stack.add_titled(self.header_widget, self.id, self.name)
  22. # Get all UI Components
  23. self.ui = {}
  24. components = [
  25. "open_window",
  26. "open_chooser",
  27. "preview_button",
  28. "open_header",
  29. "open_open_button",
  30. "open_cancel_button",
  31. "images",
  32. "preview",
  33. "export_image",
  34. "open_pf2",
  35. "add_button",
  36. "remove_button",
  37. "scroll_window",
  38. "popovermenu"
  39. ]
  40. self.paths = []
  41. self.exporting = False
  42. for component in components:
  43. self.ui[component] = self.builder.get_object("%s_%s" % (self.id, component))
  44. self.menu_popover = self.ui["popovermenu"]
  45. self.ui["open_window"].set_transient_for(self.root)
  46. self.ui["open_window"].set_titlebar(self.ui["open_header"])
  47. # Connect Siginals
  48. self.ui["add_button"].connect("clicked", self.on_add_clicked)
  49. self.ui["remove_button"].connect("clicked", self.on_remove_clicked)
  50. self.ui["open_open_button"].connect("clicked", self.on_file_opened)
  51. self.ui["open_cancel_button"].connect("clicked", self.on_file_canceled)
  52. self.ui["preview_button"].connect("clicked", self.on_preview_clicked)
  53. self.ui["export_image"].connect("clicked", self.export_clicked)
  54. self.ui["open_pf2"].connect("clicked", self.export_pf2_clicked)
  55. self.update_enabled()
  56. def on_add_clicked(self, sender):
  57. self.ui["open_window"].show_all()
  58. def on_file_canceled(self, sender):
  59. self.ui["open_window"].hide()
  60. def on_file_opened(self, sender):
  61. self.ui["open_window"].hide()
  62. paths = self.ui["open_chooser"].get_filenames()
  63. self.show_message("Loading Images", "Please wait while PhotoFiddle loads your images…", True, True)
  64. threading.Thread(target=self.add_images, args=(paths,)).start()
  65. def update_enabled(self):
  66. enabled = len(self.get_paths()) > 0
  67. self.ui["export_image"].set_sensitive(enabled)
  68. self.ui["open_pf2"].set_sensitive(enabled)
  69. self.ui["preview_button"].set_sensitive(enabled)
  70. def add_images(self, paths):
  71. c = 0
  72. for path in paths:
  73. # Create box
  74. box = Gtk.HBox()
  75. box.set_spacing(6)
  76. im = Gtk.Image()
  77. pb = GdkPixbuf.Pixbuf.new_from_file_at_scale(path, 128, 128, True)
  78. im.set_from_pixbuf(pb)
  79. box.add(im)
  80. label = Gtk.Label()
  81. label.set_ellipsize(Pango.EllipsizeMode.START)
  82. label.set_text(path)
  83. box.add(label)
  84. box.show_all()
  85. c += 1
  86. GLib.idle_add(self.add_image_row, box, c, len(paths))
  87. GLib.idle_add(self.hide_message)
  88. def add_image_row(self, row, count, length):
  89. self.ui["images"].add(row)
  90. self.update_enabled()
  91. self.update_message_progress(count, length)
  92. def on_remove_clicked(self, sender):
  93. items = self.ui["images"].get_selected_rows()
  94. for item in items:
  95. item.destroy()
  96. self.update_enabled()
  97. def on_open(self, path):
  98. self.root.get_titlebar().set_subtitle("Focus Stack")
  99. pass
  100. def on_exit(self):
  101. if(not self.exporting):
  102. self.root.get_titlebar().set_subtitle("")
  103. self.on_init()
  104. return True
  105. return False
  106. def on_preview_clicked(self, sender):
  107. self.show_message("Generating Preview…", "PhotoFiddle is preparing to generate a preview", True, True)
  108. paths = self.get_paths()
  109. w = self.ui["scroll_window"].get_allocated_width() - 12
  110. h = self.ui["scroll_window"].get_allocated_height() - 12
  111. threading.Thread(target=self.do_preview, args=(paths, w, h)).start()
  112. def do_stack(self, images, output):
  113. GLib.idle_add(self.show_message, "Focus Stacking…", "PhotoFiddle is aligning images…", True, False)
  114. if(not os.path.exists("/tmp/stack-%s" % getpass.getuser())):
  115. os.mkdir("/tmp/stack-%s" % getpass.getuser())
  116. command = ["align_image_stack", "-m", "-a", "/tmp/stack-%s/OUT_" % getpass.getuser()] + images
  117. subprocess.call(command)
  118. aligned_list = glob.glob("/tmp/stack-%s/OUT*.tif" % getpass.getuser())
  119. aligned_list.reverse()
  120. GLib.idle_add(self.show_message, "Focus Stacking…", "PhotoFiddle is performing a focus stack…", True, False)
  121. command = ["enfuse", "--output=/tmp/stacked-%s.tiff" % getpass.getuser(), "--exposure-weight=0",
  122. "--saturation-weight=0", "--contrast-weight=1", "--hard-mask",
  123. "-v"] + aligned_list
  124. subprocess.call(command)
  125. shutil.copyfile("/tmp/stacked-%s.tiff" % getpass.getuser(), output)
  126. shutil.rmtree("/tmp/stack-%s" % getpass.getuser())
  127. os.unlink("/tmp/stacked-%s.tiff" % getpass.getuser())
  128. GLib.idle_add(self.hide_message)
  129. def get_paths(self):
  130. paths = []
  131. for item in self.ui["images"].get_children():
  132. paths += [item.get_children()[0].get_children()[1].get_text(),]
  133. return paths
  134. def do_preview(self, paths, w, h):
  135. if (not os.path.exists("/tmp/stack-%s" % getpass.getuser())):
  136. os.mkdir("/tmp/stack-%s" % getpass.getuser())
  137. length = len(paths)*3
  138. count = 0
  139. images = []
  140. for path in paths:
  141. GLib.idle_add(self.update_message_progress, count, length)
  142. im = cv2.imread(path, 2 | 1)
  143. height, width = im.shape[:2]
  144. # Get fitting size
  145. ratio = float(w) / width
  146. if (height * ratio > h):
  147. ratio = float(h) / height
  148. nw = width * ratio
  149. nh = height * ratio
  150. count += 1
  151. GLib.idle_add(self.update_message_progress, count, length)
  152. im = cv2.resize(im, (int(nw), int(nh)), interpolation=cv2.INTER_AREA)
  153. count += 1
  154. GLib.idle_add(self.update_message_progress, count, length)
  155. cv2.imwrite("/tmp/stack-%s/IN%i.tiff" % (getpass.getuser(),count), im)
  156. images += ["/tmp/stack-%s/IN%i.tiff" % (getpass.getuser(),count),]
  157. count += 1
  158. self.do_stack(images, "/tmp/stack-preview-%s.tiff" % getpass.getuser())
  159. pb = GdkPixbuf.Pixbuf.new_from_file("/tmp/stack-preview-%s.tiff" % getpass.getuser())
  160. GLib.idle_add(self.update_preview, pb)
  161. def update_preview(self, pb):
  162. self.ui["preview"].set_from_pixbuf(pb)
  163. def get_export_image(self, w, h):
  164. GLib.idle_add(self.export_started)
  165. self.do_stack(self.paths, "/tmp/export-stack-%s.tiff" % getpass.getuser())
  166. GLib.idle_add(self.show_message, "Exporting…", "PhotoFiddle is exporting the focus stack…", True, False)
  167. im = cv2.imread("/tmp/export-stack-%s.tiff" % getpass.getuser(), 2 | 1)
  168. im = cv2.resize(im, (int(w), int(h)), interpolation=cv2.INTER_AREA)
  169. return im
  170. def export_started(self):
  171. self.exporting = True
  172. self.export_state_changed()
  173. def export_complete(self, path):
  174. self.exporting = False
  175. self.export_state_changed()
  176. self.show_message("Export Complete!", "Your photo has been exported to '%s'" % path)
  177. def export_state_changed(self):
  178. self.ui["export_image"].set_sensitive(not self.exporting)
  179. self.ui["open_pf2"].set_sensitive(not self.exporting)
  180. self.ui["preview_button"].set_sensitive(not self.exporting)
  181. def export_clicked(self, sender):
  182. self.paths = self.get_paths()
  183. height, width = cv2.imread(self.paths[0]).shape[:2]
  184. Export.ExportDialog(self.root, self.builder, width, height, self.get_export_image,
  185. self.export_complete, self.paths[0])
  186. def export_pf2_clicked(self, sender):
  187. self.paths = self.get_paths()
  188. threading.Thread(target=self.do_export_pf2).start()
  189. self.export_started()
  190. def do_export_pf2(self):
  191. export_path = "".join(self.paths[0].split(".")[:-1])
  192. export_path += "_FocusStack.tiff"
  193. self.do_stack(self.paths, export_path)
  194. GLib.idle_add(self.export_pf2_complete, export_path)
  195. def export_pf2_complete(self, path):
  196. self.exporting = False
  197. self.export_state_changed()
  198. self.switch_activity("PF2", path)