top of page

Batch Denoise UI

This is a tool that allows the user to select a starting frame and ending frame of Renderman rendered .exr files and run Renderman's denoiser in order to clean up the images.

​

This code consists of 2 parts. The first is the actual denoise operation, and the second script controls the interface and calls the denoise script to action.

​

The code to run the denoise script is as follows.

​

import os
import os.path
import sys
import re
import glob
import subprocess

class BruceOpps():
    def __init__(self):
        self.frames = ''
        self.parent_dir = ''
        self.exr_fullpath = ''
        self.start_name = ''
        self.start_num = ''
        self.end_num = ''
        self.bin_path = 'denoise' # assumes we are using linux
        self.flags = ' --crossframe -v variance -f default.filter.json '
        self.batch_script = ''
    #_________________________________________
    def setBinPath(self, path):
        self.bin_path = path
    #_________________________________________
    def setStartEndPaths(self, start_exr_path, end_exr_path):
        # Now we know the start and end paths we should check if their exr names match
        # - incase the user has not chosen the correct exr files.
        start_dir, start_name, start_num = self.get_dir_name_num(start_exr_path)
        end_dir, end_name, end_num = self.get_dir_name_num(end_exr_path)
        #print start_name
        if not start_name.endswith('variance'):
            raise RuntimeError("Only variance files can be denoised")    
        if not start_name == end_name:
            raise RuntimeError("OpenEXR names don't match :(")
        if not start_dir == end_dir:
            raise RuntimeError("OpenEXR directories don't match :(")
            
        self.parent_dir = start_dir
        self.exr_fullpath = os.path.join(start_dir, start_name)
        #print self.exr_fullpath
        self.start_name = start_name
        self.start_num = start_num
        self.end_num = end_num
    #_________________________________________        
    def getListOfNumExts(self):
        # Get a list of all the .exr files and then find the ones that
        # are within the start_num and end_num range.
        glob_pattern = os.path.join(self.parent_dir, '*_variance.*.exr')
        paths = glob.glob(glob_pattern)
        listOfNumExt = []
        for path in paths:
            dir,name,num = self.get_dir_name_num(path)
            if name == self.start_name and int(num) >= int(self.start_num) and int(num) <= int(self.end_num):
                listOfNumExt.append(num)    
        self.frames = (",".join(listOfNumExt))
    #_________________________________________            
    def writeBatchScript(self):
        self.command = self.bin_path + self.flags + self.exr_fullpath + '.{' + self.frames + '}.exr'
        if os.name == 'nt':
            batch_name = self.start_name + '.bat'
        else:
            batch_name = self.start_name
        self.batch_script = os.path.join(self.parent_dir, batch_name)
        batch = open(self.batch_script , 'w')
        batch.write(self.command)
        batch.close()
    #_________________________________________        
    def runDenoiseCommand(self):
        process = subprocess.Popen(self.command, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
        for line in iter(process.stdout.readline, ''):
            print line
    #_________________________________________
    # The double underscore makes this a method that can only be used
    # by instances of the class ie. it is a "private" procedure.
    def get_dir_name_num(self, path):
        print path
        
        parent_dir = os.path.dirname(path)
        name = os.path.basename(path)
        reg_pat = re.compile(r'(\w+)[._]+(\d+)[._]+exr')
        found_it = re.search(reg_pat, name)
        if found_it:
            name = found_it.group(1)
            num  = found_it.group(2)
            return [parent_dir, name, num]
        else:
            return None

if __name__ == '__main__':
    bruce = BruceOpps()
    bruce.getListOfNumExts()
    bruce.writeBatchScript()
    bruce.runDenoiseCommand()

​

The second part of this script controls the user interface of the denoise tool. The reason that there are 2 scripts is because on controls the denoise operation and the other controls the UI and calls the denoise script into action.

​

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import uic

import os, re, sys, subprocess
import bruce_oops
reload(bruce_oops)

class DenoiseWindow(QMainWindow):
    def __init__(self):
        super(DenoiseWindow, self).__init__()

        uic.loadUi(os.path.join(os.path.dirname(__file__), 'UI', 'denoise.ui'), self)
        self.output = []
        self.makeConnections()
        self.bruce = bruce_oops.BruceOpps()

        home_dir = os.environ['HOME']
        if os.name == 'posix':
            self.pathToFarm = os.path.join(home_dir, 'mount', 'renderfarm', )
        else:
            self.pathToFarm = 'I:\Savannah\SDM Render Farm'
            
        self.LNE_firstFrame.setText(self.pathToFarm)
        self.LNE_endFrame.setText(self.pathToFarm)

        rman_dir = os.environ['RMANTREE']
        self.denoiser = os.path.join(rman_dir, 'bin', 'denoise')
        self.LNE_osBrowse.setText(self.denoiser)
        
    def makeConnections(self):
        self.B_firstFrame.clicked.connect(self.browseStart)
        self.B_endFrame.clicked.connect(self.browseEnd)
        self.B_osBrowse.clicked.connect(self.browseBin)
        self.B_Denoise.clicked.connect(self.execute)
        self.B_Cancel.clicked.connect(sys.exit)

    def browseStart(self):
        path = QFileDialog.getOpenFileName(self, caption='Choose start frame', directory=os.path.expanduser('~'))

        self.LNE_firstFrame.setText(path)

    def browseEnd(self):
        path = QFileDialog.getOpenFileName(self, caption='Choose end frame', directory=os.path.expanduser('~'))

        self.LNE_endFrame.setText(path)

    def browseBin(self):
        path = QFileDialog.getOpenFileName(self, caption='Choose denoise bin script', directory=os.path.expanduser('~'))

        self.LNE_osBrowse.setText(path)

    def updateOutput(self, line):
        self.output.append(line)
        self.TXT_output.setPlainText('\n'.join(self.output))
        self.TXT_output.verticalScrollBar().setValue(self.TXT_output.verticalScrollBar().maximum())

    def execute(self):
        self.bruce.setBinPath( str(self.LNE_osBrowse.text()) )
        print self.LNE_firstFrame.text()
        
        self.bruce.setStartEndPaths(str(self.LNE_firstFrame.text()), str(self.LNE_endFrame.text()))
        self.bruce.getListOfNumExts()
        self.bruce.writeBatchScript()
        cmd = self.bruce.command
        process = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
        for line in iter(process.stdout.readline, ''):
            #sys.stdout.write(line)
            self.updateOutput(line)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = DenoiseWindow()
    window.show()
    sys.exit(app.exec_())










 

Screenshot from 2019-03-14 09-54-01.png
bottom of page