Example #1
0
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))
Example #2
0
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))
Example #3
0
	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
Example #4
0
    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)
Example #5
0
    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)
Example #6
0
	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)
Example #7
0
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)
Example #8
0
            # 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)
Example #9
0
	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
Example #10
0
    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
Example #11
0
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))
Example #12
0
    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
Example #13
0
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))
Example #14
0
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()
Example #15
0
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()
Example #17
0
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)