Skip to content

Instantly share code, notes, and snippets.

@kohyuk91
Last active November 17, 2019 16:28
Show Gist options
  • Select an option

  • Save kohyuk91/9df1c8a6903931f2c8ab7cb6bd532d95 to your computer and use it in GitHub Desktop.

Select an option

Save kohyuk91/9df1c8a6903931f2c8ab7cb6bd532d95 to your computer and use it in GitHub Desktop.
# Name:
# easy_dewarp.py
#
# Author:
# Hyuk Ko ([email protected])
#
#
#
# 3DE4.script.name: Easy Dewarp...
#
# 3DE4.script.version: v1.0
#
# 3DE4.script.gui: Main Window::Python
#
# 3DE4.script.comment: Export Dewarped images with Warp4
#
# MODIFY THIS AT YOUR OWN RISK
import os
import sys
import math
import time
import string
import platform
import subprocess
import multiprocessing
import multiprocessing.pool
from functools import partial
TDE4_PATH = tde4.get3DEInstallPath()
OS = platform.system()
if OS == "Windows":
WARP4_PATH = r"{0}\bin\warp4.exe".format(TDE4_PATH)
DIRECTORY_SEPARATOR = "\\"
else:
WARP4_PATH = "{0}/bin/warp4".format(TDE4_PATH)
DIRECTORY_SEPARATOR = "/"
# /py_scripts/calc_bounding_box_for_dynamic_distortion.py
class bbdld_bounding_box:
def __init__(self):
self._x_min = float("inf")
self._x_max = float("-inf")
self._y_min = float("inf")
self._y_max = float("-inf")
# Extend so that it is symmetric around (cx,cy)
def symmetrize(self,cx,cy):
if cx - self._x_min > self._x_max - cx:
self._x_max = 2.0 * cx - self._x_min
else:
self._x_min = 2.0 * cx - self._x_max
if cy - self._y_min > self._y_max - cy:
self._y_max = 2.0 * cy - self._y_min
else:
self._y_min = 2.0 * cy - self._y_max
# Extend the bounding box so that it contains (x,y)
def extend(self,x,y):
self._x_min = min(self._x_min,float(x))
self._x_max = max(self._x_max,float(x))
self._y_min = min(self._y_min,float(y))
self._y_max = max(self._y_max,float(y))
# Symmetric extension around (cx,cy)
def extend_symm(self,x,y,cx,cy):
self.extend(x,y)
self.symmetrize(cx,cy)
# Scale, multiply x and y by some positiv number
def scale(self,sx,sy):
self._x_min *= sx
self._x_max *= sx
self._y_min *= sy
self._y_max *= sy
# Convenient for pixel coordinates (ignore float artefacs, therefore 1e-12 thingees)
def extend_to_integer(self):
self._x_min = math.floor(self._x_min + 1e-12)
self._x_max = math.ceil(self._x_max - 1e-12)
self._y_min = math.floor(self._y_min + 1e-12)
self._y_max = math.ceil(self._y_max - 1e-12)
# Properties
def dx(self):
return self._x_max - self._x_min
def dy(self):
return self._y_max - self._y_min
def x_min(self):
return self._x_min
def x_max(self):
return self._x_max
def y_min(self):
return self._y_min
def y_max(self):
return self._y_max
def __str__(self):
return "[" + str(self._x_min) + "," + str(self._x_max) + "," + str(self._y_min) + "," + str(self._y_max) + "]"
def bbdld_compute_bounding_box():
# List of selected cameras
id_cam = tde4.getCurrentCamera()
# We genererate a number of samples around the image in normalized coordinates.
# These samples are later unwarped, and the unwarped points
# will be used to create a bounding box. In general, it is *not* sufficient to
# undistort only the corners, because distortion might be moustache-shaped.
# This is our list of samples:
warped = []
for i in range(10):
warped.append([i / 10.0,0.0])
warped.append([(i + 1) / 10.0,1.0])
warped.append([0.0,i / 10.0])
warped.append([1.0,(i + 1) / 10.0])
# Run through sequence cameras
if tde4.getCameraType(id_cam) == "SEQUENCE":
name = tde4.getCameraName(id_cam)
# The lens of this sequence
id_lens = tde4.getCameraLens(id_cam)
# Lens center offset as given in GUI
lco = [tde4.getLensLensCenterX(id_lens),tde4.getLensLensCenterY(id_lens)]
# The lens center is by definition the fixed point of the distortion mapping.
elc = [0.5 + lco[0],0.5 + lco[1]]
# Image size
w_px = tde4.getCameraImageWidth(id_cam)
h_px = tde4.getCameraImageHeight(id_cam)
# The bounding boxes for non-symmetrized and symmetrized cased.
bb_nonsymm = bbdld_bounding_box()
bb_symm = bbdld_bounding_box()
# Run through the frames of this camera
n_frames = tde4.getCameraNoFrames(id_cam)
for i_frame in range(n_frames):
# 3DE4 counts from 1.
frame = i_frame + 1
# Now we undistort all edge points for the given
# camera and frame and extend the bounding boxes.
for p in warped:
p_unwarped = tde4.removeDistortion2D(id_cam,frame,p)
# Accumulate bounding boxes
bb_nonsymm.extend(p_unwarped[0],p_unwarped[1])
bb_symm.extend_symm(p_unwarped[0],p_unwarped[1],elc[0],elc[1])
# Scale to pixel coordinates and extend to pixel-aligned values
bb_nonsymm.scale(w_px,h_px)
bb_nonsymm.extend_to_integer()
# Image width and height for the non-symmetrized case
w_nonsymm_px = bb_nonsymm.dx()
h_nonsymm_px = bb_nonsymm.dy()
# Lower left corner for the symmetrized case. This tells us
# how the undistorted image is related to the distorted image.
x_nonsymm_px = bb_nonsymm.x_min()
y_nonsymm_px = bb_nonsymm.y_min()
# Scale to pixel coordinates and extend to pixel-aligned values
bb_symm.scale(w_px,h_px)
bb_symm.extend_to_integer()
# Image width and height for the symmetrized case
w_symm_px = bb_symm.dx()
h_symm_px = bb_symm.dy()
# Lower left corner for the symmetrized case. This tells us
# how the undistorted image is related to the distorted image.
x_symm_px = bb_symm.x_min()
y_symm_px = bb_symm.y_min()
return int(w_symm_px), int(h_symm_px)
# /py_scripts/export_nuke_LD_3DE4_Lens_Distortion.py
def getLDmodelParameterList(model):
l = []
for p in range(tde4.getLDModelNoParameters(model)):
l.append(tde4.getLDModelParameterName(model, p))
return l
def update_overscan_resolution():
tde4.setWidgetValue(req, 'overscan_width', str(int(math.ceil(float(tde4.getWidgetValue(req, "overscan_value")) * w_px)))) # Always Even Number
tde4.setWidgetValue(req, 'overscan_height', str(int(math.ceil(float(tde4.getWidgetValue(req, "overscan_value")) * h_px)))) # Always Even Number
def update_bbox_fg_color():
if int(tde4.getWidgetValue(req, "overscan_width")) < int(tde4.getWidgetValue(req, "bounding_box_width")) or int(tde4.getWidgetValue(req, "overscan_height")) < int(tde4.getWidgetValue(req, "bounding_box_height")):
tde4.setWidgetFGColor(req, 'bounding_box_width', 1.0, 0.0, 0.0)
tde4.setWidgetFGColor(req, 'bounding_box_height', 1.0, 0.0, 0.0)
else:
tde4.setWidgetFGColor(req, 'bounding_box_width', 0.0, 1.0, 0.0)
tde4.setWidgetFGColor(req, 'bounding_box_height', 0.0, 1.0, 0.0)
def _overscan_callback(requester, widget, action):
if widget == 'minus_05':
val = tde4.getWidgetValue(req, "overscan_value")
tde4.setWidgetValue(req, 'overscan_value', str(float(val)-0.5))
update_overscan_resolution()
update_bbox_fg_color()
if widget == 'minus_01':
val = tde4.getWidgetValue(req, "overscan_value")
tde4.setWidgetValue(req, 'overscan_value', str(float(val)-0.1))
update_overscan_resolution()
update_bbox_fg_color()
if widget == 'minus_005':
val = tde4.getWidgetValue(req, "overscan_value")
tde4.setWidgetValue(req, 'overscan_value', str(float(val)-0.05))
update_overscan_resolution()
update_bbox_fg_color()
if widget == 'plus_005':
val = tde4.getWidgetValue(req, "overscan_value")
tde4.setWidgetValue(req, 'overscan_value', str(float(val)+0.05))
update_overscan_resolution()
update_bbox_fg_color()
if widget == 'plus_01':
val = tde4.getWidgetValue(req, "overscan_value")
tde4.setWidgetValue(req, 'overscan_value', str(float(val)+0.1))
update_overscan_resolution()
update_bbox_fg_color()
if widget == 'plus_05':
val = tde4.getWidgetValue(req, "overscan_value")
tde4.setWidgetValue(req, 'overscan_value', str(float(val)+0.5))
update_overscan_resolution()
update_bbox_fg_color()
if widget == 'reset':
val = tde4.getWidgetValue(req, "overscan_value")
tde4.setWidgetValue(req, 'overscan_value', str(1.0))
update_overscan_resolution()
update_bbox_fg_color()
id_cam = tde4.getCurrentCamera()
id_lens = tde4.getCameraLens(id_cam)
model = tde4.getLensLDModel(id_lens)
num_frames = tde4.getCameraNoFrames(id_cam)
w_fb_cm = tde4.getLensFBackWidth(id_lens)
h_fb_cm = tde4.getLensFBackHeight(id_lens)
lco_x_cm = tde4.getLensLensCenterX(id_lens)
lco_y_cm = tde4.getLensLensCenterY(id_lens)
pxa = tde4.getLensPixelAspect(id_lens)
w_px = tde4.getCameraImageWidth(id_cam)
h_px = tde4.getCameraImageHeight(id_cam)
bbx_w_px, bbx_h_px = bbdld_compute_bounding_box()
footagePath = tde4.getCameraPath(id_cam) # "/path/to/file/trackboy.####.jpg"
footageDir, footageFilename = os.path.split(footagePath) # ["/path/to/file", "trackboy.####.jpg"]
footageHead, footageTail = footageFilename.split(".", 1) # ["trackboy", "####.jpg"]
dewarpedDirectory = footageDir
dewarpedFilename = "{0}_dewarped.{1}".format(footageHead, footageTail)
## GUI
req = tde4.createCustomRequester()
tde4.addFileWidget(req, "dewarped_directory", "Dewarped Directory", "*", dewarpedDirectory)
tde4.addTextFieldWidget(req, "dewarped_filename", "Dewarped Filename", dewarpedFilename)
tde4.setWidgetSensitiveFlag(req, 'dewarped_filename', 0)
tde4.addSeparatorWidget(req, 'separator1') #######################################
tde4.addTextFieldWidget(req, 'overscan_value', 'Overscan Value', "1.0")
tde4.setWidgetLinks(req,"overscan_value","separator1","", "separator1", "")
tde4.setWidgetAttachModes(req, "overscan_value","ATTACH_AS_IS","ATTACH_NONE","ATTACH_AS_IS","ATTACH_AS_IS")
tde4.setWidgetSensitiveFlag(req, 'overscan_value', 0)
tde4.setWidgetSize(req, "overscan_value", 80,20)
tde4.addButtonWidget(req, "minus_05", "-0.5")
tde4.setWidgetLinks(req,"minus_05","overscan_value","", "overscan_value", "")
tde4.setWidgetAttachModes(req, "minus_05","ATTACH_AS_IS","ATTACH_NONE","ATTACH_AS_IS","ATTACH_AS_IS")
tde4.addButtonWidget(req, "minus_01", "-0.1")
tde4.setWidgetOffsets(req,"minus_01",5,0,0,0)
tde4.setWidgetLinks(req,"minus_01","minus_05","", "minus_05", "")
tde4.setWidgetAttachModes(req,"minus_01","ATTACH_WIDGET","ATTACH_NONE","ATTACH_OPPOSITE_WIDGET","ATTACH_NONE")
tde4.addButtonWidget(req, "minus_005", "-0.05")
tde4.setWidgetOffsets(req,"minus_005",5,0,0,0)
tde4.setWidgetLinks(req,"minus_005","minus_01","", "minus_01", "")
tde4.setWidgetAttachModes(req,"minus_005","ATTACH_WIDGET","ATTACH_NONE","ATTACH_OPPOSITE_WIDGET","ATTACH_NONE")
tde4.addButtonWidget(req, "plus_005", "+0.05")
tde4.setWidgetOffsets(req,"plus_005",5,0,0,0)
tde4.setWidgetLinks(req,"plus_005","minus_005","", "minus_005", "")
tde4.setWidgetAttachModes(req,"plus_005","ATTACH_WIDGET","ATTACH_NONE","ATTACH_OPPOSITE_WIDGET","ATTACH_NONE")
tde4.addButtonWidget(req, "plus_01", "+0.1")
tde4.setWidgetOffsets(req,"plus_01",5,0,0,0)
tde4.setWidgetLinks(req,"plus_01","plus_005","", "plus_005", "")
tde4.setWidgetAttachModes(req,"plus_01","ATTACH_WIDGET","ATTACH_NONE","ATTACH_OPPOSITE_WIDGET","ATTACH_NONE")
tde4.addButtonWidget(req, "plus_05", "+0.5")
tde4.setWidgetOffsets(req,"plus_05",5,0,0,0)
tde4.setWidgetLinks(req,"plus_05","plus_01","", "plus_01", "")
tde4.setWidgetAttachModes(req,"plus_05","ATTACH_WIDGET","ATTACH_NONE","ATTACH_OPPOSITE_WIDGET","ATTACH_NONE")
tde4.addButtonWidget(req, "reset", "Reset")
tde4.setWidgetOffsets(req,"reset",5,0,0,0)
tde4.setWidgetLinks(req,"reset","plus_05","", "plus_05", "")
tde4.setWidgetAttachModes(req,"reset","ATTACH_WIDGET","ATTACH_NONE","ATTACH_OPPOSITE_WIDGET","ATTACH_NONE")
tde4.setWidgetBGColor(req, 'reset', 0.5, 0.0, 0.0)
tde4.addTextFieldWidget(req, 'overscan_width', 'Overscan Resolution', str(w_px))
tde4.setWidgetLinks(req,"overscan_width","minus_05","", "minus_05", "")
tde4.setWidgetAttachModes(req, "overscan_width","ATTACH_AS_IS","ATTACH_NONE","ATTACH_AS_IS","ATTACH_AS_IS")
tde4.setWidgetSize(req, "overscan_width", 80,20)
tde4.setWidgetSensitiveFlag(req, 'overscan_width', 0)
tde4.addTextFieldWidget(req, 'overscan_height', 'X', str(h_px))
tde4.setWidgetOffsets(req,"overscan_height",25,0,0,0)
tde4.setWidgetLinks(req,"overscan_height","overscan_width","", "overscan_width", "")
tde4.setWidgetAttachModes(req,"overscan_height","ATTACH_WIDGET","ATTACH_NONE","ATTACH_OPPOSITE_WIDGET","ATTACH_NONE")
tde4.setWidgetSize(req, "overscan_height", 80,20)
tde4.setWidgetSensitiveFlag(req, 'overscan_height', 0)
tde4.addTextFieldWidget(req, 'bounding_box_width', 'Bounding Box', str(bbx_w_px))
tde4.setWidgetLinks(req,"bounding_box_width","overscan_width","", "overscan_width", "")
tde4.setWidgetAttachModes(req, "bounding_box_width","ATTACH_AS_IS","ATTACH_NONE","ATTACH_AS_IS","ATTACH_AS_IS")
tde4.setWidgetSize(req, "bounding_box_width", 80,20)
tde4.setWidgetSensitiveFlag(req, 'bounding_box_width', 0)
tde4.addTextFieldWidget(req, 'bounding_box_height', 'X', str(bbx_h_px))
tde4.setWidgetOffsets(req,"bounding_box_height",25,0,0,0)
tde4.setWidgetLinks(req,"bounding_box_height","bounding_box_width","", "bounding_box_width", "")
tde4.setWidgetAttachModes(req,"bounding_box_height","ATTACH_WIDGET","ATTACH_NONE","ATTACH_OPPOSITE_WIDGET","ATTACH_NONE")
tde4.setWidgetSize(req, "bounding_box_height", 80,20)
tde4.setWidgetSensitiveFlag(req, 'bounding_box_height', 0)
#
## If "Negative Distortion" disable overscan.
if bbx_w_px <= w_px and bbx_h_px <= h_px:
tde4.setWidgetFGColor(req, 'overscan_value', 0.7, 0.7, 0.7)
tde4.setWidgetFGColor(req, 'overscan_width', 0.7, 0.7, 0.7)
tde4.setWidgetFGColor(req, 'overscan_height', 0.7, 0.7, 0.7)
tde4.setWidgetFGColor(req, 'bounding_box_width', 0.7, 0.7, 0.7)
tde4.setWidgetFGColor(req, 'bounding_box_height', 0.7, 0.7, 0.7)
tde4.setWidgetSensitiveFlag(req, 'minus_05', 0)
tde4.setWidgetSensitiveFlag(req, 'minus_01', 0)
tde4.setWidgetSensitiveFlag(req, 'minus_005', 0)
tde4.setWidgetSensitiveFlag(req, 'plus_005', 0)
tde4.setWidgetSensitiveFlag(req, 'plus_01', 0)
tde4.setWidgetSensitiveFlag(req, 'plus_05', 0)
tde4.setWidgetSensitiveFlag(req, 'reset', 0)
else:
tde4.setWidgetFGColor(req, 'bounding_box_width', 1.0, 0.0, 0.0)
tde4.setWidgetFGColor(req, 'bounding_box_height', 1.0, 0.0, 0.0)
#
## Callback
tde4.setWidgetCallbackFunction(req, 'minus_05', '_overscan_callback')
tde4.setWidgetCallbackFunction(req, 'minus_01', '_overscan_callback')
tde4.setWidgetCallbackFunction(req, 'minus_005', '_overscan_callback')
tde4.setWidgetCallbackFunction(req, 'plus_005', '_overscan_callback')
tde4.setWidgetCallbackFunction(req, 'plus_01', '_overscan_callback')
tde4.setWidgetCallbackFunction(req, 'plus_05', '_overscan_callback')
tde4.setWidgetCallbackFunction(req, 'reset', '_overscan_callback')
ret = tde4.postCustomRequester(req, "Easy Dewarp...", 900, 240, "Ok", "Cancel")
if ret==1:
cmd_list = []
for frame in range(1, num_frames+1):
focal = tde4.getCameraFocalLength(id_cam, frame)
focus = tde4.getCameraFocus(id_cam, frame)
inPath = tde4.getCameraFrameFilepath(id_cam, frame)
inDirectory, inFilename = os.path.split(inPath)
inHead, inTail = inFilename.split(".", 1)
outPath = r"{0}{1}{2}_dewarped.{3}".format(tde4.getWidgetValue(req, 'dewarped_directory'), DIRECTORY_SEPARATOR, inHead, inTail)
parameters = ""
for para in getLDmodelParameterList(model):
parameters += str(tde4.getLensLDAdjustableParameter(id_lens, para, focal, focus)) + " "
cmd = r'{0} -in {1} -out {2} -action remove_distortion -model "{3}" -parameters {4} -pixel_aspect {5} -lco {6} {7} -filmback {8} {9} -focal_length {10} -overscan {11} {12}'.format(WARP4_PATH, inPath, outPath, model, parameters, pxa, lco_x_cm, lco_y_cm, w_fb_cm, h_fb_cm, focal, tde4.getWidgetValue(req, 'overscan_width'), tde4.getWidgetValue(req, 'overscan_height'))
cmd_list.append(cmd)
p = multiprocessing.pool.ThreadPool(multiprocessing.cpu_count()-1) # Used ThreadPool instead of Pool. OS compat reasons...
start = time.time()
tde4.postProgressRequesterAndContinue("Easy Dewarp...", "Dewarping Frame 1 out of {0}...".format(len(cmd_list)), len(cmd_list), "Stop")
for i, returncode in enumerate(p.imap(partial(subprocess.call, shell=True), cmd_list)):
tde4.updateProgressRequester(i+1,"Dewarping Frame {0} out of {1}...".format(i+1, len(cmd_list)))
if returncode != 0:
print("%d command failed: %d" % (i, returncode))
tde4.unpostProgressRequester()
print 'Time elapsed: %s' % (time.time() - start)
# Copy miscellaneous data to Clipboard
cb_string = r"{0}{1}{2} {3} {4} {5} {6}".format(tde4.getWidgetValue(req, 'dewarped_directory'), DIRECTORY_SEPARATOR, dewarpedFilename, w_px, h_px, tde4.getWidgetValue(req, 'overscan_width'), tde4.getWidgetValue(req, 'overscan_height'))
tde4.setClipboardString(cb_string)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment