123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- import os
- import nuke
- import nukescripts
- import re
- import subprocess
- import tempfile
-
- # martin@keller.io
- # numpy install:
- # https://jurajtomori.wordpress.com/2018/11/28/nuke-tip-installing-numpy-for-nuke-python-on-windows/
-
- '''noice 6.2.1.0 [359aaa66] - the Arnold denoiser
- Usage: noice [options] ...
- -i, --input %s Input image, multiple images can be included for stability, only first input image will be denoised
- -ef, --extraframes %d Number of frames to pad before and after for temporal stability (0 - 2, default:0)
- -f, --frames %d Number of frames to denoise in the sequence (default:1)
- -fe, --feature %s Use this AOV as a denoising feature
- -pr, --patchradius %d Neighborhood patch radius, size of pixel neighborhood to compare (0 - 6, default:3)
- -sr, --searchradius %d Search radius, higher values mean a wider search for similar pixel neighborhoods (6 - 21, default:9)
- -v, --variance %f Variance threshold, higher values mean more aggressive denoising (0.0 - 1.0, default:0.25)
- -l, --aov %s Light AOV name to be co-denoised, multiple can be specified
- -o, --output %s Output image
- -t, --threads %d Number of threads (0: use all cores, negative numbers specify number of idle cores)
- -irgba,--ignore_rgba Ignore RGBA layer: will not denoise it or copy it to output file
- -n, --notices Print copyright notices'''
-
- class denoice(KellerNukePlugin):
-
- def configurePlugin(self):
- menubar = nuke.menu("Nuke")
- self.m = menubar.addMenu("&Render")
- self.m.addCommand('Denoise with Noice', 'import denoice; denoice.addPanel()')
-
-
- def unconfigurePlugin(self):
- self.m.removeItem("Denoise with Noice")
-
-
-
- #######################################################################################
- #
- # GLOBALS
- #
- #######################################################################################
- DISABLE_AOVS = ['albedo', 'default', 'variance', 'Matte', 'matte', 'Crypto', 'crypto', 'denoise_albedo', 'P', 'N', 'Z']
- # Muster
- MSTRSERVER = "192.168.0.251"
- SUBMISSION_USER ="admin"
- PASSWORD = ""
- MRTOOL = "\\\\hades\\p\\env\\muster9\\Mrtool.exe"
- USERNAME = "admin"
- TEMPLATE = "2008"
- MUSTERFOLDER = "34758"
- POOL = "arnold_16thread"
- PSIZE = 1
- PRIO = 10
-
-
- def find_node_layers(node):
- node_channels = node.channels()
- store_layers = []
- for channel in node_channels:
- cut = channel.split(".")
- last = cut[0]
- store_layers.append(last)
- node_layers = list(set(store_layers))
- node_layers.sort()
-
- # put on top if rgb in layers
- if 'rgba' in node_layers:
- node_layers.remove("rgba")
- node_layers.insert(0, "rgba")
- return node_layers
-
-
- class denoicePanel(nukescripts.PythonPanel):
- def __init__(self):
- nukescripts.panels.PythonPanel.__init__(self, "KellerDenoicePanel", scrollable=True)
-
-
- self.node = nuke.selectedNode()
- self.nodeLayers = find_node_layers(self.node)
- self.layerKnobs = []
-
- # just disable layers
- disable = [lay for lay in self.nodeLayers if any(x in lay for x in DISABLE_AOVS)]
- num = len(self.nodeLayers)
- for layer in self.nodeLayers:
- self.layer = nuke.Boolean_Knob(layer, layer, True)
- self.layer.setFlag(nuke.STARTLINE)
- self.addKnob(self.layer)
- self.layerKnobs.append(self.layer)
- if layer in disable:
- self.layer.setValue(False)
-
- # interface selection
- self.div1 = nuke.Text_Knob("div1", " ", "")
- self.addKnob(self.div1)
- self.all = nuke.PyScript_Knob("select_all", " Select All ")
- self.addKnob(self.all)
- self.invert = nuke.PyScript_Knob("invert_selection", " Invert Selection ")
- self.addKnob(self.invert)
- self.clear = nuke.PyScript_Knob("clear_selection", " Clear Selection ")
- self.addKnob(self.clear)
- self.div2 = nuke.Text_Knob("div2", " ", "")
- self.addKnob(self.div2)
-
- # settings
- # variance
- self.variance = nuke.Double_Knob("variance", "Variance", arraysize=1)
- self.variance.setRange(0,1)
- self.variance.setTooltip(
- '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. '
- '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 '
- '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 '
- 'considering similar neighborhoods that have bigger color disparities.')
- self.addKnob(self.variance)
- self.variance.setValue(0.25)
-
- # patchradius
- self.patchradius = nuke.Int_Knob("patchradius", "Patchradius", arraysize=1)
- self.patchradius.setRange(0, 10)
- self.patchradius.setTooltip(
- '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. '
- '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 '
- ' (or -pr) command-line argument. The default value is set to 3, which gives a 7x7 square neighborhood.')
- self.addKnob(self.patchradius)
- self.patchradius.setValue(3)
-
- # searchradius
- self.searchradius = nuke.Int_Knob("searchradius", "Searchradius", arraysize=1)
- self.searchradius.setRange(0, 50)
- self.searchradius.setTooltip(
- 'Default: 9\nThis is the area over which similar neighborhoods are found. The higher the better, but it will increase '
- 'the cost of denoising. For every pixel noice will search a square area with a radius set with the '
- 'command line argument -searchradius (-sr). The bigger this area the bigger the denoising stability and '
- 'the higher the chance that similar neighborhoods to be considered will be found. The default value is 9, '
- 'which gives a 19x19 square neighborhood. Setting it to 21 (a search window of 42 x 42) will look over a '
- 'pixel area equivalent to loading 5 frames.')
- self.addKnob(self.searchradius)
- self.searchradius.setValue(9)
-
- # extraFrames
- self.temporalFrames = nuke.Int_Knob("temporalFrames", "ExtraFrames", arraysize=1)
- self.temporalFrames.setRange(0, 2)
- 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)')
- self.addKnob(self.temporalFrames)
- self.temporalFrames.setValue(0)
-
- # threads
- self.threads = nuke.Int_Knob("threads", "Threads", arraysize=1)
- self.threads.setTooltip('Number of threads (0: use all cores, negative numbers specify number of idle cores)')
- self.addKnob(self.threads)
- self.threads.setValue(-2)
-
- # div
- self.div3 = nuke.Text_Knob("div3", " ", "")
- self.addKnob(self.div3)
-
- # filepatn in
- self.inputpath = nuke.File_Knob("input", "Input")
- self.addKnob(self.inputpath)
- self.inputpath.setTooltip('Needs to be in #### syntax. Not in %04d')
- self.inputpath.setValue(nuke.selectedNode().knobs()['file'].value().replace('%04d','####'))
-
- # filepath out
- self.outputpath = nuke.File_Knob("output", "Output")
- file = nuke.selectedNode().knobs()['file'].value().replace('%04d', '####')
- file = file.replace('####', 'denoise.####')
- self.outputpath.setValue(file)
- self.addKnob(self.outputpath)
-
- # set path
- self.setpath = nuke.PyScript_Knob("setpath", " Set Path ")
- self.layer.setFlag(nuke.STARTLINE)
- self.addKnob(self.setpath)
-
- # startframe
- self.startframe = nuke.Int_Knob("startframe", "Startframe")
- self.addKnob(self.startframe)
-
- # endframe
- self.endframe = nuke.Int_Knob("endframe", "Endframe")
- self.addKnob(self.endframe)
- self.endframe.clearFlag(nuke.STARTLINE)
-
- # set to input
- self.setToInput = nuke.PyScript_Knob("setToInput", " Set to Input ")
- self.addKnob(self.setToInput)
- selected = nuke.selectedNode()
- fr = selected.frameRange()
- start = fr.first()
- end = fr.last()
- self.startframe.setValue(start)
- self.endframe.setValue(end)
- self.setToInput.clearFlag(nuke.STARTLINE)
-
- # set framerange
- self.setFrameRange = nuke.PyScript_Knob("setFrameRange", " Set Framerange ")
- self.addKnob(self.setFrameRange)
- self.setFrameRange.clearFlag(nuke.STARTLINE)
-
- # filepath
- self.arnoldBinPath = nuke.File_Knob("arnoldBinPath", " Arnold Bin Path ")
- self.addKnob(self.arnoldBinPath)
- self.arnoldBinPath.setValue('$env(PROJECT_ROOT_3D)/000_env/arnold/_noice/bin/')
- self.arnoldBinPath.setTooltip('Please be sure to select the bin folder with a "/" at the end.')
-
- # div
- self.div4 = nuke.Text_Knob("div4", " ", "")
- self.addKnob(self.div4)
-
- # render mode
- self.renderChoice = nuke.Enumeration_Knob('renderChoice', 'Render On', ['muster', 'local_machine'])
- 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.')
- self.addKnob(self.renderChoice)
-
- # muster pool
- self.musterPool = nuke.String_Knob("musterPool", "Muster Pool")
- self.musterPool.setValue(POOL)
- self.addKnob(self.musterPool)
-
- # muster pool
- self.musterFolder = nuke.String_Knob("musterFolder", "Muster Folder ID")
- self.musterFolder.setValue(MUSTERFOLDER)
- self.addKnob(self.musterFolder)
-
- # div
- self.div5 = nuke.Text_Knob("div5", " ", "")
- self.addKnob(self.div5)
-
- # window
- #height = len(self.layerKnobs) * 20
- height = 1200
- width = 800
- #if height > 1200:
- # width = 700
- # height = 1200
- self.setMinimumSize(width, height)
- #self.setMaximumSize(800, 1050)
-
-
- def knobChanged(self, knob):
- if knob.name() == "select_all":
- for layer in self.layerKnobs:
- layer.setValue(True)
-
- if knob.name() == "invert_selection":
- selected = []
- for layer in self.layerKnobs:
- if layer.value() is True:
- selected.append(layer)
- layer.setValue(False)
- inverse = list(set(self.layerKnobs).difference(set(selected)))
- for check in inverse:
- check.setValue(True)
-
- if knob.name() == "clear_selection":
- for layer in self.layerKnobs:
- layer.setValue(False)
-
- if knob.name() == "setToInput":
- selected = nuke.selectedNode()
- fr = selected.frameRange()
- start = fr.first()
- end = fr.last()
- self.startframe.setValue(start)
- self.endframe.setValue(end)
-
- if knob.name() == "setFrameRange":
- self.startframe.setValue(int(nuke.root()['first_frame'].value()))
- self.endframe.setValue(int(nuke.root()['last_frame'].value()))
-
- if knob.name() == "setpath":
- file = nuke.selectedNode().knobs()['file'].value().replace('%04d','####')
- file = file.replace('####','denoise.####')
- self.outputpath.setValue(file)
-
- if knob.name() == "renderChoice":
- if self.renderChoice.value() == "local_machine":
- self.musterPool.setVisible(False)
- self.musterFolder.setVisible(False)
- if self.renderChoice.value() == "muster":
- self.musterPool.setVisible(True)
- self.musterFolder.setVisible(True)
-
-
- if knob.name() == "OK":
- if self.renderChoice.value() == "local_machine":
- print('Denoising locally......')
- self.executeNoiceLocally()
- if self.renderChoice.value() == "muster":
- print('Denoising on Muster......')
- self.sendToMuster()
-
-
- def executeNoiceLocally(self):
- # saving selected aovs
- light_aov_names = []
- for layer in self.layerKnobs:
- if layer.value() is True:
- light_aov_names.append(layer.name())
-
- binpath = self.arnoldBinPath.evaluate().replace('\\','/')
-
- filename_input_sequence_number = re.sub(r'#+', lambda m: r'{{:0{}d}}'.format(len(m.group(0))), self.inputpath.value().replace('%04d','####'))
- filename_output_sequence_number = re.sub(r'#+', lambda m: r'{{:0{}d}}'.format(len(m.group(0))), self.outputpath.value().replace('%04d','####'))
-
- command = ''
- for i in range(self.startframe.value(), self.endframe.value() + 1):
- light_aov_string = ""
-
- for k in light_aov_names:
- print('Denoice: Info. AOV: %s') % k
- light_aov_string += "-aov " + str(k) + " "
-
- command += '"' + binpath + '/noice.exe' + '"' + ' ' \
- + '-patchradius ' + str(self.patchradius.value()) + ' ' \
- + '-searchradius ' + str(self.searchradius.value()) + ' ' \
- + '-variance ' + str(self.variance.value()) + ' ' \
- + light_aov_string \
- + '-i ' + filename_input_sequence_number.format(i) + ' ' \
- + '-t ' + str(self.threads.value()) + ' ' \
- + '-ef ' + str(self.temporalFrames.value()) + ' ' \
- + '-output ' + filename_output_sequence_number.format(i)
-
- print('_________________________________________________________________________________________________')
- print('')
- print('--------------------------------> Denoise Command for Frame %s <---------------------------') %i
- print('_________________________________________________________________________________________________')
- print('')
- print('')
- print('')
-
- if i is not self.endframe.value():
- command += "\n"
- print(command)
-
- temp = tempfile.NamedTemporaryFile(prefix='Noice_', suffix='.bat', delete=False)
- temp.write(command)
- print('Created temp file is:', temp.name)
- temp.close()
-
- filepath = 'cmd /c start "Denoice " "%s"' %temp.name
- p = subprocess.call(filepath, shell=True, stdout=subprocess.PIPE)
-
-
- def fixfilename(self,infile):
-
- infile = infile.replace("p:/", "//hades/p/")
- infile = infile.replace("P:/", "//hades/p/")
- infile = infile.replace("o:/", "//calculon/o/")
- infile = infile.replace("O:/", "//calculon/o/")
- infile = infile.replace("/", "\\")
-
- return infile
-
-
- def sendToMuster(self):
- # jobname
- jobname = nuke.selectedNode().knobs()['file'].value().split("/")[-1].replace('.%04d.exr',"__denoise")
- # aovs
- # saving selected aovs
- light_aov_names = []
- light_aov_string = ""
- for layer in self.layerKnobs:
- if layer.value() is True:
- light_aov_names.append(layer.name())
- for k in light_aov_names:
- print('Denoice: Info. AOV: %s') % k
- light_aov_string += "-l " + str(k) + " "
- light_aov_string = light_aov_string.rstrip()
- light_aov_string = '"' + light_aov_string + '"'
-
- a_bin_path = self.fixfilename(self.arnoldBinPath.evaluate())
- a_input_path = self.fixfilename(self.inputpath.evaluate().replace('%04d','####'))
- a_output_path = self.fixfilename(self.outputpath.evaluate().replace('%04d','####'))
-
- # building Command
- cmd = '%s -s %s -u %s -b -e %s -n %s' % (MRTOOL, MSTRSERVER, USERNAME, TEMPLATE, jobname)
- cmd += ' -pk %i -pr %i -pool "%s" -parent %i' % (int(PSIZE), int(PRIO), self.musterPool.value(), int(self.musterFolder.value()))
- cmd += ' -attr "NOICEBIN" %s 0' % a_bin_path
- cmd += ' -attr "PATCHRADIUS" %i 0' % int(self.patchradius.value())
- cmd += ' -attr "SEARCHRADIUS" %i 0' % int(self.searchradius.value())
- cmd += ' -attr "VARIANCE" %s 0' % self.variance.value()
- cmd += ' -sf %i' % int(self.startframe.value())
- cmd += ' -ef %i' % int(self.endframe.value())
- cmd += ' -bf %i' % 1
- cmd += ' -f %s' % a_input_path
-
- cmd += ' -attr "LIGHTAOVSTRING" %s 0' % light_aov_string
- cmd += ' -attr "TEMPORALFRAMES" %i 0' % int(self.temporalFrames.value())
- cmd += ' -attr "DENOISETHREADS" %i 0' % int(self.threads.value())
- cmd += ' -attr "OUTPUTFILE" %s 0' % a_output_path
-
- # send
- print (cmd)
- print (os.system(cmd))
- return
-
-
- def addPanel():
- try:
- selected = nuke.selectedNode()
- except:
- nuke.message("Denoice: Error. Please select a Read node with EXR file")
- return
- if selected.Class() == "Read":
- if os.path.splitext(selected["file"].value())[1] == ".exr":
- launch_panel = denoicePanel()
- launch_panel.showModalDialog()
- else:
- nuke.message("Denoice: Error. Please select a Read node with EXR file")
- else:
- nuke.message("Denoice: Error. Please select a Read node")
|