Sin descripción
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. import os
  2. import nuke
  3. import nukescripts
  4. import re
  5. import subprocess
  6. import tempfile
  7. # martin@keller.io
  8. # numpy install:
  9. # https://jurajtomori.wordpress.com/2018/11/28/nuke-tip-installing-numpy-for-nuke-python-on-windows/
  10. '''noice 6.2.1.0 [359aaa66] - the Arnold denoiser
  11. Usage: noice [options] ...
  12. -i, --input %s Input image, multiple images can be included for stability, only first input image will be denoised
  13. -ef, --extraframes %d Number of frames to pad before and after for temporal stability (0 - 2, default:0)
  14. -f, --frames %d Number of frames to denoise in the sequence (default:1)
  15. -fe, --feature %s Use this AOV as a denoising feature
  16. -pr, --patchradius %d Neighborhood patch radius, size of pixel neighborhood to compare (0 - 6, default:3)
  17. -sr, --searchradius %d Search radius, higher values mean a wider search for similar pixel neighborhoods (6 - 21, default:9)
  18. -v, --variance %f Variance threshold, higher values mean more aggressive denoising (0.0 - 1.0, default:0.25)
  19. -l, --aov %s Light AOV name to be co-denoised, multiple can be specified
  20. -o, --output %s Output image
  21. -t, --threads %d Number of threads (0: use all cores, negative numbers specify number of idle cores)
  22. -irgba,--ignore_rgba Ignore RGBA layer: will not denoise it or copy it to output file
  23. -n, --notices Print copyright notices'''
  24. class denoice(KellerNukePlugin):
  25. def configurePlugin(self):
  26. menubar = nuke.menu("Nuke")
  27. self.m = menubar.addMenu("&Render")
  28. self.m.addCommand('Denoise with Noice', 'import denoice; denoice.addPanel()')
  29. def unconfigurePlugin(self):
  30. self.m.removeItem("Denoise with Noice")
  31. #######################################################################################
  32. #
  33. # GLOBALS
  34. #
  35. #######################################################################################
  36. DISABLE_AOVS = ['albedo', 'default', 'variance', 'Matte', 'matte', 'Crypto', 'crypto', 'denoise_albedo', 'P', 'N', 'Z']
  37. # Muster
  38. MSTRSERVER = "192.168.0.251"
  39. SUBMISSION_USER ="admin"
  40. PASSWORD = ""
  41. MRTOOL = "\\\\hades\\p\\env\\muster9\\Mrtool.exe"
  42. USERNAME = "admin"
  43. TEMPLATE = "2008"
  44. MUSTERFOLDER = "34758"
  45. POOL = "arnold_16thread"
  46. PSIZE = 1
  47. PRIO = 10
  48. def find_node_layers(node):
  49. node_channels = node.channels()
  50. store_layers = []
  51. for channel in node_channels:
  52. cut = channel.split(".")
  53. last = cut[0]
  54. store_layers.append(last)
  55. node_layers = list(set(store_layers))
  56. node_layers.sort()
  57. # put on top if rgb in layers
  58. if 'rgba' in node_layers:
  59. node_layers.remove("rgba")
  60. node_layers.insert(0, "rgba")
  61. return node_layers
  62. class denoicePanel(nukescripts.PythonPanel):
  63. def __init__(self):
  64. nukescripts.panels.PythonPanel.__init__(self, "KellerDenoicePanel", scrollable=True)
  65. self.node = nuke.selectedNode()
  66. self.nodeLayers = find_node_layers(self.node)
  67. self.layerKnobs = []
  68. # just disable layers
  69. disable = [lay for lay in self.nodeLayers if any(x in lay for x in DISABLE_AOVS)]
  70. num = len(self.nodeLayers)
  71. for layer in self.nodeLayers:
  72. self.layer = nuke.Boolean_Knob(layer, layer, True)
  73. self.layer.setFlag(nuke.STARTLINE)
  74. self.addKnob(self.layer)
  75. self.layerKnobs.append(self.layer)
  76. if layer in disable:
  77. self.layer.setValue(False)
  78. # interface selection
  79. self.div1 = nuke.Text_Knob("div1", " ", "")
  80. self.addKnob(self.div1)
  81. self.all = nuke.PyScript_Knob("select_all", " Select All ")
  82. self.addKnob(self.all)
  83. self.invert = nuke.PyScript_Knob("invert_selection", " Invert Selection ")
  84. self.addKnob(self.invert)
  85. self.clear = nuke.PyScript_Knob("clear_selection", " Clear Selection ")
  86. self.addKnob(self.clear)
  87. self.div2 = nuke.Text_Knob("div2", " ", "")
  88. self.addKnob(self.div2)
  89. # settings
  90. # variance
  91. self.variance = nuke.Double_Knob("variance", "Variance", arraysize=1)
  92. self.variance.setRange(0,1)
  93. self.variance.setTooltip(
  94. 'Default: 0.25\nThe strength of the filter is determined by the variance parameter, the higher the variance the more forceful the denoising will be. '
  95. 'For variance maybe 0.25 (the default) / 0.5 / 0.75 are good low/min/max values. How aggressive the Arnold denoiser is in removing noise can be controlled by '
  96. 'setting a variance threshold with the command line argument -variance (-v). The default value is 0.25, higher values will make the denoising more aggressive by '
  97. 'considering similar neighborhoods that have bigger color disparities.')
  98. self.addKnob(self.variance)
  99. self.variance.setValue(0.25)
  100. # patchradius
  101. self.patchradius = nuke.Int_Knob("patchradius", "Patchradius", arraysize=1)
  102. self.patchradius.setRange(0, 10)
  103. self.patchradius.setTooltip(
  104. 'Default: 3\nThis increases the softness of the denoising (while always preserving the features). The default is 3 (on the big side), but a low value would be maybe 0 or 1, middle 3, and high 5. '
  105. 'For every pixel, the Arnold denoiser will consider its neighborhood patch and look for other pixels with similar neighborhood patches. The radius of this neighborhood can be controlled with the -patchradius '
  106. ' (or -pr) command-line argument. The default value is set to 3, which gives a 7x7 square neighborhood.')
  107. self.addKnob(self.patchradius)
  108. self.patchradius.setValue(3)
  109. # searchradius
  110. self.searchradius = nuke.Int_Knob("searchradius", "Searchradius", arraysize=1)
  111. self.searchradius.setRange(0, 50)
  112. self.searchradius.setTooltip(
  113. 'Default: 9\nThis is the area over which similar neighborhoods are found. The higher the better, but it will increase '
  114. 'the cost of denoising. For every pixel noice will search a square area with a radius set with the '
  115. 'command line argument -searchradius (-sr). The bigger this area the bigger the denoising stability and '
  116. 'the higher the chance that similar neighborhoods to be considered will be found. The default value is 9, '
  117. 'which gives a 19x19 square neighborhood. Setting it to 21 (a search window of 42 x 42) will look over a '
  118. 'pixel area equivalent to loading 5 frames.')
  119. self.addKnob(self.searchradius)
  120. self.searchradius.setValue(9)
  121. # extraFrames
  122. self.temporalFrames = nuke.Int_Knob("temporalFrames", "ExtraFrames", arraysize=1)
  123. self.temporalFrames.setRange(0, 2)
  124. self.temporalFrames.setTooltip('This argument (-ef n or --extraframes n) specifies how many additional source frames before and after the current one should be used, for improved stability in animation sequences.\n(0-2. default:0)')
  125. self.addKnob(self.temporalFrames)
  126. self.temporalFrames.setValue(0)
  127. # threads
  128. self.threads = nuke.Int_Knob("threads", "Threads", arraysize=1)
  129. self.threads.setTooltip('Number of threads (0: use all cores, negative numbers specify number of idle cores)')
  130. self.addKnob(self.threads)
  131. self.threads.setValue(-2)
  132. # div
  133. self.div3 = nuke.Text_Knob("div3", " ", "")
  134. self.addKnob(self.div3)
  135. # filepatn in
  136. self.inputpath = nuke.File_Knob("input", "Input")
  137. self.addKnob(self.inputpath)
  138. self.inputpath.setTooltip('Needs to be in #### syntax. Not in %04d or 1001 (single frame syntax')
  139. self.inputpath.setValue(nuke.selectedNode().knobs()['file'].value().replace('%04d','####'))
  140. # filepath out
  141. self.outputpath = nuke.File_Knob("output", "Output")
  142. file = nuke.selectedNode().knobs()['file'].value().replace('%04d', '####')
  143. file = file.replace('####', 'denoise.####')
  144. self.outputpath.setValue(file)
  145. self.addKnob(self.outputpath)
  146. # set path
  147. self.setpath = nuke.PyScript_Knob("setpath", " Set Path ")
  148. self.layer.setFlag(nuke.STARTLINE)
  149. self.addKnob(self.setpath)
  150. # startframe
  151. self.startframe = nuke.Int_Knob("startframe", "Startframe")
  152. self.addKnob(self.startframe)
  153. # endframe
  154. self.endframe = nuke.Int_Knob("endframe", "Endframe")
  155. self.addKnob(self.endframe)
  156. self.endframe.clearFlag(nuke.STARTLINE)
  157. # set to input
  158. self.setToInput = nuke.PyScript_Knob("setToInput", " Set to Input ")
  159. self.addKnob(self.setToInput)
  160. selected = nuke.selectedNode()
  161. fr = selected.frameRange()
  162. start = fr.first()
  163. end = fr.last()
  164. self.startframe.setValue(start)
  165. self.endframe.setValue(end)
  166. self.setToInput.clearFlag(nuke.STARTLINE)
  167. # set framerange
  168. self.setFrameRange = nuke.PyScript_Knob("setFrameRange", " Set Framerange ")
  169. self.addKnob(self.setFrameRange)
  170. self.setFrameRange.clearFlag(nuke.STARTLINE)
  171. # filepath
  172. self.arnoldBinPath = nuke.File_Knob("arnoldBinPath", " Arnold Bin Path ")
  173. self.addKnob(self.arnoldBinPath)
  174. self.arnoldBinPath.setValue('$env(PROJECT_ROOT_3D)/000_env/arnold/_noice/bin/')
  175. self.arnoldBinPath.setTooltip('Please be sure to select the bin folder with a "/" at the end.')
  176. # div
  177. self.div4 = nuke.Text_Knob("div4", " ", "")
  178. self.addKnob(self.div4)
  179. # render mode
  180. self.renderChoice = nuke.Enumeration_Knob('renderChoice', 'Render On', ['muster', 'local_machine'])
  181. self.renderChoice.setTooltip('local_machine: creates a .bat file with all frames, saves it to the temp directory and runs it on the local machine.\nmuster: sends the job to muster.')
  182. self.addKnob(self.renderChoice)
  183. # muster pool
  184. self.musterPool = nuke.String_Knob("musterPool", "Muster Pool")
  185. self.musterPool.setValue(POOL)
  186. self.addKnob(self.musterPool)
  187. # muster pool
  188. self.musterFolder = nuke.String_Knob("musterFolder", "Muster Folder ID")
  189. self.musterFolder.setValue(MUSTERFOLDER)
  190. self.addKnob(self.musterFolder)
  191. # div
  192. self.div5 = nuke.Text_Knob("div5", " ", "")
  193. self.addKnob(self.div5)
  194. # window
  195. #height = len(self.layerKnobs) * 20
  196. height = 1200
  197. width = 800
  198. #if height > 1200:
  199. # width = 700
  200. # height = 1200
  201. self.setMinimumSize(width, height)
  202. #self.setMaximumSize(800, 1050)
  203. def knobChanged(self, knob):
  204. if knob.name() == "select_all":
  205. for layer in self.layerKnobs:
  206. layer.setValue(True)
  207. if knob.name() == "invert_selection":
  208. selected = []
  209. for layer in self.layerKnobs:
  210. if layer.value() is True:
  211. selected.append(layer)
  212. layer.setValue(False)
  213. inverse = list(set(self.layerKnobs).difference(set(selected)))
  214. for check in inverse:
  215. check.setValue(True)
  216. if knob.name() == "clear_selection":
  217. for layer in self.layerKnobs:
  218. layer.setValue(False)
  219. if knob.name() == "setToInput":
  220. selected = nuke.selectedNode()
  221. fr = selected.frameRange()
  222. start = fr.first()
  223. end = fr.last()
  224. self.startframe.setValue(start)
  225. self.endframe.setValue(end)
  226. if knob.name() == "setFrameRange":
  227. self.startframe.setValue(int(nuke.root()['first_frame'].value()))
  228. self.endframe.setValue(int(nuke.root()['last_frame'].value()))
  229. if knob.name() == "setpath":
  230. file = nuke.selectedNode().knobs()['file'].value().replace('%04d','####')
  231. file = file.replace('####','denoise.####')
  232. self.outputpath.setValue(file)
  233. if knob.name() == "renderChoice":
  234. if self.renderChoice.value() == "local_machine":
  235. self.musterPool.setVisible(False)
  236. self.musterFolder.setVisible(False)
  237. if self.renderChoice.value() == "muster":
  238. self.musterPool.setVisible(True)
  239. self.musterFolder.setVisible(True)
  240. if knob.name() == "OK":
  241. # todo: fix with regexp
  242. if self.inputpath.value() == self.outputpath.value():
  243. print("Please use a output file different from the input file! Exiting.")
  244. nuke.message("Please use a output file different from the input file! Exiting.")
  245. else:
  246. # go
  247. if self.renderChoice.value() == "local_machine":
  248. print('Denoising locally......')
  249. self.executeNoiceLocally()
  250. if self.renderChoice.value() == "muster":
  251. print('Denoising on Muster......')
  252. self.sendToMuster()
  253. def executeNoiceLocally(self):
  254. # saving selected aovs
  255. light_aov_names = []
  256. for layer in self.layerKnobs:
  257. if layer.value() is True:
  258. light_aov_names.append(layer.name())
  259. binpath = self.arnoldBinPath.evaluate().replace('\\','/')
  260. filename_input_sequence_number = re.sub(r'#+', lambda m: r'{{:0{}d}}'.format(len(m.group(0))), self.inputpath.value().replace('%04d','####'))
  261. filename_output_sequence_number = re.sub(r'#+', lambda m: r'{{:0{}d}}'.format(len(m.group(0))), self.outputpath.value().replace('%04d','####'))
  262. command = ''
  263. for i in range(self.startframe.value(), self.endframe.value() + 1):
  264. light_aov_string = ""
  265. for k in light_aov_names:
  266. print('Denoice: Info. AOV: %s') % k
  267. light_aov_string += "-aov " + str(k) + " "
  268. command += '"' + binpath + '/noice.exe' + '"' + ' ' \
  269. + '-patchradius ' + str(self.patchradius.value()) + ' ' \
  270. + '-searchradius ' + str(self.searchradius.value()) + ' ' \
  271. + '-variance ' + str(self.variance.value()) + ' ' \
  272. + light_aov_string \
  273. + '-i ' + filename_input_sequence_number.format(i) + ' ' \
  274. + '-t ' + str(self.threads.value()) + ' ' \
  275. + '-ef ' + str(self.temporalFrames.value()) + ' ' \
  276. + '-output ' + filename_output_sequence_number.format(i)
  277. print('_________________________________________________________________________________________________')
  278. print('')
  279. print('--------------------------------> Denoise Command for Frame %s <---------------------------') %i
  280. print('_________________________________________________________________________________________________')
  281. print('')
  282. print('')
  283. print('')
  284. if i is not self.endframe.value():
  285. command += "\n"
  286. print(command)
  287. temp = tempfile.NamedTemporaryFile(prefix='Noice_', suffix='.bat', delete=False)
  288. temp.write(command)
  289. print('Created temp file is:', temp.name)
  290. temp.close()
  291. filepath = 'cmd /c start "Denoice " "%s"' %temp.name
  292. p = subprocess.call(filepath, shell=True, stdout=subprocess.PIPE)
  293. def fixfilename(self,infile):
  294. infile = infile.replace("p:/", "//hades/p/")
  295. infile = infile.replace("P:/", "//hades/p/")
  296. infile = infile.replace("o:/", "//calculon/o/")
  297. infile = infile.replace("O:/", "//calculon/o/")
  298. infile = infile.replace("/", "\\")
  299. return infile
  300. def sendToMuster(self):
  301. # jobname
  302. jobname = nuke.selectedNode().knobs()['file'].value().split("/")[-1].replace('.%04d.exr',"__denoise")
  303. # aovs
  304. # saving selected aovs
  305. light_aov_names = []
  306. light_aov_string = ""
  307. for layer in self.layerKnobs:
  308. if layer.value() is True:
  309. light_aov_names.append(layer.name())
  310. for k in light_aov_names:
  311. print('Denoice: Info. AOV: %s') % k
  312. light_aov_string += "-l " + str(k) + " "
  313. light_aov_string = light_aov_string.rstrip()
  314. light_aov_string = '"' + light_aov_string + '"'
  315. a_bin_path = self.fixfilename(self.arnoldBinPath.evaluate())
  316. a_input_path = self.fixfilename(self.inputpath.evaluate().replace('%04d','####'))
  317. a_output_path = self.fixfilename(self.outputpath.evaluate().replace('%04d','####'))
  318. # building Command
  319. cmd = '%s -s %s -u %s -b -e %s -n %s' % (MRTOOL, MSTRSERVER, USERNAME, TEMPLATE, jobname)
  320. cmd += ' -pk %i -pr %i -pool "%s" -parent %i' % (int(PSIZE), int(PRIO), self.musterPool.value(), int(self.musterFolder.value()))
  321. cmd += ' -attr "NOICEBIN" %s 0' % a_bin_path
  322. cmd += ' -attr "PATCHRADIUS" %i 0' % int(self.patchradius.value())
  323. cmd += ' -attr "SEARCHRADIUS" %i 0' % int(self.searchradius.value())
  324. cmd += ' -attr "VARIANCE" %s 0' % self.variance.value()
  325. cmd += ' -sf %i' % int(self.startframe.value())
  326. cmd += ' -ef %i' % int(self.endframe.value())
  327. cmd += ' -bf %i' % 1
  328. cmd += ' -f %s' % a_input_path
  329. cmd += ' -attr "LIGHTAOVSTRING" %s 0' % light_aov_string
  330. cmd += ' -attr "TEMPORALFRAMES" %i 0' % int(self.temporalFrames.value())
  331. cmd += ' -attr "DENOISETHREADS" %i 0' % int(self.threads.value())
  332. cmd += ' -attr "OUTPUTFILE" %s 0' % a_output_path
  333. # send
  334. print (cmd)
  335. print (os.system(cmd))
  336. return
  337. def addPanel():
  338. try:
  339. selected = nuke.selectedNode()
  340. except:
  341. nuke.message("Denoice: Error. Please select a Read node with EXR file")
  342. return
  343. if selected.Class() == "Read":
  344. if os.path.splitext(selected["file"].value())[1] == ".exr":
  345. launch_panel = denoicePanel()
  346. launch_panel.showModalDialog()
  347. else:
  348. nuke.message("Denoice: Error. Please select a Read node with EXR file")
  349. else:
  350. nuke.message("Denoice: Error. Please select a Read node")