Batch Denoise GUI

This page explains the development of a GUI that enables a non-programmer to batch denoise a sequence of RenderMan .exr image files.

 
 

ALGORITHM

1. Create a UI template in QT Designer.
2. Create a class from the batch denoise script.
2.1. Set the frame range and checks for errors.
2.2. Set the bin path.
2.3. Get list of frame numbers.
2.4. Write out a batch render file.
3. Connect the controls for the GUI.
4. Run the batch denoise from the GUI. See instructions below.

 

INSTRUCTIONS

To Run the Script:

  1. Create a directory for the Denoise GUI.

  2. Place the "denoise_oops.py" and "denoiseUI.py" to the directory.

  3. Create a subdirectory called "ui".

  4. Place the "denoiserWindow.ui" into this subdirectory.

  5. On Linux double click the file called "denoiseUI", on Windows double click the file called "denoiseUI.bat".

To Use the Deniose GUI:

  1. Click the "Browse" button next to the "start frame file path" bar and find the first image in the sequence you wish to denoise.

  2. Click the "Browse" button next to the "end frame file path" bar and find the last image in the sequence you wish to denoise.

  3. Click the "Browse" button next to the "denoise bin path" bar and select the directory you wish to set as the bin path for the images.

  4. Click the "Do It" button and the progress of the denoising process will be printed in the Output box.

  5. Click the "Cancel" button to close the GUI.

 

THE CODE - "denoise_oops.py"

import os
import os.path
import re
import glob
import subprocess
  
class DenoiseOops():
    def __init__(self):
        self.frames = ''
        self.parent_dir = ''
        self.exr_fullpath = ''
        self.start_name = ''
        self.start_num = ''
        self.end_num = ''
        self.bin_path = 'denoise'
        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):
        # Check to see if the .exr names match. 
        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)
        
        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)
        self.start_name = start_name
        self.start_num = start_num
        self.end_num = end_num
    
    # Obtain a list of all .exr files and find ones within the start_num and end_num range.    
    def getListOfNumExts(self):
        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
    
    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__':
    denoise = DenoiseOops()
    denoise.setStartEndPaths('.../images/demo__perspShape_beauty_variance.0003.exr',
                            '.../images/demo__perspShape_beauty_variance.0007.exr')
    denoise.getListOfNumExts()
    denoise.writeBatchScript()
    denoise.runDenoiseCommand()
 

THE CODE - "denoiseUI.py"

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import uic
  
import os
import re
import sys
import subprocess
import denoise_oops
reload(denoise_oops)
  
class DenoiseWindow(QMainWindow):
    def __init__(self):
        super(DenoiseWindow, self).__init__()
  
        uic.loadUi(os.path.join(os.path.dirname(__file__), 'ui', 'denoiserWindow.ui'), self)
        self.output = []
        self.makeConnections()
        self.denoise = denoise_oops.DenoiseOops()
        
        # Set a reasonable path to the where the images for denoising are most likely to be stored.
        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_startFrame.setText(self.pathToFarm)
        self.LNE_endFrame.setText(self.pathToFarm)
        
        # Set a full path to the denoise application.
        rman_dir = os.environ['RMANTREE']
        self.denoiser = os.path.join(rman_dir, 'bin', 'denoise')
        self.LNE_bin.setText(self.denoiser)
    
    # Make connections.
    def makeConnections(self):
        self.BTN_startBrowse.clicked.connect(self.browseStart)
        self.BTN_endBrowse.clicked.connect(self.browseEnd)
        self.BTN_binBrowse.clicked.connect(self.browseBin)
        self.BTN_execute.clicked.connect(self.execute)
        self.BTN_cancel.clicked.connect(sys.exit)
  
    def browseStart(self):
        path = QFileDialog.getOpenFileName(self, caption='Choose start frame', directory=self.pathToFarm) 
        self.LNE_startFrame.setText(path)
  
    def browseEnd(self):
        path = QFileDialog.getOpenFileName(self, caption='Choose end frame', directory=self.pathToFarm) 
        self.LNE_endFrame.setText(path)
  
    def browseBin(self):
        path = QFileDialog.getOpenFileName(self, caption='denoise bin directory', directory=os.path.expanduser('~'))
        self.LNE_bin.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.denoise.setBinPath( str(self.LNE_bin.text()) )
        print self.LNE_startFrame.text()
        
        self.denoise.setStartEndPaths(str(self.LNE_startFrame.text()), str(self.LNE_endFrame.text()))
        self.denoise.getListOfNumExts()
        self.denoise.writeBatchScript()
        cmd = self.denoise.command
        process = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
        for line in iter(process.stdout.readline, ''):
            self.updateOutput(line)
 

SHELL SCRIPTS

Linux - "denoiseUI"

python denoiseUI.py
read

Windows - "denoiseUI.bat"

C:\python27\python denoiseUI.py
pause

 

Denoiser GUI Template in Qt Designer

Image of the Denoiser GUI template being created in Qt Designer. Step 1 of the Algorithm.

 

Denoiser GUI

Image of the Denoiser GUI when the "denoiseUI" or "denoiseUI.bat" are run on their respective OS.

 

RESTRICTIONS

This is meant as an example for the process of denoising a batch of rendered .exr files. PyQt4 needs to be installed on the respective computers for the code to work properly. The initial directory paths are predetermined by the code. They can be changed by pressing the "Browse" buttons, but if the user desires to set other initial directories upon opening the window, he or she must manually change the code. This code should be use as a starting point and should be tailored to suit the user's needs.