def run(self): from xystitch.pto.project import PTOProject '''Take in a list of pto files and merge them into pto''' pto_temp_file = ManagedTempFile.get(None, ".pto") args = ["pto_merge"] args.append("--output=%s" % pto_temp_file) for pto in self.ptos: args.append(pto.get_a_file_name()) print 'MERGING: %s' % (args, ) rc = execute.without_output(args) # go go go if not rc == 0: print print print #print 'Output:' #print output print 'rc: %d' % rc if rc == 35072: # ex: empty projects seem to cause this print 'Out of memory, expect malformed project file' raise Exception('failed pto_merge') if not os.path.exists(str(pto_temp_file)): raise Exception('Output file missing: %s' % (pto_temp_file, )) return PTOProject.from_temp_file(pto_temp_file)
def from_file_name(file_name, is_temporary=False): ret = PTOProject() ret.file_name = file_name if is_temporary: ret.temp_file = ManagedTempFile.from_existing(file_name) ret.parse() return ret
def get_a_file_name(self, prefix=None, postfix=None): '''Return a file name that has current .pto contents''' '''If doesn't have a real file, create a temp file''' if self.file_name: return self.file_name if postfix is None: postfix = ".pto" self.temp_file = ManagedTempFile.get(prefix, postfix) self.file_name = self.temp_file.file_name # hmmm... self.save() return self.file_name
def generate_core(self, image_file_names): project_file = ManagedTempFile.get(None, ".pto") command = "autopano-sift-c" args = list() # Try to post process them to make them more accurate #args.append("--refine") # Perform RANSAC to try to get bad control points out #args.append("--ransac") #args.append("on") # Unlimited matches args.append("--maxmatches") args.append("0") # ? #args.append("--maxdim") #args.append("10000") # Project file args.append(project_file.file_name) # Images for image_file_name in image_file_names: args.append(image_file_name) # go go go #(rc, output) = Execute.with_output(command, args) (rc, output) = (exc_ret_istr(command, args), '') if not rc == 0: print() print() print() print('output:\n%s' % output) raise Exception('Bad rc: %d' % rc) # We return PTO object, not string return PTOProject.from_temp_file(project_file)
def generate_core(self, image_file_names): command = "autopanoaj" args = list() project_file = ManagedTempFile.get(None, ".pto") # default is .oto args.append("/project:hugin") # Use image args instead of dir args.append("/f") args.append('/path:Z:\\tmp') # Images for image_file_name in image_file_names: args.append(image_file_name.replace("/tmp/", "Z:\\tmp\\")) # go go go #(rc, output) = Execute.with_output(command, args) rc, output = exc_ret_istr(command, args, print_out=True) if not rc == 0: raise Exception('Bad rc: %d' % rc) # We return PTO object, not string # Ditch the gen file because its unreliable shutil.move("/tmp/panorama0.pto", project_file.file_name) f = open(project_file.file_name, 'r') project_text = f.read() # Under WINE, do fixup project_text = project_text.replace('Z:\\tmp\\', '/tmp/') if 0: print() print() print() print(project_text) print() print() print() f.close() f = open(project_file.file_name, 'w') f.write(project_text) return PTOProject.from_temp_file(project_file)
def soften_composite(src_fn, dst_fn=None): tmp_file = ManagedTempFile.from_same_extension(src_fn) soften_gauss(src_fn, tmp_file.file_name) if dst_fn is None: dst_fn = src_fn args = ["convert"] args.append(src_fn) args.append(tmp_file.file_name) args.append("-compose") args.append("Blend") args.append("-define") args.append("compose:args=60,40%") args.append("-composite") # If we got a dest file, use it args.append(dst_fn) print('going to execute: %s' % (args, )) subp = subprocess.Popen(args, stdout=None, stderr=None, shell=False) subp.communicate() print('Execute done, rc: %s' % (subp.returncode, )) if not subp.returncode == 0: raise Exception('failed to form strong blur') # having some problems that looks like file isn't getting written to disk # monitoring for such errors # remove if I can root cause the source of these glitches for i in range(30): if os.path.exists(dst_fn): break if i == 0: print( 'WARNING: soften missing strong blur dest file name %s, waiting a bit...' % (dst_fn, )) time.sleep(0.1) else: raise Exception('Missing soften strong blur output file name %s' % dst_fn)
def hugin_form(self): ''' This is used when merging through fortify stitch Something is causing pto_merge to hang, but NOT ptomerge Only occurs if I wrap my commands in a script... The script doesn't do any fancy I/O redirection clear rm -rf /tmp/xystitch_* pr0nstitch *.jpg out.pto pto_merge produces nicer output than ptomerge While ptomerge produces the fields I need, it leaves some other junk I think pto_merge also calculates width/heigh attributes part of Hugin [mcmaster@gespenst first]$ pto_merge Warning: pto_merge requires at least 2 project files pto_merge: merges several project files pto_merge version 2010.4.0.854952d82c8f part of perl-Panotools-Script [mcmaster@gespenst first]$ ptomerge --help cannot read-open --help at /usr/share/perl5/Panotools/Script.pm line 91. man ptomerge ... ptomerge infile1.pto infile2.pto infile3.pto [...] outfile.pto ... ''' # However, this tool also generates an archaic .pto format that pto can parse, but I don't want to # pretend to merge into an empty project to force Hugin to clean it up # pto_merge --output=temp.pto /dev/null temp.pto if False: args = list() args.append("%s" % self.get_a_file_name()) args.append("%s" % self.get_a_file_name()) args.append("%s" % self.get_a_file_name()) (rc, output) = Execute.with_output("ptomerge", args) else: args = list() args.append("--output=%s" % self.get_a_file_name()) args.append("%s" % self.get_a_file_name()) if False: args.append("/dev/null") else: empty_file = ManagedTempFile.get(None, ".pto") open(empty_file.file_name, 'w').write('') args.append(empty_file.file_name) (rc, output) = Execute.with_output("pto_merge", args) if not rc == 0: print print print if rc == 35072: # ex: empty projects seem to cause this print 'Out of memory, expect malformed project file' print 'output:%s' % output raise Exception('Bad rc: %d' % rc) self.reopen()
def do_generate_control_points_by_pair(self, pair, image_fn_pair): '''high level function uses by sub-stitches. Given a pair of images make a best effort to return a .pto object''' ''' pair: ImageCoordinatePair() object image_fn_pair: tuple of strings Algorithm: First try to stitch normally (either whole image or partial depending on the mode) If that doesn't succeed and softening is enabled try up to three times to soften to produce a match If that still doesn't produce a decent solution return None and let higher levels deal with ''' soften_iterations = 3 print print #print 'Generating project for image pair (%s / %s, %s / %s)' % (image_fn_pair[0], str(pair[0]), image_fn_pair[1], str(pair[1])) print 'Generating project for image pair (%s, %s)' % (image_fn_pair[0], image_fn_pair[1]) if True: # Try raw initially print 'Attempting sharp match...' ret_project = self.try_control_points_with_position( pair, image_fn_pair) if ret_project: return ret_project print 'WARNING: bad project, attempting soften...' soften_image_file_0_managed = ManagedTempFile.from_same_extension( image_fn_pair[0]) soften_image_file_1_managed = ManagedTempFile.from_same_extension( image_fn_pair[1]) print 'Soften fn0: %s' % soften_image_file_0_managed.file_name print 'Soften fn1: %s' % soften_image_file_1_managed.file_name for i in xrange(soften_iterations): self.soften_try[i] += 1 # And then start screwing with it # Wonder if we can combine features from multiple soften passes? # Or at least take the maximum # Do features get much less accurate as the soften gets up there? print 'Attempting soften %d / %d' % (i + 1, soften_iterations) if i == 0: soften_composite(image_fn_pair[0], soften_image_file_0_managed.file_name) soften_composite(image_fn_pair[1], soften_image_file_1_managed.file_name) else: soften_composite(soften_image_file_0_managed.file_name) soften_composite(soften_image_file_1_managed.file_name) pair_soften_image_file_names = ( soften_image_file_0_managed.file_name, soften_image_file_1_managed.file_name) ret_project = self.try_control_points_with_position( pair, pair_soften_image_file_names) # Did we win? if ret_project: # Fixup the project to reflect the correct file names text = str(ret_project) if 0: print print 'Before sub' print print str(ret_project) print print print print '%s => %s' % (soften_image_file_0_managed.file_name, image_fn_pair[0]) text = text.replace(soften_image_file_0_managed.file_name, image_fn_pair[0]) print '%s => %s' % (soften_image_file_1_managed.file_name, image_fn_pair[1]) text = text.replace(soften_image_file_1_managed.file_name, image_fn_pair[1]) ret_project.set_text(text) if 0: print print 'After sub' print print str(ret_project) print print print #sys.exit(1) self.soften_ok[i] += 1 print 'Soften try: %s' % (self.soften_try, ) print 'Soften ok: %s' % (self.soften_ok, ) return ret_project print 'WARNING: gave up on generating control points!' return None
def control_points_by_subimage(self, pair, image_fn_pair): '''Stitch two images together by cropping to restrict overlap''' # subimage_factor: (y, x) overlap percent tuple or none for default # pair: pair of row/col or coordinate positions (used to determine relative positions) # (0, 0) at upper left # image_fn_pair: pair of image file names print 'Preparing subimage stitch on %s:%s' % (image_fn_pair[0], image_fn_pair[1]) ''' Just work on the overlap section, maybe even less ''' images = [ PImage.from_file(image_file_name) for image_file_name in image_fn_pair ] ''' image_0 used as reference 4 basic situations: left, right, up right 8 extended: 4 basic + corners Pairs should be sorted, which simplifies the logic ''' sub_image_0_x_delta = 0 sub_image_0_y_delta = 0 sub_image_1_x_end = images[1].width() sub_image_1_y_end = images[1].height() # Add some backlash margin # "more overlap" means will try a slightly larger area #margin = 0.05 x_overlap = self.x_overlap y_overlap = self.y_overlap # image 0 left of image 1? if pair.first.col < pair.second.col: # Keep image 0 right, image 1 left sub_image_0_x_delta = int(images[0].width() * x_overlap) sub_image_1_x_end = int( round(images[1].width() * (1.0 - x_overlap))) # image 0 above image 1? if pair.first.row < pair.second.row: # Keep image 0 top, image 1 bottom sub_image_0_y_delta = int(images[0].height() * y_overlap) sub_image_1_y_end = int( round(images[1].height() * (1.0 - y_overlap))) ''' print 'image 0 x delta: %d, y delta: %d' % (sub_image_0_x_delta, sub_image_0_y_delta) Note y starts at top in PIL ''' sub_image_0 = images[0].subimage(sub_image_0_x_delta, None, sub_image_0_y_delta, None) sub_image_1 = images[1].subimage(None, sub_image_1_x_end, None, sub_image_1_y_end) sub_image_0_file = ManagedTempFile.get(None, '.jpg') sub_image_1_file = ManagedTempFile.get(None, '.jpg') print 'sub image 0: width=%d, height=%d, name=%s' % (sub_image_0.width( ), sub_image_0.height(), sub_image_0_file.file_name) print 'sub image 1: width=%d, height=%d, name=%s' % (sub_image_1.width( ), sub_image_1.height(), sub_image_1_file.file_name) #sys.exit(1) sub_image_0.image.save(sub_image_0_file.file_name) sub_image_1.image.save(sub_image_1_file.file_name) sub_image_fn_pair = (sub_image_0_file.file_name, sub_image_1_file.file_name) # subimage file name symbolic link to subimage file name # this should be taken care of inside of control point actually #sub_link_to_sub = dict() # subimage to the image it came from sub_to_real = dict() sub_to_real[sub_image_0_file.file_name] = image_fn_pair[0] sub_to_real[sub_image_1_file.file_name] = image_fn_pair[1] # Returns a pto project object pair_project = self.control_point_gen.generate_core(sub_image_fn_pair) if pair_project is None: print 'WARNING: failed to gen control points @ %s' % repr(pair) return None # all we need to do is adjust xy positions # afaik above is way overcomplicated final_pair_project = pto_unsub( pair_project, (sub_image_0_file, sub_image_1_file), (sub_image_0_x_delta, sub_image_0_y_delta), sub_to_real) # Filenames become absolute #sys.exit(1) return final_pair_project
def generate_core(self, img_fns): # cpfind (and likely cpclean) trashes absolute file names # we need to restore them so that tools recognize the file names real_fn_base2full = {} args = list() project = PTOProject.from_default2() fn_obj = ManagedTempFile.get(None, ".pto") project.set_file_name(fn_obj.file_name) # Start with cpfind args.append("--multirow") args.append("--fullscale") # output file args.append("-o") args.append(project.file_name) # input file args.append(project.file_name) # Images for img_fn in img_fns: # xxx: why do we take the realpath? real_fn = os.path.realpath(img_fn) real_fn_base2full[os.path.basename(real_fn)] = img_fn project.add_image(real_fn, def_opt=True) project.save() print() print() print() print(project.get_text()) print() print() print() #(rc, output) = Execute.with_output('cpfind', args, print_output=self.print_output) print('cpfind' + ' '.join(args)) (rc, output) = exc_ret_istr('cpfind', args, print_out=self.print_output) print('PanoCP: cpfind done') if not rc == 0: print() print() print() print('output:') print(output) print() # Happens very rarely # 2018-01-24T04:00:18.720954: Exception: Bad rc: -11 # Log it but consider it a known failure if rc == -11: return None raise Exception('Bad rc: %d' % rc) # Now run cpclean args = list() # output file args.append("-o") args.append(project.file_name) # input file args.append(project.file_name) (rc, output) = exc_ret_istr('cpclean', args, print_out=self.print_output) print('PanoCP: cpclean done') if not rc == 0: print() print() print() print('output:') print(output) print() raise Exception('Bad rc: %d' % rc) project.reopen() print('Fixing image lines...') for il in project.image_lines: src = il.get_name() dst = real_fn_base2full[src] print(' %s => %s' % (src, dst)) il.set_name(dst) project.set_file_name(None) fn_obj = None # Will happen if failed to match # be optimistic: cpclean work will be wasted but avoids parsing project twice if len(project.get_control_point_lines()) == 0: print('WARNING: failed') return None return project