Skip to content

Commit

Permalink
Only use CG if direct solver fails
Browse files Browse the repository at this point in the history
  • Loading branch information
zfergus committed Oct 27, 2024
1 parent 0e8223f commit 3940aa7
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 48 deletions.
4 changes: 2 additions & 2 deletions seam_erasure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
__copyright__ = "Copyright 2016, Zachary Ferguson"
__credits__ = "Zachary Ferguson, Yotam Gingold, Songrun Liu, Alec Jacobson"
__license__ = "MIT"
__version__ = "1.0.5"
__version__ = "1.0.6"
__maintainer__ = "Zachary Ferguson"
__email__ = "[email protected]"
__email__ = "[email protected]"
__status__ = "Production"
37 changes: 19 additions & 18 deletions seam_erasure/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,20 @@ def parse_args(parser=None):
(in_mesh, in_texture, out_texture, loadFromDirectory, loadFromData,
method, sv_method)
"""
if(parser is None):
if (parser is None):
parser = create_parser()
args = parser.parse_args()

# Check that in_mesh exists
if(not os.path.exists(args.in_mesh)):
if (not os.path.exists(args.in_mesh)):
parser.error("Path to input mesh does not exist.")
# Check that in_texture exists
if(not os.path.exists(args.in_texture)):
if (not os.path.exists(args.in_texture)):
parser.error("Path to input texture(s) does not exist.")

loadFromDirectory = os.path.isdir(args.in_texture)
if(args.out_texture is None):
if(loadFromDirectory):
if (args.out_texture is None):
if (loadFromDirectory):
args.out_texture = os.path.normpath(args.in_texture +
"/erased") + "/"
else:
Expand All @@ -92,13 +92,13 @@ def parse_args(parser=None):
# Create a temporary output texture filename.
args.out_texture = in_path + '-erased' + in_ext
else:
if(loadFromDirectory):
if(os.path.isfile(args.out_texture)):
if (loadFromDirectory):
if (os.path.isfile(args.out_texture)):
parser.error("Input texture is a directory, but output is a " +
"file.")
args.out_texture += "/"
else:
if(os.path.isdir(args.out_texture)):
if (os.path.isdir(args.out_texture)):
parser.error("Input texture is a file, but output is a " +
"directory.")

Expand All @@ -107,7 +107,7 @@ def parse_args(parser=None):
"lerp": seam_erasure.SeamValueMethod.LERP}
sv_method = sv_methods[args.sv_method]

if(loadFromDirectory and sv_method == sv_methods["lerp"]):
if (loadFromDirectory and sv_method == sv_methods["lerp"]):
parser.error("Unable to perform seam value energy computation using " +
"lerp while performing batch texture solving.")

Expand All @@ -127,7 +127,7 @@ def loadTextures(in_path, loadFromDirectory, loadFromData):
Returns array of texture data and list of cooresponding
InputTextureFile objects.
"""
if(loadFromDirectory):
if (loadFromDirectory):
files = sorted([f for f in os.listdir(in_path) if
os.path.isfile(os.path.join(in_path, f))])
else:
Expand All @@ -138,13 +138,13 @@ def loadTextures(in_path, loadFromDirectory, loadFromData):
textureData = None
for f in files:
fpath = os.path.join(in_path, f)
if(loadFromData or os.path.splitext(f)[1] == ".data"):
if (loadFromData or os.path.splitext(f)[1] == ".data"):
data = weight_data.read_tex_from_path(fpath)[0]
isFloatTexture, isDataFile = True, True
else:
data = numpy.array(texture.load_texture(fpath))
isFloatTexture = not issubclass(data.dtype.type, numpy.integer)
if(not isFloatTexture):
if (not isFloatTexture):
data = data / 255.0
isDataFile = False

Expand Down Expand Up @@ -172,28 +172,28 @@ def saveTextures(outData, textures, out_path, loadFromDirectory):
assert len(outData.shape) == 3

out_dir = os.path.dirname(out_path)
if(out_dir != "" and not os.path.exists(out_dir)):
if (out_dir != "" and not os.path.exists(out_dir)):
os.makedirs(out_dir)

current_depth = 0
for textureFile in textures:
next_depth = current_depth + textureFile.depth

textureData = outData[:, :, current_depth:next_depth]
if(textureData.shape[2] < 2):
if (textureData.shape[2] < 2):
textureData = numpy.squeeze(textureData, axis=2)

