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