def center_anchor_by_fn(pto): '''Rely on filename to make an anchor estimate''' pto.parse() m = ImageCoordinateMap.from_tagged_file_names(pto.get_file_names()) # Chose a decent center image fn = m.get_image(int(m.width() / 2), int(m.height() / 2)) print 'Selected %s as anchor' % fn anchor(pto, pto.get_image_by_fn(fn))
def center_anchor_by_fn(pto): '''Rely on filename to make an anchor estimate''' pto.parse() m = ImageCoordinateMap.from_tagged_file_names(pto.get_file_names()) # Chose a decent center image fn = m.get_image(int(m.width() / 2), int(m.height() / 2)) dbg('Selected %s as anchor' % fn) anchor(pto, pto.get_image_by_fn(fn))
def __init__(self, file_names, max_level, min_level = 0, out_dir_base=None): self.map = ImageCoordinateMap.from_tagged_file_names(file_names) #self.map.debug_print() self.max_level = max_level self.min_level = min_level self.out_dir_base = out_dir_base #self.set_out_extension('.png') self.set_out_extension('.jpg') self.zoom_factor = 2 self.t_width = 250 self.t_height = 250 # JPEG quality level, 1-100 or something self.quality = 70
def __init__(self, dir_in, threads=1): print "TileMapSource()" self.tw = 250 self.th = 250 self.threads = threads self.file_names = set() for f in os.listdir(dir_in): self.file_names.add(dir_in + "/" + f) self.src_dir = dir_in self.map = ImageCoordinateMap.from_tagged_file_names(self.file_names) self.x_tiles = self.map.width() self.y_tiles = self.map.height() print "Tile canvas width %d, height %d" % (self.width(), self.height()) MapSource.__init__(self)
def __init__(self, dir_in, threads=1): print 'TileMapSource()' self.tw = 250 self.th = 250 self.threads = threads self.file_names = set() for f in os.listdir(dir_in): self.file_names.add(dir_in + "/" + f) self.src_dir = dir_in self.map = ImageCoordinateMap.from_tagged_file_names(self.file_names) self.x_tiles = self.map.width() self.y_tiles = self.map.height() print 'Tile canvas width %d, height %d' % (self.width(), self.height()) MapSource.__init__(self)
def __init__(self, dir_in): print 'TileMapSource()' tw = None th = None tw = 256 th = 256 self.file_names = set() for f in os.listdir(dir_in): self.file_names.add(dir_in + "/" + f) self.dir_in = dir_in self.map = ImageCoordinateMap.from_tagged_file_names(self.file_names) self.x_tiles = self.map.width() self.y_tiles = self.map.height() self.tw = tw self.th = th print 'Tile canvas width %d, height %d' % (self.width(), self.height()) MapSource.__init__(self)
def rotate_tiles(src_dir, dst_dir, degrees, force = False, rc = False): self = Object() if src_dir[-1] == '/': src_dir = src_dir[0:-1] if dst_dir is None: dst_dir = src_dir + "-rotated" if os.path.exists(dst_dir): if force: shutil.rmtree(dst_dir) else: raise Exception('Output alrady exists, must set force') if not os.path.exists(dst_dir): os.mkdir(dst_dir) if degrees == 0: print 'WARNING: rotate got 0 degrees, aborting' return # And that only if the tiles are the same width and height # which is not required but the usual if not degrees in (90, 180, 270): #if not degrees in [180]: raise Exception('Only right angle degrees currently supported') print 'Rotating dir %s to dir %s %d degrees' % (src_dir, dst_dir, degrees) icm = ImageCoordinateMap.from_dir_tagged_file_names(src_dir) # Verify uniform size print "Verifying tile size...." self.tw = None self.th = None # For the first level we copy things over n = 0 for (src, row, col) in icm.images(): n += 1 pi = PImage.from_file(src) # I could actually set with / height here but right now this is # coming up fomr me accidentially using 256 x 256 tiles when the # standard is 250 x 250 if self.tw is None: self.tw = pi.width() if self.th is None: self.th = pi.height() if pi.width() != self.tw or pi.height() != self.th: raise Exception('Source image incorrect size') this_n = 0 for (src, src_row, src_col) in icm.images(): this_n += 1 extension = '.jpg' extension = '.' + src.split('.')[-1] if degrees == 180: dst_row = icm.height() - src_row - 1 dst_col = icm.width() - src_col - 1 elif degrees == 90: # r0-c0 => r0-cn # r0-cn => rn-cm dst_row = src_col dst_col = icm.height() - src_row - 1 elif degrees == 270: dst_row = icm.height() - src_col - 1 dst_col = src_row else: dst_row = src_row dst_col = src_col if rc: dst = os.path.join(dst_dir, 'c%04d_r%04d%s' % (dst_col, dst_row, extension)) else: dst = os.path.join(dst_dir, 'y%03d_x%03d%s' % (dst_row, dst_col, extension)) pi = PImage.from_file(src) # rotates CCW...w/e pip = pi.rotate(-degrees) print '%d / %d: %s => %s' % (this_n, n, src, dst) pip.save(dst)
# Delete the old key del icm.layout[(col + 1, row)] # Nothing to shift in except KeyError: pass if __name__ == "__main__": parser = argparse.ArgumentParser(description='Manipulate .pto files') parser.add_argument('--dry', action='store_true', help='Dont actually do anything') parser.add_argument('dir', help='Image directory to work on') parser.add_argument('actions', nargs='+', help='Actions to perform') args = parser.parse_args() print 'Constructing ICM' working_set = set(glob.glob(os.path.join(args.dir, '*.jpg'))) icm = ImageCoordinateMap.from_tagged_file_names(working_set) print 'ICM ready' for action in args.actions: print 'Action: %s' % action m = re.match('r([0-9]+)-', action) if m: row = int(m.group(1)) print 'Remove row %d' % row # if additional rows exist shift them for cur_row in xrange(row + 1, icm.rows): for cur_col in xrange(icm.cols): fn = icm.get_image(cur_col, cur_row) icm.set_image(cur_col, cur_row - 1, fn) icm.set_image(cur_col, cur_row, None)
def run(self): self.prep_out_dir_base() for self.zoom_level in xrange(self.max_level, self.min_level - 1, -1): print print '************' print 'Zoom level %d' % self.zoom_level out_dir = '%s/%d' % (self.out_dir_base, self.zoom_level) if os.path.exists(out_dir): os.system('rm -rf %s' % out_dir) os.mkdir(out_dir) # For the first level we copy things over if self.zoom_level == self.max_level: for (img_fn, row, col) in self.map.images(): dst = self.get_fn(row, col) if 0: print 'Direct copying %s => %s' % (img_fn, dst) shutil.copy(img_fn, dst) # This allows to do type conversions if needed # Presumably the conversion process for jps should be lossless although I haven't verified else: print 'Basic conversion %s => %s w/ quality %u' % (img_fn, dst, self.quality) pi = PImage.from_file(img_fn) # I could actually set with / height here but right now this is # coming up fomr me accidentially using 256 x 256 tiles when the # standard is 250 x 250 if self.t_width is None: self.t_width = pi.width() if self.t_height is None: self.t_height = pi.height() if pi.width() != self.t_width or pi.height() != self.t_height: raise Exception('Source image incorrect size') pi.save(dst, quality=self.quality) # Additional levels we take the image coordinate map and shrink else: # Prepare a new image coordinate map so we can form the next tile set new_cols = int(math.ceil(1.0 * self.map.width() / self.zoom_factor)) new_rows = int(math.ceil(1.0 * self.map.height() / self.zoom_factor)) #print 'Shrink by %s: cols %s => %s, rows %s => %s' % (str(self.zoom_factor), self.map.width(), new_cols, self.map.height(), new_rows) if 0: print self.map.debug_print() print new_map = ImageCoordinateMap(new_cols, new_rows) todo = new_rows * new_cols this = 0 for new_row in xrange(new_rows): old_row = new_row * self.zoom_factor for new_col in xrange(new_cols): this += 1 old_col = new_col * self.zoom_factor #print print 'z%d %d/%d: transforming row %d => %d, col %d => %d w/ quality %u' % (self.zoom_level, this, todo, old_row, new_row, old_col, new_col, self.quality) # Paste the old (4) images together imgp = PImage.from_filename_array([[self.get_old(old_row + 0, old_col + 0), self.get_old(old_row + 0, old_col + 1)], [self.get_old(old_row + 1, old_col + 0), self.get_old(old_row + 1, old_col + 1)]]) if imgp.width() != self.t_width * self.zoom_factor or imgp.height() != self.t_height * self.zoom_factor: print 'New image width %d, height: %d from tile width %d, height %d' % (imgp.width(), imgp.height(), self.t_width, self.t_height) raise Exception('Combined image incorrect size') scaled = imgp.get_scaled(0.5, filt=Image.ANTIALIAS) if scaled.width() != self.t_width or scaled.height() != self.t_height: raise Exception('Scaled image incorrect size') new_fn = self.get_fn(new_row, new_col) scaled.save(new_fn, quality=self.quality) #sys.exit(1) new_map.set_image(new_col, new_row, new_fn) # Next shrink will be on the previous tile set, not the original print 'Rotating image map' self.map = new_map
def run(self): bench = Benchmark() # The following will assume all of the images have the same size self.verify_images() # Copy project so we can trash it self.opt_project = self.project.copy() self.prepare_pto(self.opt_project) print 'Building image coordinate map' i_fns = [] for il in self.opt_project.image_lines: i_fns.append(il.get_name()) self.icm = ImageCoordinateMap.from_file_names(i_fns) print 'Built image coordinate map' if self.icm.width() <= self.tw: raise Exception('Decrease tile width') if self.icm.height() <= self.th: raise Exception('Decrease tile height') order = 2 ''' Phase 1: baseline Fully optimize a region in the center of our pano ''' print 'Phase 1: baseline' x0 = (self.icm.width() - self.tw) / 2 if x0 % order != 0: x0 += 1 x1 = x0 + self.tw - 1 y0 = (self.icm.height() - self.th) / 2 if y0 % order != 0: y0 += 1 y1 = y0 + self.th - 1 (center_pto, center_cplis) = self.partial_optimize(x0, x1, y0, y1) merge_pto(center_pto, self.opt_project, center_cplis) ''' Phase 2: predict Now use base center project to predict optimization positions for rest of project Assume that scanning left/right and that backlash will cause rows to alternate ("order 2") Note this will also fill in position estimates for unmatched images x = c0 * c + c1 * r + c2 y = c3 * c + c4 * r + c5 XXX: is there reason to have order 2 y coordinates? ''' print 'Phase 2: predict' ((c0s, c1s, c2s), (c3s, c4s, c5s)) = linearize(self.opt_project, center_pto, allow_missing=False, order=order) # Exclude filenames directly optimized center_is = set() for il in center_pto.get_image_lines(): center_is.add(self.opt_project.i2i(center_pto, il.get_index())) for row in xrange(self.icm.width()): for col in xrange(self.icm.height()): fn = self.icm.get_image(col, row) il = self.project.img_fn2l(fn) # Skip directly optimized lines if il.get_index() in center_is: continue # Otherwise predict position x = c0s[col % order] * col + c1s[col % order] * row + c2s[ col % order] il.set_variable('d', x) y = c3s[row % order] * col + c4s[row % order] * row + c5s[ row % order] il.set_variable('e', y) ''' Phase 3: optimize Moving out from center, optimize sub-sections based off of prediction Move in a cross pattern Left Right Up Down Expand scope ''' ''' x0 = self.icm.width() / 2 if x0 % order != 0: x0 += 1 x1 = x0 + self.tw - 1 y0 = self.icm.height() / 2 if y0 % order != 0: y0 += 1 y1 = y0 + self.th - 1 (center_pto, center_cplis) = self.partial_optimize(x0, x1, y0, y1) merge_pto(center_pto, self.opt_project, center_cplis) ''' if self.debug: print self.project bench.stop() print 'Optimized project in %s' % bench
def linearize(pto, pto_ref=None, allow_missing=False, order=2): if order is 0: raise Exception('Can not have order 0') if type(order) != type(0): raise Exception('Order is bad type') ''' Our model should be like this: -Each axis will have some degree of backlash. This backlash will create a difference between adjacent rows / cols -Axes may not be perfectly adjacent The naive approach would give: x = c * dx + xc y = r * dy + yc But really we need this: x = c * dx + r * dx/dy + xc y = c * dy/dx + r * dy + yc Each equation can be solved separately Need 3 points to solve each and should be in the expected direction of that line Perform a linear regression on each row/col? Might lead to very large y = mx + b equations for the column math ''' if pto_ref is None: pto_ref = pto ''' Phase 1: calculate linear system ''' # Start by building an image coordinate map so we know what x and y are pto_ref.parse() ref_fns = pto_ref.get_file_names() real_fns = pto.get_file_names() print 'Files (all: %d, ref: %d):' % (len(real_fns), len(ref_fns)) if 0: for fn in real_fns: if fn in ref_fns: ref_str = '*' else: ref_str = ' ' print ' %s%s' % (ref_str, fn) m_ref = ImageCoordinateMap.from_tagged_file_names(ref_fns) # Ref likely does not cover the entire map ((ref_x0, ref_x1), (ref_y0, ref_y1)) = m_ref.active_box() print 'Reference map uses x(%d:%d), y(%d:%d)' % (ref_x0, ref_x1, ref_y0, ref_y1) #m_real = ImageCoordinateMap.from_tagged_file_names(real_fns) #print 'Real map uses x(%d:%d), y(%d:%d)' % (ref_x0, ref_x1, ref_y0, ref_y1) #m.debug_print() ''' Ultimately trying to form this equation x = c0 * c + c1 * r + c2 y = c3 * c + c4 * r + c5 Except that constants will also have even and odd varities c2 and c5 will be taken from reasonable points of reference, likely (0, 0) or something like that ''' c0s = [] c1s = [] c3s = [] c4s = [] for cur_order in xrange(order): # Ex: ref_x0 = 3 # cur_order 0: start 4 # cur_order 1: start 3 if ref_x0 % order == cur_order: reg_x0 = ref_x0 else: reg_x0 = ref_x0 + 1 if ref_y0 % order == cur_order: reg_y0 = ref_y0 else: reg_y0 = ref_y0 + 1 reg_x1 = ref_x1 reg_y1 = ref_y1 print 'Order %d: using x(%d:%d), y(%d:%d)' % (cur_order, reg_x0, reg_x1, reg_y0, reg_y1) # Given a column find x (primary x) # dependence of x on col in specified rows c0s.append( regress_row(m_ref, pto_ref, cols=xrange(reg_x0, reg_x1 + 1, order), rows=xrange(reg_y0, reg_y1 + 1, order), selector=lambda x: x.x(), allow_missing=allow_missing)) # dependence of x on row in specified cols c1s.append( regress_col(m_ref, pto_ref, cols=xrange(reg_x0, reg_x1 + 1, order), rows=xrange(reg_y0, reg_y1 + 1, order), selector=lambda x: x.x(), allow_missing=allow_missing)) # Given a row find y (primary y) # dependence of y on col in specified rows c3s.append( regress_row(m_ref, pto_ref, cols=xrange(reg_x0, reg_x1 + 1, order), rows=xrange(reg_y0, reg_y1 + 1, order), selector=lambda x: x.y(), allow_missing=allow_missing)) # cdependence of y on row in specified cols c4s.append( regress_col(m_ref, pto_ref, cols=xrange(reg_x0, reg_x1 + 1, order), rows=xrange(reg_y0, reg_y1 + 1, order), selector=lambda x: x.y(), allow_missing=allow_missing)) # Now chose a point in the center # it doesn't have to be a good fitting point in the old system, it just has to be centered # Fix at the origin ''' Actually the even and the odd should have the same slope The only difference should be their offset ''' if 0: print 'Solution found' print ' x = %g c + %g r + TBD' % (c0s[0], c1s[0]) print ' y = %g c + %g r + TBD' % (c3s[0], c4s[0]) # Verify the solution matrix by checking it against the reference project print print 'Verifying reference solution matrix....' # Entire reference is assumed to be good always, no border (c2s_ref, c5s_ref) = calc_constants(order, m_ref, pto_ref, c0s, c1s, c3s, c4s, m_ref, allow_missing) #c1s = [c1 + 12 for c1 in c1s] # Print the solution matrx for debugging for cur_order in range(order): # XXX: if we really cared we could center these up # its easier to just run the centering algorithm after though if one cares print 'Reference order %d solution:' % cur_order print ' x = %g c + %g r + %g' % (c0s[cur_order], c1s[cur_order], c2s_ref[cur_order]) print ' y = %g c + %g r + %g' % (c3s[cur_order], c4s[cur_order], c5s_ref[cur_order]) return ((c0s, c1s, c2s_ref), (c3s, c4s, c5s_ref))
def run(self): bench = Benchmark() # The following will assume all of the images have the same size self.verify_images() # Copy project so we can trash it self.opt_project = self.project.copy() self.prepare_pto(self.opt_project) print 'Building image coordinate map' i_fns = [] for il in self.opt_project.image_lines: i_fns.append(il.get_name()) self.icm = ImageCoordinateMap.from_file_names(i_fns) print 'Built image coordinate map' if self.icm.width() <= self.tw: raise Exception('Decrease tile width') if self.icm.height() <= self.th: raise Exception('Decrease tile height') order = 2 ''' Phase 1: baseline Fully optimize a region in the center of our pano ''' print 'Phase 1: baseline' x0 = (self.icm.width() - self.tw) / 2 if x0 % order != 0: x0 += 1 x1 = x0 + self.tw - 1 y0 = (self.icm.height() - self.th) / 2 if y0 % order != 0: y0 += 1 y1 = y0 + self.th - 1 (center_pto, center_cplis) = self.partial_optimize(x0, x1, y0, y1) merge_pto(center_pto, self.opt_project, center_cplis) ''' Phase 2: predict Now use base center project to predict optimization positions for rest of project Assume that scanning left/right and that backlash will cause rows to alternate ("order 2") Note this will also fill in position estimates for unmatched images x = c0 * c + c1 * r + c2 y = c3 * c + c4 * r + c5 XXX: is there reason to have order 2 y coordinates? ''' print 'Phase 2: predict' ((c0s, c1s, c2s), (c3s, c4s, c5s)) = linearize(self.opt_project, center_pto, allow_missing=False, order=order) # Exclude filenames directly optimized center_is = set() for il in center_pto.get_image_lines(): center_is.add(self.opt_project.i2i(center_pto, il.get_index())) for row in xrange(self.icm.width()): for col in xrange(self.icm.height()): fn = self.icm.get_image(col, row) il = self.project.img_fn2l(fn) # Skip directly optimized lines if il.get_index() in center_is: continue # Otherwise predict position x = c0s[col%order] * col + c1s[col%order] * row + c2s[col%order] il.set_variable('d', x) y = c3s[row%order] * col + c4s[row%order] * row + c5s[row%order] il.set_variable('e', y) ''' Phase 3: optimize Moving out from center, optimize sub-sections based off of prediction Move in a cross pattern Left Right Up Down Expand scope ''' ''' x0 = self.icm.width() / 2 if x0 % order != 0: x0 += 1 x1 = x0 + self.tw - 1 y0 = self.icm.height() / 2 if y0 % order != 0: y0 += 1 y1 = y0 + self.th - 1 (center_pto, center_cplis) = self.partial_optimize(x0, x1, y0, y1) merge_pto(center_pto, self.opt_project, center_cplis) ''' if self.debug: print self.project bench.stop() print 'Optimized project in %s' % bench
def linearize(pto, pto_ref = None, allow_missing = False, order = 2): if order is 0: raise Exception('Can not have order 0') if type(order) != type(0): raise Exception('Order is bad type') ''' Our model should be like this: -Each axis will have some degree of backlash. This backlash will create a difference between adjacent rows / cols -Axes may not be perfectly adjacent The naive approach would give: x = c * dx + xc y = r * dy + yc But really we need this: x = c * dx + r * dx/dy + xc y = c * dy/dx + r * dy + yc Each equation can be solved separately Need 3 points to solve each and should be in the expected direction of that line Perform a linear regression on each row/col? Might lead to very large y = mx + b equations for the column math ''' if pto_ref is None: pto_ref = pto ''' Phase 1: calculate linear system ''' # Start by building an image coordinate map so we know what x and y are pto_ref.parse() ref_fns = pto_ref.get_file_names() real_fns = pto.get_file_names() print 'Files (all: %d, ref: %d):' % (len(real_fns), len(ref_fns)) if 0: for fn in real_fns: if fn in ref_fns: ref_str = '*' else: ref_str = ' ' print ' %s%s' % (ref_str, fn) m_ref = ImageCoordinateMap.from_tagged_file_names(ref_fns) # Ref likely does not cover the entire map ((ref_x0, ref_x1), (ref_y0, ref_y1)) = m_ref.active_box() print 'Reference map uses x(%d:%d), y(%d:%d)' % (ref_x0, ref_x1, ref_y0, ref_y1) #m_real = ImageCoordinateMap.from_tagged_file_names(real_fns) #print 'Real map uses x(%d:%d), y(%d:%d)' % (ref_x0, ref_x1, ref_y0, ref_y1) #m.debug_print() ''' Ultimately trying to form this equation x = c0 * c + c1 * r + c2 y = c3 * c + c4 * r + c5 Except that constants will also have even and odd varities c2 and c5 will be taken from reasonable points of reference, likely (0, 0) or something like that ''' c0s = [] c1s = [] c3s = [] c4s = [] for cur_order in xrange(order): # Ex: ref_x0 = 3 # cur_order 0: start 4 # cur_order 1: start 3 if ref_x0 % order == cur_order: reg_x0 = ref_x0 else: reg_x0 = ref_x0 + 1 if ref_y0 % order == cur_order: reg_y0 = ref_y0 else: reg_y0 = ref_y0 + 1 reg_x1 = ref_x1 reg_y1 = ref_y1 print 'Order %d: using x(%d:%d), y(%d:%d)' % (cur_order, reg_x0, reg_x1, reg_y0, reg_y1) # Given a column find x (primary x) # dependence of x on col in specified rows c0s.append(regress_row(m_ref, pto_ref, cols=xrange(reg_x0, reg_x1 + 1, order), rows=xrange(reg_y0, reg_y1 + 1, order), selector=lambda x: x.x(), allow_missing=allow_missing)) # dependence of x on row in specified cols c1s.append(regress_col(m_ref, pto_ref, cols=xrange(reg_x0, reg_x1 + 1, order), rows=xrange(reg_y0, reg_y1 + 1, order), selector=lambda x: x.x(), allow_missing=allow_missing)) # Given a row find y (primary y) # dependence of y on col in specified rows c3s.append(regress_row(m_ref, pto_ref, cols=xrange(reg_x0, reg_x1 + 1, order), rows=xrange(reg_y0, reg_y1 + 1, order), selector=lambda x: x.y(), allow_missing=allow_missing)) # cdependence of y on row in specified cols c4s.append(regress_col(m_ref, pto_ref, cols=xrange(reg_x0, reg_x1 + 1, order), rows=xrange(reg_y0, reg_y1 + 1, order), selector=lambda x: x.y(), allow_missing=allow_missing)) # Now chose a point in the center # it doesn't have to be a good fitting point in the old system, it just has to be centered # Fix at the origin ''' Actually the even and the odd should have the same slope The only difference should be their offset ''' if 0: print 'Solution found' print ' x = %g c + %g r + TBD' % (c0s[0], c1s[0]) print ' y = %g c + %g r + TBD' % (c3s[0], c4s[0]) # Verify the solution matrix by checking it against the reference project print print 'Verifying reference solution matrix....' # Entire reference is assumed to be good always, no border (c2s_ref, c5s_ref) = calc_constants(order, m_ref, pto_ref, c0s, c1s, c3s, c4s, m_ref, allow_missing) #c1s = [c1 + 12 for c1 in c1s] # Print the solution matrx for debugging for cur_order in range(order): # XXX: if we really cared we could center these up # its easier to just run the centering algorithm after though if one cares print 'Reference order %d solution:' % cur_order print ' x = %g c + %g r + %g' % (c0s[cur_order], c1s[cur_order], c2s_ref[cur_order]) print ' y = %g c + %g r + %g' % (c3s[cur_order], c4s[cur_order], c5s_ref[cur_order]) return ((c0s, c1s, c2s_ref), (c3s, c4s, c5s_ref))
def linear_reoptimize(pto, pto_ref=None, allow_missing=False, order=2, border=False): '''Change XY positions to match the trend in a linear XY positioned project (ex from XY stage). pto must have all images in pto_ref ''' if scipy is None: raise Exception('Re-optimizing requires scipi') if order is 0: raise Exception('Can not have order 0') if type(order) != type(0): raise Exception('Order is bad type') ''' Our model should be like this: -Each axis will have some degree of backlash. This backlash will create a difference between adjacent rows / cols -Axes may not be perfectly adjacent The naive approach would give: x = c * dx + xc y = r * dy + yc But really we need this: x = c * dx + r * dx/dy + xc y = c * dy/dx + r * dy + yc Each equation can be solved separately Need 3 points to solve each and should be in the expected direction of that line Perform a linear regression on each row/col? Might lead to very large y = mx + b equations for the column math ''' if pto_ref is None: pto_ref = pto ''' Phase 1: calculate linear system ''' # Start by building an image coordinate map so we know what x and y are pto_ref.parse() ref_fns = pto_ref.get_file_names() real_fns = pto.get_file_names() print 'Files (all: %d, ref: %d):' % (len(real_fns), len(ref_fns)) for fn in real_fns: if fn in ref_fns: ref_str = '*' else: ref_str = ' ' print ' %s%s' % (ref_str, fn) m_ref = ImageCoordinateMap.from_tagged_file_names(ref_fns) m_real = ImageCoordinateMap.from_tagged_file_names(real_fns) #m.debug_print() ''' Ultimately trying to form this equation x = c0 * c + c1 * r + c2 y = c3 * c + c4 * r + c5 Except that constants will also have even and odd varities c2 and c5 will be taken from reasonable points of reference, likely (0, 0) or something like that ''' c0s = [] c1s = [] c3s = [] c4s = [] for cur_order in range(order): # Given a column find x (primary x) c0s.append( regress_c0(m_ref, pto_ref, xrange(cur_order, m_ref.height(), order), allow_missing)) c1s.append( regress_c1(m_ref, pto_ref, xrange(cur_order, m_ref.width(), order), allow_missing)) # Given a row find y (primary y) c3s.append( regress_c3(m_ref, pto_ref, xrange(cur_order, m_ref.height(), order), allow_missing)) c4s.append( regress_c4(m_ref, pto_ref, xrange(cur_order, m_ref.width(), order), allow_missing)) # Now chose a point in the center # it doesn't have to be a good fitting point in the old system, it just has to be centered # Fix at the origin ''' Actually the even and the odd should have the same slope The only difference should be their offset ''' c2 = None c5 = None if 0: print 'Solution found' print ' x = %g c + %g r + TBD' % (c0, c1) print ' y = %g c + %g r + TBD' % (c3, c4) # Verify the solution matrix by checking it against the reference project print print 'Verifying reference solution matrix....' # Entire reference is assumed to be good always, no border (c2s_ref, c5s_ref) = calc_constants(order, m_ref, pto_ref, c0s, c1s, c3s, c4s, m_ref, allow_missing) #c1s = [c1 + 12 for c1 in c1s] # Print the solution matrx for debugging for cur_order in range(order): # XXX: if we really cared we could center these up # its easier to just run the centering algorithm after though if one cares print 'Reference order %d solution:' % cur_order print ' x = %g c + %g r + %g' % (c0s[cur_order], c1s[cur_order], c2s_ref[cur_order]) print ' y = %g c + %g r + %g' % (c3s[cur_order], c4s[cur_order], c5s_ref[cur_order]) calc_ref_xs = [] calc_ref_ys = [] ref_xs = [] ref_ys = [] print 'Errors:' x_last = None y_last = None for col in range(m_ref.width()): for row in range(m_ref.height()): fn = m_ref.get_image(col, row) if fn is None: continue il = pto_ref.get_image_by_fn(fn) col_eo = col % order row_eo = row % order x_calc = c0s[row_eo] * col + c1s[row_eo] * row + c2s_ref[row_eo] y_calc = c3s[col_eo] * col + c4s[col_eo] * row + c5s_ref[col_eo] calc_ref_xs.append(x_calc) calc_ref_ys.append(y_calc) x_orig = il.x() y_orig = il.y() ref_xs.append(x_orig) ref_ys.append(y_orig) print ' c%d r%d: x%g y%g (x%g, y%g)' % ( col, row, x_calc - x_orig, y_calc - y_orig, x_orig, y_orig) if col > 0: fn_old = m_ref.get_image(col - 1, row) if fn_old: il_old = pto_ref.get_image_by_fn(fn_old) print ' dx: %g' % (il.x() - il_old.x()) if col > 1: ''' x1' = x1 - x0 x2' = x2 - x1 x2'' = x2' - x1' = (x2 - x1) - (x1 - x0) = x2 - 2 x1 + x0 ''' fn_old2 = m_ref.get_image(col - 2, row) if fn_old2: il_old2 = pto_ref.get_image_by_fn(fn_old2) print ' dx2: %g' % (il.x() - 2 * il_old.x() + il_old2.x()) if row != 0: fn_old = m_ref.get_image(col, row - 1) if fn_old: il_old = pto_ref.get_image_by_fn(fn_old) print ' dy: %g' % (il.y() - il_old.y()) if row > 1: fn_old2 = m_ref.get_image(col, row - 2) if fn_old2: il_old2 = pto_ref.get_image_by_fn(fn_old2) print ' dy2: %g' % (il.y() - 2 * il_old.y() + il_old2.y()) x_ref_rms_error = rms_error_diff(calc_ref_xs, ref_xs) y_ref_rms_error = rms_error_diff(calc_ref_ys, ref_ys) print 'Reference RMS error x%g y%g' % (x_ref_rms_error, y_ref_rms_error) print #exit(1) ''' The reference project might not start at 0,0 Therefore scan through to find some good starting positions so that we can calc each point in the final project ''' print 'Anchoring solution...' ''' Calculate the constant at each reference image Compute reference positions from these values ''' ''' FIXME: we have to calculate these initially and then re-calc for border if required if top_bottom_backlash and border: row_border = 1 else: row_border = 0 if left_right_backlash and border: col_border = 1 else: col_border = 0 ''' row_border = 0 col_border = 0 (c2s, c5s) = calc_constants(order, m_real, pto_ref, c0s, c1s, c3s, c4s, m_ref, allow_missing, col_border, row_border) #c2s = [c2 + 30 for c2 in c2s] # Print the solution matrx for debugging for cur_order in range(order): # XXX: if we really cared we could center these up # its easier to just run the centering algorithm after though if one cares print 'Order %d solution:' % cur_order print ' x = %g c + %g r + %g' % (c0s[cur_order], c1s[cur_order], c2s[cur_order]) print ' y = %g c + %g r + %g' % (c3s[cur_order], c4s[cur_order], c5s[cur_order]) c2_rms = rms_errorl(c2s) c5_rms = rms_errorl(c5s) print 'RMS offset error x%g y%g' % (c2_rms, c5_rms) left_right_backlash = False top_bottom_backlash = False if c2_rms > c5_rms: print 'x offset varies most, expect left-right scanning' left_right_backlash = True else: print 'y offset varies most, expect top-bottom scanning' top_bottom_backlash = True #exit(1) ''' We have the solution matrix now so lets roll ''' optimized = set() for col in range(col_border, m_real.width() - col_border): for row in range(row_border, m_real.height() - row_border): fn = m_real.get_image(col, row) il = pto.get_image_by_fn(fn) if fn is None: if not allow_missing: raise Exception('Missing item') continue col_eo = col % order row_eo = row % order # FIRE! # take the dot product x = c0s[row_eo] * col + c1s[row_eo] * row + c2s[row_eo] y = c3s[col_eo] * col + c4s[col_eo] * row + c5s[col_eo] # And push it out #print '%s: c%d r%d => x%g y%d' % (fn, col, row, x, y) il.set_x(x) il.set_y(y) #print il optimized.add(fn) ''' Finally manually optimize those that were in the border area ''' if border: # Gather all file names # There are essentially four cases to do this faster but be lazy since it will still be O(images) to_manually_optimize = set() for col in range(0, m_real.width()): for row in range(m_real.height()): fn = m_real.get_image(col, row) il = pto.get_image_by_fn(fn) if fn is None: if not allow_missing: raise Exception('Missing item') continue if fn in optimized: continue to_manually_optimize.add(fn) # Prepare the pto to operate on the ones we want optimize_xy_only_for_images(pto, to_manually_optimize) # and run optimizer = PTOptimizer(pto) # Don't clear out the xy data we just calculated optimizer.reoptimize = False optimizer.run()
def rotate_tiles(src_dir, dst_dir, degrees, force=False, rc=False): self = Object() if src_dir[-1] == '/': src_dir = src_dir[0:-1] if dst_dir is None: dst_dir = src_dir + "-rotated" if os.path.exists(dst_dir): if force: shutil.rmtree(dst_dir) else: raise Exception('Output alrady exists, must set force') if not os.path.exists(dst_dir): os.mkdir(dst_dir) if degrees == 0: print 'WARNING: rotate got 0 degrees, aborting' return # And that only if the tiles are the same width and height # which is not required but the usual if not degrees in (90, 180, 270): #if not degrees in [180]: raise Exception('Only right angle degrees currently supported') print 'Rotating dir %s to dir %s %d degrees' % (src_dir, dst_dir, degrees) icm = ImageCoordinateMap.from_dir_tagged_file_names(src_dir) # Verify uniform size print "Verifying tile size...." self.tw = None self.th = None # For the first level we copy things over n = 0 for (src, row, col) in icm.images(): n += 1 pi = PImage.from_file(src) # I could actually set with / height here but right now this is # coming up fomr me accidentially using 256 x 256 tiles when the # standard is 250 x 250 if self.tw is None: self.tw = pi.width() if self.th is None: self.th = pi.height() if pi.width() != self.tw or pi.height() != self.th: raise Exception('Source image incorrect size') this_n = 0 for (src, src_row, src_col) in icm.images(): this_n += 1 extension = '.jpg' extension = '.' + src.split('.')[-1] if degrees == 180: dst_row = icm.height() - src_row - 1 dst_col = icm.width() - src_col - 1 elif degrees == 90: # r0-c0 => r0-cn # r0-cn => rn-cm dst_row = src_col dst_col = icm.height() - src_row - 1 elif degrees == 270: dst_row = icm.height() - src_col - 1 dst_col = src_row else: dst_row = src_row dst_col = src_col if rc: dst = os.path.join(dst_dir, 'c%04d_r%04d%s' % (dst_col, dst_row, extension)) else: dst = os.path.join(dst_dir, 'y%03d_x%03d%s' % (dst_row, dst_col, extension)) pi = PImage.from_file(src) # rotates CCW...w/e pip = pi.rotate(-degrees) print '%d / %d: %s => %s' % (this_n, n, src, dst) pip.save(dst)
def linear_reoptimize(pto, pto_ref = None, allow_missing = False, order = 2, border = False): '''Change XY positions to match the trend in a linear XY positioned project (ex from XY stage). pto must have all images in pto_ref ''' if scipy is None: raise Exception('Re-optimizing requires scipi') if order is 0: raise Exception('Can not have order 0') if type(order) != type(0): raise Exception('Order is bad type') ''' Our model should be like this: -Each axis will have some degree of backlash. This backlash will create a difference between adjacent rows / cols -Axes may not be perfectly adjacent The naive approach would give: x = c * dx + xc y = r * dy + yc But really we need this: x = c * dx + r * dx/dy + xc y = c * dy/dx + r * dy + yc Each equation can be solved separately Need 3 points to solve each and should be in the expected direction of that line Perform a linear regression on each row/col? Might lead to very large y = mx + b equations for the column math ''' if pto_ref is None: pto_ref = pto ''' Phase 1: calculate linear system ''' # Start by building an image coordinate map so we know what x and y are pto_ref.parse() ref_fns = pto_ref.get_file_names() real_fns = pto.get_file_names() print 'Files (all: %d, ref: %d):' % (len(real_fns), len(ref_fns)) for fn in real_fns: if fn in ref_fns: ref_str = '*' else: ref_str = ' ' print ' %s%s' % (ref_str, fn) m_ref = ImageCoordinateMap.from_tagged_file_names(ref_fns) m_real = ImageCoordinateMap.from_tagged_file_names(real_fns) #m.debug_print() ''' Ultimately trying to form this equation x = c0 * c + c1 * r + c2 y = c3 * c + c4 * r + c5 Except that constants will also have even and odd varities c2 and c5 will be taken from reasonable points of reference, likely (0, 0) or something like that ''' c0s = [] c1s = [] c3s = [] c4s = [] for cur_order in range(order): # Given a column find x (primary x) c0s.append(regress_c0(m_ref, pto_ref, xrange(cur_order, m_ref.height(), order), allow_missing)) c1s.append(regress_c1(m_ref, pto_ref, xrange(cur_order, m_ref.width(), order), allow_missing)) # Given a row find y (primary y) c3s.append(regress_c3(m_ref, pto_ref, xrange(cur_order, m_ref.height(), order), allow_missing)) c4s.append(regress_c4(m_ref, pto_ref, xrange(cur_order, m_ref.width(), order), allow_missing)) # Now chose a point in the center # it doesn't have to be a good fitting point in the old system, it just has to be centered # Fix at the origin ''' Actually the even and the odd should have the same slope The only difference should be their offset ''' c2 = None c5 = None if 0: print 'Solution found' print ' x = %g c + %g r + TBD' % (c0, c1) print ' y = %g c + %g r + TBD' % (c3, c4) # Verify the solution matrix by checking it against the reference project print print 'Verifying reference solution matrix....' # Entire reference is assumed to be good always, no border (c2s_ref, c5s_ref) = calc_constants(order, m_ref, pto_ref, c0s, c1s, c3s, c4s, m_ref, allow_missing) #c1s = [c1 + 12 for c1 in c1s] # Print the solution matrx for debugging for cur_order in range(order): # XXX: if we really cared we could center these up # its easier to just run the centering algorithm after though if one cares print 'Reference order %d solution:' % cur_order print ' x = %g c + %g r + %g' % (c0s[cur_order], c1s[cur_order], c2s_ref[cur_order]) print ' y = %g c + %g r + %g' % (c3s[cur_order], c4s[cur_order], c5s_ref[cur_order]) calc_ref_xs = [] calc_ref_ys = [] ref_xs = [] ref_ys = [] print 'Errors:' x_last = None y_last = None for col in range(m_ref.width()): for row in range(m_ref.height()): fn = m_ref.get_image(col, row) if fn is None: continue il = pto_ref.get_image_by_fn(fn) col_eo = col % order row_eo = row % order x_calc = c0s[row_eo] * col + c1s[row_eo] * row + c2s_ref[row_eo] y_calc = c3s[col_eo] * col + c4s[col_eo] * row + c5s_ref[col_eo] calc_ref_xs.append(x_calc) calc_ref_ys.append(y_calc) x_orig = il.x() y_orig = il.y() ref_xs.append(x_orig) ref_ys.append(y_orig) print ' c%d r%d: x%g y%g (x%g, y%g)' % (col, row, x_calc - x_orig, y_calc - y_orig, x_orig, y_orig) if col > 0: fn_old = m_ref.get_image(col - 1, row) if fn_old: il_old = pto_ref.get_image_by_fn(fn_old) print ' dx: %g' % (il.x() - il_old.x()) if col > 1: ''' x1' = x1 - x0 x2' = x2 - x1 x2'' = x2' - x1' = (x2 - x1) - (x1 - x0) = x2 - 2 x1 + x0 ''' fn_old2 = m_ref.get_image(col - 2, row) if fn_old2: il_old2 = pto_ref.get_image_by_fn(fn_old2) print ' dx2: %g' % (il.x() - 2 * il_old.x() + il_old2.x()) if row != 0: fn_old = m_ref.get_image(col, row - 1) if fn_old: il_old = pto_ref.get_image_by_fn(fn_old) print ' dy: %g' % (il.y() - il_old.y()) if row > 1: fn_old2 = m_ref.get_image(col, row - 2) if fn_old2: il_old2 = pto_ref.get_image_by_fn(fn_old2) print ' dy2: %g' % (il.y() - 2 * il_old.y() + il_old2.y()) x_ref_rms_error = rms_error_diff(calc_ref_xs, ref_xs) y_ref_rms_error = rms_error_diff(calc_ref_ys, ref_ys) print 'Reference RMS error x%g y%g' % (x_ref_rms_error, y_ref_rms_error) print #exit(1) ''' The reference project might not start at 0,0 Therefore scan through to find some good starting positions so that we can calc each point in the final project ''' print 'Anchoring solution...' ''' Calculate the constant at each reference image Compute reference positions from these values ''' ''' FIXME: we have to calculate these initially and then re-calc for border if required if top_bottom_backlash and border: row_border = 1 else: row_border = 0 if left_right_backlash and border: col_border = 1 else: col_border = 0 ''' row_border = 0 col_border = 0 (c2s, c5s) = calc_constants(order, m_real, pto_ref, c0s, c1s, c3s, c4s, m_ref, allow_missing, col_border, row_border) #c2s = [c2 + 30 for c2 in c2s] # Print the solution matrx for debugging for cur_order in range(order): # XXX: if we really cared we could center these up # its easier to just run the centering algorithm after though if one cares print 'Order %d solution:' % cur_order print ' x = %g c + %g r + %g' % (c0s[cur_order], c1s[cur_order], c2s[cur_order]) print ' y = %g c + %g r + %g' % (c3s[cur_order], c4s[cur_order], c5s[cur_order]) c2_rms = rms_errorl(c2s) c5_rms = rms_errorl(c5s) print 'RMS offset error x%g y%g' % (c2_rms, c5_rms) left_right_backlash = False top_bottom_backlash = False if c2_rms > c5_rms: print 'x offset varies most, expect left-right scanning' left_right_backlash = True else: print 'y offset varies most, expect top-bottom scanning' top_bottom_backlash = True #exit(1) ''' We have the solution matrix now so lets roll ''' optimized = set() for col in range(col_border, m_real.width() - col_border): for row in range(row_border, m_real.height() - row_border): fn = m_real.get_image(col, row) il = pto.get_image_by_fn(fn) if fn is None: if not allow_missing: raise Exception('Missing item') continue col_eo = col % order row_eo = row % order # FIRE! # take the dot product x = c0s[row_eo] * col + c1s[row_eo] * row + c2s[row_eo] y = c3s[col_eo] * col + c4s[col_eo] * row + c5s[col_eo] # And push it out #print '%s: c%d r%d => x%g y%d' % (fn, col, row, x, y) il.set_x(x) il.set_y(y) #print il optimized.add(fn) ''' Finally manually optimize those that were in the border area ''' if border: # Gather all file names # There are essentially four cases to do this faster but be lazy since it will still be O(images) to_manually_optimize = set() for col in range(0, m_real.width()): for row in range(m_real.height()): fn = m_real.get_image(col, row) il = pto.get_image_by_fn(fn) if fn is None: if not allow_missing: raise Exception('Missing item') continue if fn in optimized: continue to_manually_optimize.add(fn) # Prepare the pto to operate on the ones we want optimize_xy_only_for_images(pto, to_manually_optimize) # and run optimizer = PTOptimizer(pto) # Don't clear out the xy data we just calculated optimizer.reoptimize = False optimizer.run()
from pr0ntools.stitch.image_coordinate_map import ImageCoordinateMap import subprocess import shutil if __name__ == "__main__": parser = argparse.ArgumentParser(description='create tiles from unstitched images') parser.add_argument('--border', default='1', help='border size') parser.add_argument('pto', default='out.pto', nargs='?', help='pto project') args = parser.parse_args() args.border = int(args.border, 0) pto_orig = PTOProject.from_file_name(args.pto) img_fns = [] for il in pto_orig.get_image_lines(): img_fns.append(il.get_name()) icm = ImageCoordinateMap.from_tagged_file_names(img_fns) # Reduced .pto pto_red = pto_orig.copy() # Delete all lines not in the peripheral pto_orig.build_image_fn_map() ils_del = [] for y in xrange(args.border, icm.height() - args.border): for x in xrange(args.border, icm.width() - args.border): im = icm.get_image(x, y) if im is None: continue ils_del.append(pto_orig.img_fn2il[im]) print 'Deleting %d / %d images' % (len(ils_del), icm.width() * icm.height()) pto_red.del_images(ils_del) pto_red.save_as(pto_orig.file_name.replace('.pto', '_sm.pto'), is_new_filename=True)