if(not textureFile.isFloat):
if (not textureFile.isFloat):
textureData = to_uint8(textureData, normalize=False)

# Save the solved texture
if(loadFromDirectory):
if (loadFromDirectory):
base, ext = os.path.splitext(textureFile.name)
out_texture = os.path.join(out_path, base + "-erased" + ext)
else:
out_texture = out_path

if(textureFile.isDataFile):
if (textureFile.isDataFile):
weight_data.write_tex_to_path(out_texture, textureData)
else:
texture.save_texture(textureData, out_texture)
Expand All @@ -205,7 +205,8 @@ def main():
logging.basicConfig(
# format="[%(levelname)s] [%(asctime)s] %(message)s",
format="%(message)s",
datefmt="%m/%d/%Y %I:%M:%S %p", level=logging.INFO)
datefmt="%m/%d/%Y %I:%M:%S %p",
level=logging.INFO)
(in_mesh, in_texture, out_texture, loadFromDirectory, loadFromData,
sv_method, do_global) = parse_args()

Expand Down
65 changes: 37 additions & 28 deletions seam_erasure/seam_erasure.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,36 +199,45 @@ def erase_seam(mesh, texture, sv_method=SeamValueMethod.NONE, do_global=False):
dot_process = Process(target=print_dots)
dot_process.start()

# Use iterative solver for large textures
if (quad.nnz >= 2e6):
logging.info(
"Using iterative solver for large system (nnz={})".format(quad.nnz))
textureVec = texture.reshape(N, -1)
solution = numpy.empty(textureVec.shape)
for j in range(textureVec.shape[1]):
logging.info("Solving channel {}".format(j))

def callback(xk): return logging.debug("||Qx - l||={:.3e}".format(
numpy.linalg.norm(quad.dot(xk) + lin[:, j].toarray().flatten())))
solution[:, j], _ = scipy.sparse.linalg.cg(
quad, (-lin[:, j]).toarray(), x0=textureVec[:, j],
tol=1 / 255., atol=1 / 255., callback=callback)
# Use direct solver for smaller textures
else:
try:
# CVXOPT cholmod linsolve should be less memory intensive.
import cvxopt
import cvxopt.cholmod
quad = quad.tocoo()
system = cvxopt.spmatrix(quad.data, numpy.array(quad.row, dtype=int),
numpy.array(quad.col, dtype=int))
rhs = cvxopt.matrix(-lin.toarray())
cvxopt.cholmod.linsolve(system, rhs)
solution = numpy.array(rhs)
except Exception as e:
logging.warning(
f"cvxopt.cholmod failed, using scipy.sparse.linalg.spsolve(): {e:s}")
try:
# CVXOPT cholmod linsolve should be less memory intensive.
import cvxopt
import cvxopt.cholmod
quad = quad.tocoo()
system = cvxopt.spmatrix(quad.data, numpy.array(quad.row, dtype=int),
numpy.array(quad.col, dtype=int))
rhs = cvxopt.matrix(-lin.toarray())
cvxopt.cholmod.linsolve(system, rhs)
solution = numpy.array(rhs)
except Exception as e:
logging.warning("cvxopt.cholmod failed, "
+ "using scipy.sparse.linalg.spsolve(): %s" % e)
solution = scipy.sparse.linalg.spsolve(quad, -lin)
except Exception as e:
# Use iterative solver for large textures
logging.error(f"scipy.sparse.linalg.spsolve() failed: {e:s}")
logging.info(
f"Using iterative solver for large system (nnz={quad.nnz})")
textureVec = texture.reshape(N, -1)
solution = numpy.empty(textureVec.shape)
for j in range(textureVec.shape[1]):
logging.info("Solving channel {}".format(j))

def callback(xk):
return logging.debug("||Qx - l||={:.3e}".format(
numpy.linalg.norm(quad.dot(xk) + lin[:, j].toarray().flatten())))

solution[:, j], _ = scipy.sparse.linalg.cg(
quad,
(-lin[:, j]).toarray(), x0=textureVec[:, j],
rtol=1 / 255.,
atol=1 / 255.,
maxiter=100,
callback=callback
)

# Use direct solver for smaller textures
dot_process.terminate()

logging.info("Done\n")
Expand Down

0 comments on commit 3940aa7

Please sign in to comment.