hugin.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. """
  2. Various utilities to work with Hugin's file format (.pto)
  3. http://hugin.sourceforge.net/docs/manual/PTOptimizer.html
  4. Ideally, we would use the SWIG-generated Python interface, but it might
  5. not be available everywhere, and it is missing some useful bits of the API
  6. (most notably graph handling).
  7. """
  8. import sys
  9. import re
  10. from collections import defaultdict
  11. from pprint import pprint
  12. # Regex matching a single item in a line
  13. ITEM_REGEX = re.compile(r"([a-zA-Z]+[^\" ]+ ?)|([a-zA-Z]+\".*?\" ?)")
  14. def parse_pto(f):
  15. """Very basic parsing of a .pto file, without attempting to interpret any
  16. fields. Might be missing some functionalities.
  17. Returns a dict of the form:
  18. {'i': [
  19. ['w3264', 'h2448', 'f0', 'v=0', 'n"my picture.jpg"'],
  20. ['w3264', 'h2448', 'f0', 'v=0', 'n"my other picture.jpg"'],
  21. ],
  22. 'p': [
  23. ['f1', 'w16117', 'h2284', 'v360', 'E14.3378', 'R0', 'S0,16117,525,2116', 'n"TIFF_m c:LZW r:CROP"']
  24. ],
  25. }
  26. In other words, each line is represent as a list of its items,
  27. classified by the "category" (first char on the line)
  28. """
  29. res = defaultdict(list)
  30. for line in f:
  31. line = line.strip()
  32. if len(line) == 0 or line.startswith("#"):
  33. continue
  34. if line == "*":
  35. break
  36. category = line[0]
  37. items = [m.group(0).strip() for m in ITEM_REGEX.finditer(line[2:])]
  38. res[category].append(items)
  39. return res
  40. def union_find(nodes, edges):
  41. """Very inefficient union-find algorithm to find connected components"""
  42. def union(n1, n2):
  43. if root[n1] == root[n2]:
  44. return
  45. components[root[n1]].update(components[root[n2]])
  46. components[root[n2]].clear()
  47. del components[root[n2]]
  48. # Update root for all nodes that pointed to root[n2]
  49. root_n2 = root[n2]
  50. for node, parent in root.items():
  51. if parent == root_n2:
  52. root[node] = root[n1]
  53. components = {node: {node} for node in nodes}
  54. root = {node: node for node in nodes}
  55. for (n1, n2) in edges:
  56. union(n1, n2)
  57. return components.values()
  58. def compute_connected_components(pto):
  59. """Compute connected components of a project, where nodes are images and
  60. edges are control points linking images. Images are index from 0."""
  61. nb_images = len(pto['i'])
  62. # Set of frozenset({image1, image2})
  63. edges = set()
  64. for controlpoint in pto['c']:
  65. image1 = image2 = None
  66. for item in controlpoint:
  67. if item[0] == 'n':
  68. image1 = int(item[1:])
  69. if item[0] == 'N':
  70. image2 = int(item[1:])
  71. if image1 is not None and image2 is not None and image1 != image2:
  72. edges.add(frozenset({image1, image2}))
  73. return union_find(list(range(nb_images)), edges)
  74. def filter_images(input_pto, images, output_pto):
  75. """Filter the input pto file to only keep the specified images, and write
  76. the result to [output_pto].
  77. We assume that only cpfind has been run on the input pto. In
  78. particular, we don't touch optimisation variables or project
  79. parameters (FOV, size, etc), since they are not normally set at this
  80. early stage in the pipeline.
  81. """
  82. with open(input_pto) as f:
  83. with open(output_pto, 'w') as out:
  84. image_id = 0
  85. for line in f:
  86. # Check image declarations
  87. if line[0] == 'i':
  88. if image_id in images:
  89. out.write(line)
  90. image_id += 1
  91. # Check control points
  92. elif line[0] == 'c':
  93. discard = False
  94. for item in ITEM_REGEX.finditer(line[2:]):
  95. item = item.group(0).strip()
  96. # The control point references a removed image
  97. if (item[0] == 'n' or item[0] == 'N') and int(item[1:]) not in images:
  98. discard = True
  99. if not discard:
  100. out.write(line)
  101. # Just copy the line to the output
  102. else:
  103. out.write(line)
  104. if __name__ == '__main__':
  105. with open(sys.argv[1]) as f:
  106. #pprint(parse_pto(f))
  107. pprint(compute_connected_components(parse_pto(f)))