I have been parsing through the files of other people a lot lately, and finally took the time to make a little function to give me general information about a sequence of files. It uses regex to yank the numeric parts out of a filename, figure out the padding, and glob to tell you how many files in the sequence. Here’s the code and an example usage:
#returns [base name, padding, filetype, number of files, first file, last file]
def getSeqInfo(file):
dir = os.path.dirname(file)
file = os.path.basename(file)
segNum = re.findall(r'\d+', file)[-1]
numPad = len(segNum)
baseName = file.split(segNum)[0]
fileType = file.split('.')[-1]
globString = baseName
for i in range(0,numPad): globString += '?'
theGlob = glob.glob(dir+'\\'+globString+file.split(segNum)[1])
numFrames = len(theGlob)
firstFrame = theGlob[0]
lastFrame = theGlob[-1]
return [baseName, numPad, fileType, numFrames, firstFrame, lastFrame] |
#returns [base name, padding, filetype, number of files, first file, last file]
def getSeqInfo(file):
dir = os.path.dirname(file)
file = os.path.basename(file)
segNum = re.findall(r'\d+', file)[-1]
numPad = len(segNum)
baseName = file.split(segNum)[0]
fileType = file.split('.')[-1]
globString = baseName
for i in range(0,numPad): globString += '?'
theGlob = glob.glob(dir+'\\'+globString+file.split(segNum)[1])
numFrames = len(theGlob)
firstFrame = theGlob[0]
lastFrame = theGlob[-1]
return [baseName, numPad, fileType, numFrames, firstFrame, lastFrame]
Here is an example of usage:
print getSeqInfo('E:\\data\\data\\Games\\Project\\CaptureOutput\\Frame000547.jpg')
>>['Frame', 6, 'jpg', 994, 'E:\\data\\data\\Games\\Project\\CaptureOutput\\Frame000000.jpg', 'E:\\data\\data\\Games\\Project\\CaptureOutput\\Frame000993.jpg'] |
print getSeqInfo('E:\\data\\data\\Games\\Project\\CaptureOutput\\Frame000547.jpg')
>>['Frame', 6, 'jpg', 994, 'E:\\data\\data\\Games\\Project\\CaptureOutput\\Frame000000.jpg', 'E:\\data\\data\\Games\\Project\\CaptureOutput\\Frame000993.jpg']
I know this is pretty simple, but I looked around a bit online and didn’t see anything readily available showing how to deal with different numbered file sets. I have needed something like this for a while that will work with anything from OBJs sent from external contractors, to images from After Effects…
posted by admin at 6:49 PM
So I have always been wondering how you can create almost like a ‘droplet’ to steal the photoshop lingo, from a python script. A while ago I came across some sites showing how to edit shellex in regedit to allow for files to be dropped on any python script and fed to it as args (Windows).
It’s really simple, you grab this reg file [py_drag_n_drop.reg] and install it.
Now when you drop files onto a python script, their filenames will be passed as args, here’s a simple script to test.
import sys
f = open('c:\\tmp.txt', 'w')
for arg in sys.argv:
f.write(arg + '\n')
f.close() |
import sys
f = open('c:\\tmp.txt', 'w')
for arg in sys.argv:
f.write(arg + '\n')
f.close()
When you save this, and drop files onto its icon, it will create tmp.txt, which will look like this:
X:\projects\2010\python\drag_and_drop\drag_n_drop.py
X:\photos\2010.04 - easter weekend\fuji\DSCF9048.MPO
X:\photos\2010.04 - easter weekend\fuji\DSCF9049.MPO
X:\photos\2010.04 - easter weekend\fuji\DSCF9050.MPO
X:\photos\2010.04 - easter weekend\fuji\DSCF9051.MPO
X:\photos\2010.04 - easter weekend\fuji\DSCF9052.MPO |
X:\projects\2010\python\drag_and_drop\drag_n_drop.py
X:\photos\2010.04 - easter weekend\fuji\DSCF9048.MPO
X:\photos\2010.04 - easter weekend\fuji\DSCF9049.MPO
X:\photos\2010.04 - easter weekend\fuji\DSCF9050.MPO
X:\photos\2010.04 - easter weekend\fuji\DSCF9051.MPO
X:\photos\2010.04 - easter weekend\fuji\DSCF9052.MPO
The script itself is the first arg, then all the files. This way you can easily create scripts that accept drops to do things like convert files, upload files, etc..
posted by admin at 12:33 AM
I got some good feedback from the last post and updated the script to export JPEG Stereo (JPS) and PNG Stereo (PNS, really.) This way you can convert your images into a single lossless image that you can pop into photoshop and adjust hsv/levels, etc.
import mpo
mpo.makePNS('DSCF9463.MPO')
#>>Saving image: DSCF9463.PNS
#>>Save complete. |
import mpo
mpo.makePNS('DSCF9463.MPO')
#>>Saving image: DSCF9463.PNS
#>>Save complete.
This is a super simple python script, no error padding. Also, keep in mind that coming from most modern camera rigs, you are saving like a 20-40 megapixel PNG compressed file here, wait until it says it is done saving, it may take a few seconds.
posted by admin at 10:31 PM
Many stereo cameras are using the new MPO format to store multiple images in a file. Unfortunately, nothing really works with these files (Other than Stereo Photo Maker). Here is a simple python wrapper around ExifTool that will extract the Right and Left image, and return EXIF data as a dict. I think this is probably easier than explaining how to use ExifTool, but you can see from looking at the simple wrapper code.
import mpo
#Name of MPO file, name of output, whether or not you want all EXIF in a txt log
mpo.extractImagePair('DSCF9463.MPO', 'DSCF9463', True)
#>>Created DSCF9463_R.jpg
#>>Created DSCF9463_L.jpg
#>>Writing EXIF data |
import mpo
#Name of MPO file, name of output, whether or not you want all EXIF in a txt log
mpo.extractImagePair('DSCF9463.MPO', 'DSCF9463', True)
#>>Created DSCF9463_R.jpg
#>>Created DSCF9463_L.jpg
#>>Writing EXIF data
The above leaves you with two images and a text file that has all the EXIF data, even attributes that xnView and other apps do not read:
exif = getExif('DSCF9463.MPO')
print exif["Convergence Angle"]
#>>0
print exif["Field Of View"]
#>>53.7 deg
print exif["Focal Length"]
#>>6.3 mm (35 mm equivalent: 35.6 mm) |
exif = getExif('DSCF9463.MPO')
print exif["Convergence Angle"]
#>>0
print exif["Field Of View"]
#>>53.7 deg
print exif["Focal Length"]
#>>6.3 mm (35 mm equivalent: 35.6 mm)
posted by admin at 2:58 AM
I have been really amazing myself at how much knowledge I have forgotten in the past five or six months… Most of the work I did in the past year utilized the UIC module to load UI files directly, but I can find very little information about this online. I was surprised to see that even the trusty old Rapid GUI Programming with Python and Qt book doesn’t cover loading UI files with the UIC module.
So, here is a tiny script with UI file [download] that will generate a pyqt example window that does ‘stuff’:
import sys
from PyQt4 import QtGui, QtCore, uic
class TestApp(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = uic.loadUi('X:/projects/2010/python/pyqt_tutorial/pyqt_tutorial.ui')
self.ui.show()
self.connect(self.ui.doubleSpinBox, QtCore.SIGNAL("valueChanged(double)"), spinFn)
self.connect(self.ui.comboBox, QtCore.SIGNAL("currentIndexChanged(QString)"), comboFn)
self.connect(self.ui.pushButton, QtCore.SIGNAL("clicked()"), buttonFn)
def spinFn(value):
win.ui.doubleSpinBoxLabel.setText('doubleSpinBox is set to ' + str(value))
def buttonFn():
win.ui.setWindowTitle(win.ui.lineEdit.text())
def comboFn(value):
win.ui.comboBoxLabel.setText(str(value) + ' is selected')
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
win = TestApp()
sys.exit(app.exec_()) |
import sys
from PyQt4 import QtGui, QtCore, uic
class TestApp(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = uic.loadUi('X:/projects/2010/python/pyqt_tutorial/pyqt_tutorial.ui')
self.ui.show()
self.connect(self.ui.doubleSpinBox, QtCore.SIGNAL("valueChanged(double)"), spinFn)
self.connect(self.ui.comboBox, QtCore.SIGNAL("currentIndexChanged(QString)"), comboFn)
self.connect(self.ui.pushButton, QtCore.SIGNAL("clicked()"), buttonFn)
def spinFn(value):
win.ui.doubleSpinBoxLabel.setText('doubleSpinBox is set to ' + str(value))
def buttonFn():
win.ui.setWindowTitle(win.ui.lineEdit.text())
def comboFn(value):
win.ui.comboBoxLabel.setText(str(value) + ' is selected')
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
win = TestApp()
sys.exit(app.exec_())
Change the path to reflect where you have saved the UI file, and when you run the script you should get this:
EDIT: A few people have asked me to update this for other situations
PySide Inside Maya:
import sys
from PySide.QtUiTools import *
from PySide.QtCore import *
from PySide.QtGui import *
class TestApp(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loader = QUiLoader()
self.ui = loader.load('c:/pyqt_tutorial.ui')
self.ui.show()
self.connect(self.ui.doubleSpinBox, SIGNAL("valueChanged(double)"), spinFn)
self.connect(self.ui.comboBox, SIGNAL("currentIndexChanged(QString)"), comboFn)
self.connect(self.ui.pushButton, SIGNAL("clicked()"), buttonFn)
def spinFn(value):
win.ui.doubleSpinBoxLabel.setText('doubleSpinBox is set to ' + str(value))
def buttonFn():
win.ui.setWindowTitle(win.ui.lineEdit.text())
def comboFn(value):
win.ui.comboBoxLabel.setText(str(value) + ' is selected')
win = TestApp() |
import sys
from PySide.QtUiTools import *
from PySide.QtCore import *
from PySide.QtGui import *
class TestApp(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loader = QUiLoader()
self.ui = loader.load('c:/pyqt_tutorial.ui')
self.ui.show()
self.connect(self.ui.doubleSpinBox, SIGNAL("valueChanged(double)"), spinFn)
self.connect(self.ui.comboBox, SIGNAL("currentIndexChanged(QString)"), comboFn)
self.connect(self.ui.pushButton, SIGNAL("clicked()"), buttonFn)
def spinFn(value):
win.ui.doubleSpinBoxLabel.setText('doubleSpinBox is set to ' + str(value))
def buttonFn():
win.ui.setWindowTitle(win.ui.lineEdit.text())
def comboFn(value):
win.ui.comboBoxLabel.setText(str(value) + ' is selected')
win = TestApp()
PyQT Inside Maya:
import sys
from PyQt4 import QtGui, QtCore, uic
class TestApp(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = uic.loadUi('c:/pyqt_tutorial.ui')
self.ui.show()
self.connect(self.ui.doubleSpinBox, QtCore.SIGNAL("valueChanged(double)"), spinFn)
self.connect(self.ui.comboBox, QtCore.SIGNAL("currentIndexChanged(QString)"), comboFn)
self.connect(self.ui.pushButton, QtCore.SIGNAL("clicked()"), buttonFn)
def spinFn(value):
win.ui.doubleSpinBoxLabel.setText('doubleSpinBox is set to ' + str(value))
def buttonFn():
win.ui.setWindowTitle(win.ui.lineEdit.text())
def comboFn(value):
win.ui.comboBoxLabel.setText(str(value) + ' is selected')
win = TestApp() |
import sys
from PyQt4 import QtGui, QtCore, uic
class TestApp(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = uic.loadUi('c:/pyqt_tutorial.ui')
self.ui.show()
self.connect(self.ui.doubleSpinBox, QtCore.SIGNAL("valueChanged(double)"), spinFn)
self.connect(self.ui.comboBox, QtCore.SIGNAL("currentIndexChanged(QString)"), comboFn)
self.connect(self.ui.pushButton, QtCore.SIGNAL("clicked()"), buttonFn)
def spinFn(value):
win.ui.doubleSpinBoxLabel.setText('doubleSpinBox is set to ' + str(value))
def buttonFn():
win.ui.setWindowTitle(win.ui.lineEdit.text())
def comboFn(value):
win.ui.comboBoxLabel.setText(str(value) + ' is selected')
win = TestApp()
posted by admin at 11:54 PM
My friends Judd and Rich gave a talk on some of the Character Tech behind Uncharted 2. Here are the slides.
posted by admin at 8:32 PM
I have gotten back into some pyqt in my spare time, just because it’s what I used on a daily basis at the last place I worked at. However, I had trouble getting it to run in my text editor of choice. (SciTE)
I couldn’t find a solution with like 45 minutes of googling. When trying to import PyQt4 it would give me a dll error, but I could paste the code into IDLE and it would execute fine. I found a solution by editing the python preferences of SciTE. I noticed that it wasn’t running python scripts the way IDLE was, but compiling them (?). I edited the last line to just run the script, and viola! It worked.
Find this line (usually the last):
command.1.*.py=python -c "import py_compile; py_compile.compile(r'$(FilePath)')" |
command.1.*.py=python -c "import py_compile; py_compile.compile(r'$(FilePath)')"
And change it to:
command.1.*.py=python "(r'$(FilePath)')" |
command.1.*.py=python "(r'$(FilePath)')"
I don’t really know if this messes anything else up, but it does allow the PyQt4 libs to load and do their thing.
posted by admin at 8:04 PM