6
0

result.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import click
  2. import flask
  3. import simplejson as json
  4. from flask.cli import AppGroup
  5. from pycs import app
  6. from pycs import database as db
  7. result_cli = AppGroup("result", short_help="Result operations")
  8. @result_cli.command("export")
  9. @click.argument("project_id")
  10. @click.argument("indent", required=False)
  11. @click.argument("output", required=False)
  12. def export(project_id, output, indent):
  13. """ Export results for a specific project or for all projects """
  14. if project_id == "all":
  15. projects = db.Project.query.all()
  16. app.logger.info(f"Exporting results for all projects ({len(projects)})!")
  17. if output is None:
  18. output = "output.json"
  19. else:
  20. project = db.Project.query.get(project_id)
  21. if project is None:
  22. app.logger.error(f"Could not find project with ID {project_id}!")
  23. return
  24. app.logger.info(f"Exporting results for project {project}!")
  25. projects = [project]
  26. if output is None:
  27. output = f"output_project_{int(project_id):04d}.json"
  28. app.logger.info(f"Exporting to {output}")
  29. results = []
  30. for project in projects:
  31. project_files = [
  32. dict(**f.serialize(),
  33. results=[
  34. dict(**r.serialize(), label=r.label.serialize() if r.label is not None else None)
  35. for r in f.results.all()
  36. ])
  37. for f in project.files.all() if f.results.count() != 0
  38. ]
  39. results.append(dict(
  40. project_id=project.id,
  41. files=project_files,
  42. labels=[lab.serialize() for lab in project.labels.all()],
  43. ))
  44. if indent is not None:
  45. indent = int(indent)
  46. with open(output, "w", encoding="utf-8") as out_f:
  47. flask.json.dump(results, out_f, app=app, indent=indent)
  48. @result_cli.command("restore")
  49. @click.argument("infile")
  50. @click.option("--dry-run", is_flag=True)
  51. def restore(infile, dry_run):
  52. with open(infile) as f:
  53. results = json.load(f)
  54. for project_results in results:
  55. project = db.Project.get_or_404(project_results["project_id"])
  56. for file_results in project_results["files"]:
  57. file = db.File.get_or_404(file_results["id"])
  58. assert file.path == file_results["path"]
  59. # first check for new and changed results
  60. for _result in file_results["results"]:
  61. if not _is_data_valid(**_result):
  62. continue
  63. result = get_result_or_none(file, **_result)
  64. user1 = _result["origin_user"]
  65. data1 = _result["data"]
  66. ref1 = (_result["label"] or {}).get("reference")
  67. # lab1 = (_result["label"] or {}).get("id")
  68. if result is None:
  69. # we have a new result entry
  70. if not dry_run:
  71. file.create_result(
  72. result_type="bounding-box",
  73. origin="user",
  74. origin_user=user1,
  75. label=ref1,
  76. data=data1,
  77. commit=True
  78. )
  79. print(" | ".join([
  80. f"Project #{project.id:< 6d}"
  81. f"File #{file.id:< 6d} [{file.name:^30s}]",
  82. "[New Result]",
  83. f"User: {user1 or '':<10s}",
  84. f"Data: {data1}, Label-Ref: {ref1}",
  85. ])
  86. )
  87. continue
  88. assert result.file_id == _result["file_id"]
  89. user0 = result.origin_user
  90. data0 = result.data
  91. ref0 = getattr(result.label, "reference", None)
  92. # lab0 = getattr(result.label, "id", None)
  93. is_same_data = _check_data(data0, data1)
  94. if is_same_data and (ref0 == ref1 or ref1 is None):
  95. # nothing to change
  96. continue
  97. print(" | ".join([
  98. f"Project #{project.id:< 6d}"
  99. f"File #{file.id:< 6d} [{file.name:^30s}]",
  100. ]), end=" | "
  101. )
  102. if not is_same_data:
  103. # data was updated
  104. print(" | ".join([
  105. "[Data updated]",
  106. f"User: {user1 or '':<10s}",
  107. f"Data: {data0} -> {data1}"
  108. ]), end=" | "
  109. )
  110. assert user1 is not None
  111. if not dry_run:
  112. result.origin_user = user1
  113. result.data = data1
  114. if ref0 != ref1:
  115. assert user1 is not None
  116. if not dry_run:
  117. result.origin_user = user1
  118. if ref1 is None:
  119. # label was deleted
  120. print("[Label Deleted]")
  121. if not dry_run:
  122. result.label_id = None
  123. else:
  124. # label was updated
  125. print(" | ".join([
  126. "[Label updated]",
  127. f"User: {user0 or '':<10s} -> {user1 or '':<10s}",
  128. f"{ref0 or 'UNK':<6s} -> {ref1 or 'UNK':<6s}"
  129. ])
  130. )
  131. label = project.label_by_reference(ref1)
  132. if not dry_run:
  133. result.label_id = label.id
  134. else:
  135. print()
  136. if not dry_run:
  137. result.commit()
  138. # then check for deleted results
  139. for result in file.results.all():
  140. if result.origin != "user" or result.type != "bounding-box":
  141. continue
  142. found = False
  143. for _result in file_results["results"]:
  144. if not _is_data_valid(**_result):
  145. continue
  146. if _check_data(result.data, _result["data"]):
  147. found = True
  148. break
  149. if not found:
  150. print(" | ".join([
  151. f"Project #{project.id:< 6d}"
  152. f"File #{file.id:< 6d} [{file.name:^30s}]",
  153. "[Result deleted]",
  154. f"{result.data}",
  155. f"{result.label}",
  156. ])
  157. )
  158. if not dry_run:
  159. result.delete()
  160. def _is_data_valid(*, data, type, origin, **kwargs):
  161. wh = (None, None) if data is None else (data["w"], data["h"])
  162. return (type != "labeled-image" and
  163. origin == "user" and
  164. 0 not in wh)
  165. def _check_data(data0, data1):
  166. if None in (data0, data1):
  167. return data0 == data1 == None
  168. for key in data0:
  169. if data1.get(key) != data0.get(key):
  170. return False
  171. return True
  172. def get_result_or_none(file: db.File, id: int, data: dict, **kwargs):
  173. result = db.Result.query.filter(
  174. db.Result.id==id, db.Result.file_id==file.id).one_or_none()
  175. if result is not None:
  176. return result
  177. for other_results in file.results.all():
  178. if _check_data(data, other_results.data):
  179. # import pdb; pdb.set_trace()
  180. return other_results