TrackingImageDatabaseInspector.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. /****************************************************************************
  2. * Copyright 2019 Nreal Techonology Limited. All rights reserved.
  3. *
  4. * This file is part of NRSDK.
  5. *
  6. * https://www.nreal.ai/
  7. *
  8. *****************************************************************************/
  9. namespace NRKernal
  10. {
  11. using System.Collections.Generic;
  12. using System.IO;
  13. using UnityEditor;
  14. using UnityEngine;
  15. using LitJson;
  16. /// <summary> A tracking image database inspector. </summary>
  17. [CustomEditor(typeof(NRTrackingImageDatabase))]
  18. public class TrackingImageDatabaseInspector : Editor
  19. {
  20. /// <summary> Height of the image spacer. </summary>
  21. private const float m_ImageSpacerHeight = 55f;
  22. /// <summary> Size of the page. </summary>
  23. private const int m_PageSize = 5;
  24. /// <summary> Height of the header. </summary>
  25. private const float m_HeaderHeight = 30f;
  26. /// <summary> The container start. </summary>
  27. private static readonly Vector2 m_ContainerStart = new Vector2(14f, 87f);
  28. /// <summary> The quality background executor. </summary>
  29. private static BackgroundJobExecutor m_QualityBackgroundExecutor = new BackgroundJobExecutor();
  30. /// <summary> The database for quality jobs. </summary>
  31. private static NRTrackingImageDatabase m_DatabaseForQualityJobs = null;
  32. /// <summary> The updated quality scores. </summary>
  33. private static Dictionary<string, JsonData> m_UpdatedQualityScores = new Dictionary<string, JsonData>();
  34. /// <summary> Dictionary of temporary widths. </summary>
  35. private static Dictionary<string, float> m_TempWidthDict = new Dictionary<string, float>();
  36. /// <summary> Zero-based index of the page. </summary>
  37. private int m_PageIndex = 0;
  38. /// <summary> The default width. </summary>
  39. private const float defaultWidth = 0.4f;
  40. public override void OnInspectorGUI()
  41. {
  42. NRTrackingImageDatabase database = target as NRTrackingImageDatabase;
  43. if (database == null)
  44. {
  45. return;
  46. }
  47. RunDirtyQualityJobs(database);
  48. m_PageIndex = Mathf.Min(m_PageIndex, database.Count / m_PageSize);
  49. DrawTitle();
  50. DrawContainer();
  51. DrawColumnNames();
  52. int displayedImageCount = 0;
  53. int removeAt = -1;
  54. int pageStartIndex = m_PageIndex * m_PageSize;
  55. int pageEndIndex = Mathf.Min(database.Count, pageStartIndex + m_PageSize);
  56. for (int i = pageStartIndex; i < pageEndIndex; i++, displayedImageCount++)
  57. {
  58. NRTrackingImageDatabaseEntry updatedImage;
  59. bool wasRemoved;
  60. DrawImageField(database[i], out updatedImage, out wasRemoved);
  61. if (wasRemoved)
  62. {
  63. removeAt = i;
  64. }
  65. else if (!database[i].Equals(updatedImage))
  66. {
  67. database[i] = updatedImage;
  68. }
  69. }
  70. if (removeAt > -1)
  71. {
  72. database.RemoveAt(removeAt);
  73. }
  74. DrawImageSpacers(displayedImageCount);
  75. DrawPageField(database.Count);
  76. }
  77. /// <summary> Executes the 'dirty quality jobs' operation. </summary>
  78. /// <param name="database"> The database.</param>
  79. private static void RunDirtyQualityJobs(NRTrackingImageDatabase database)
  80. {
  81. if (database == null)
  82. {
  83. NRDebugger.Info("database is null");
  84. return;
  85. }
  86. if (m_DatabaseForQualityJobs != database)
  87. {
  88. // If another database is already running quality evaluation,
  89. // stop all pending jobs to prioritise the current database.
  90. if (m_DatabaseForQualityJobs != null)
  91. {
  92. m_QualityBackgroundExecutor.RemoveAllPendingJobs();
  93. }
  94. m_DatabaseForQualityJobs = database;
  95. }
  96. UpdateDatabaseQuality(database);
  97. // Set database dirty to refresh inspector UI for each frame that there are still pending jobs.
  98. // Otherwise if there exists one frame with no newly finished jobs, the UI will never get refreshed.
  99. // EditorUtility.SetDirty can only be called from main thread.
  100. if (m_QualityBackgroundExecutor.PendingJobsCount > 0)
  101. {
  102. EditorUtility.SetDirty(database);
  103. return;
  104. }
  105. List<NRTrackingImageDatabaseEntry> dirtyEntries = database.GetDirtyQualityEntries();
  106. if (dirtyEntries.Count == 0)
  107. {
  108. return;
  109. }
  110. string cliBinaryPath;
  111. if (!NRTrackingImageDatabase.FindCliBinaryPath(out cliBinaryPath))
  112. {
  113. return;
  114. }
  115. string outpath = NRTools.GetTrackingImageDataGenPath() + database.GUID + "/";
  116. if (!Directory.Exists(outpath))
  117. {
  118. Directory.CreateDirectory(outpath);
  119. }
  120. var resultjson = database.TrackingImageDataPath + "markers.json";
  121. for (int i = 0; i < dirtyEntries.Count; ++i)
  122. {
  123. NRTrackingImageDatabaseEntry image = dirtyEntries[i];
  124. var imagePath = AssetDatabase.GetAssetPath(image.Texture);
  125. imagePath = Application.dataPath.Substring(0, Application.dataPath.Length - 6) + imagePath;
  126. m_QualityBackgroundExecutor.PushJob(() =>
  127. {
  128. BuildImage(cliBinaryPath, image, imagePath, outpath, resultjson);
  129. });
  130. }
  131. }
  132. /// <summary> Builds data base. </summary>
  133. /// <param name="database"> The database.</param>
  134. public static void BuildDataBase(NRTrackingImageDatabase database)
  135. {
  136. NRDebugger.Info("Start to build database...");
  137. if (database == null)
  138. {
  139. NRDebugger.Info("database is null");
  140. return;
  141. }
  142. List<NRTrackingImageDatabaseEntry> dirtyEntries = database.GetDirtyQualityEntries();
  143. if (database.isCliUpdated)
  144. {
  145. dirtyEntries = database.GetAllEntries();
  146. }
  147. if (dirtyEntries.Count == 0)
  148. {
  149. return;
  150. }
  151. NRDebugger.Info("dirtyEntries count:" + dirtyEntries.Count);
  152. string cliBinaryPath;
  153. if (!NRTrackingImageDatabase.FindCliBinaryPath(out cliBinaryPath))
  154. {
  155. return;
  156. }
  157. string outpath = NRTools.GetTrackingImageDataGenPath() + database.GUID + "/";
  158. if (!Directory.Exists(outpath))
  159. {
  160. Directory.CreateDirectory(outpath);
  161. }
  162. var resultjson = database.TrackingImageDataPath + "markers.json";
  163. for (int i = 0; i < dirtyEntries.Count; ++i)
  164. {
  165. NRTrackingImageDatabaseEntry image = dirtyEntries[i];
  166. var imagePath = AssetDatabase.GetAssetPath(image.Texture);
  167. BuildImage(cliBinaryPath, image, imagePath, outpath, resultjson);
  168. }
  169. if (File.Exists(resultjson))
  170. {
  171. var json_data = File.ReadAllText(resultjson);
  172. var json_obj = JsonMapper.ToObject(json_data);
  173. for (int i = 0; i < dirtyEntries.Count; i++)
  174. {
  175. NRTrackingImageDatabaseEntry image = dirtyEntries[i];
  176. var textureGUID = image.TextureGUID;
  177. //NRDebugger.Info("update quality dict " + image.Name);
  178. var image_info = json_obj[image.Name];
  179. m_UpdatedQualityScores.Remove(textureGUID);
  180. m_UpdatedQualityScores.Add(textureGUID, image_info);
  181. }
  182. UpdateDatabaseQuality(database);
  183. for (int i = 0; i < database.Count; i++)
  184. {
  185. NRTrackingImageDatabaseEntry image = database[i];
  186. NRDebugger.Info(image.ToString());
  187. }
  188. }
  189. }
  190. /// <summary> Builds an image. </summary>
  191. /// <param name="cliBinaryPath"> Full pathname of the CLI binary file.</param>
  192. /// <param name="image"> The image.</param>
  193. /// <param name="imagepath"> The imagepath.</param>
  194. /// <param name="outpath"> The outpath.</param>
  195. /// <param name="resultjson"> The resultjson.</param>
  196. private static void BuildImage(string cliBinaryPath, NRTrackingImageDatabaseEntry image, string imagepath, string outpath, string resultjson)
  197. {
  198. var textureGUID = image.TextureGUID;
  199. if (image.Width < float.Epsilon)
  200. {
  201. image.Width = image.Height = (int)(defaultWidth * 1000);
  202. }
  203. string param = string.Format("-image_path=\"{0}\" -save_dir=\"{1}\" -width=\"{2}\"",
  204. imagepath, outpath, image.Width).Trim();
  205. string result = string.Empty;
  206. string error = string.Empty;
  207. ShellHelper.RunCommand(cliBinaryPath, param, out result, out error);
  208. if (File.Exists(resultjson))
  209. {
  210. var json_data = File.ReadAllText(resultjson);
  211. var json_obj = JsonMapper.ToObject(json_data);
  212. var image_info = json_obj[image.Name];
  213. lock (m_UpdatedQualityScores)
  214. {
  215. if (!m_UpdatedQualityScores.ContainsKey(textureGUID))
  216. {
  217. m_UpdatedQualityScores.Add(textureGUID, image_info);
  218. }
  219. }
  220. }
  221. //if (!string.IsNullOrEmpty(error))
  222. //{
  223. // NRDebugger.Info("BuildImage error :" + error);
  224. //}
  225. }
  226. /// <summary> Updates the database quality described by database. </summary>
  227. /// <param name="database"> The database.</param>
  228. private static void UpdateDatabaseQuality(NRTrackingImageDatabase database)
  229. {
  230. lock (m_UpdatedQualityScores)
  231. {
  232. if (m_UpdatedQualityScores.Count == 0)
  233. {
  234. return;
  235. }
  236. for (int i = 0; i < database.Count; ++i)
  237. {
  238. if (m_UpdatedQualityScores.ContainsKey(database[i].TextureGUID))
  239. {
  240. NRTrackingImageDatabaseEntry updatedImage = database[i];
  241. var image_info = m_UpdatedQualityScores[updatedImage.TextureGUID];
  242. updatedImage.Quality = float.Parse(image_info["train_score"].ToString()).ToString("#");
  243. updatedImage.Width = float.Parse(float.Parse(image_info["physical_width"].ToString()).ToString("#"));
  244. updatedImage.Height = float.Parse(float.Parse(image_info["physical_height"].ToString()).ToString("#"));
  245. database[i] = updatedImage;
  246. //NRDebugger.Info("UpdateDatabaseQuality :" + updatedImage.Name);
  247. }
  248. }
  249. m_UpdatedQualityScores.Clear();
  250. // For refreshing inspector UI as new jobs have been enqueued.
  251. EditorUtility.SetDirty(database);
  252. }
  253. // For refreshing inspector UI for updated quality scores.
  254. EditorUtility.SetDirty(database);
  255. }
  256. private void DrawTitle()
  257. {
  258. const string TITLE_STRING = "Images in Database";
  259. GUIStyle titleStyle = new GUIStyle();
  260. titleStyle.alignment = TextAnchor.MiddleCenter;
  261. titleStyle.stretchWidth = true;
  262. titleStyle.fontSize = 14;
  263. titleStyle.normal.textColor = UnityEngine.Color.white;
  264. titleStyle.padding.bottom = 15;
  265. EditorGUILayout.BeginVertical();
  266. GUILayout.Space(15);
  267. EditorGUILayout.LabelField(TITLE_STRING, titleStyle);
  268. GUILayout.Space(5);
  269. EditorGUILayout.EndVertical();
  270. }
  271. private void DrawContainer()
  272. {
  273. var containerRect = new Rect(m_ContainerStart.x, m_ContainerStart.y, EditorGUIUtility.currentViewWidth - 30,
  274. (m_PageSize * m_ImageSpacerHeight) + m_HeaderHeight);
  275. GUI.Box(containerRect, string.Empty);
  276. }
  277. private void DrawColumnNames()
  278. {
  279. EditorGUILayout.BeginVertical();
  280. GUILayout.Space(5);
  281. EditorGUILayout.BeginHorizontal();
  282. GUILayout.Space(45);
  283. var style = new GUIStyle(GUI.skin.label);
  284. style.alignment = TextAnchor.MiddleLeft;
  285. GUILayoutOption[] options = { GUILayout.Height(m_HeaderHeight - 10), GUILayout.MaxWidth(80f) };
  286. EditorGUILayout.LabelField("Name", style, options);
  287. GUILayout.Space(5);
  288. EditorGUILayout.LabelField("Width(m)", style, options);
  289. GUILayout.Space(5);
  290. EditorGUILayout.LabelField("Quality", style, options);
  291. GUILayout.FlexibleSpace();
  292. GUILayout.Space(60);
  293. EditorGUILayout.EndHorizontal();
  294. EditorGUILayout.EndVertical();
  295. }
  296. /// <summary> Quality for display. </summary>
  297. /// <param name="quality"> The quality.</param>
  298. /// <returns> A string. </returns>
  299. private string QualityForDisplay(string quality)
  300. {
  301. if (string.IsNullOrEmpty(quality))
  302. {
  303. return "Calculating...";
  304. }
  305. if (quality == "?")
  306. {
  307. return "?";
  308. }
  309. return quality + "/100";
  310. }
  311. /// <summary> Draw image field. </summary>
  312. /// <param name="image"> The image.</param>
  313. /// <param name="updatedImage"> [out] The updated image.</param>
  314. /// <param name="wasRemoved"> [out] True if was removed.</param>
  315. private void DrawImageField(NRTrackingImageDatabaseEntry image, out NRTrackingImageDatabaseEntry updatedImage, out bool wasRemoved)
  316. {
  317. updatedImage = new NRTrackingImageDatabaseEntry();
  318. EditorGUILayout.BeginVertical();
  319. GUILayout.Space(5);
  320. EditorGUILayout.BeginHorizontal();
  321. GUILayout.Space(15);
  322. var buttonStyle = new GUIStyle(GUI.skin.button);
  323. buttonStyle.margin = new RectOffset(0, 0, 13, 0);
  324. wasRemoved = GUILayout.Button("X", buttonStyle);
  325. var labelStyle = new GUIStyle(GUI.skin.label);
  326. labelStyle.alignment = TextAnchor.MiddleLeft;
  327. var textFieldStyle = new GUIStyle(GUI.skin.textField);
  328. textFieldStyle.margin = new RectOffset(5, 5, 15, 0);
  329. //updatedImage.Name = EditorGUILayout.TextField(image.Name, textFieldStyle, GUILayout.MaxWidth(80f));
  330. updatedImage.Name = image.Name;
  331. EditorGUILayout.LabelField(image.Name, labelStyle, GUILayout.Height(42), GUILayout.MaxWidth(80f));
  332. GUILayout.Space(5);
  333. float tempwidth;
  334. string key = m_DatabaseForQualityJobs == null ? image.Name : m_DatabaseForQualityJobs.GUID + image.Name;
  335. if (!m_TempWidthDict.TryGetValue(key, out tempwidth))
  336. {
  337. if (image.Width < float.Epsilon)
  338. {
  339. image.Width = defaultWidth * 1000;
  340. }
  341. //tempwidth = defaultWidth;
  342. tempwidth = image.Width / 1000;
  343. m_TempWidthDict.Add(key, tempwidth);
  344. }
  345. tempwidth = EditorGUILayout.FloatField(tempwidth, textFieldStyle, GUILayout.MaxWidth(80f));
  346. m_TempWidthDict[key] = tempwidth;
  347. var rect = GUILayoutUtility.GetLastRect();
  348. var e = Event.current;
  349. bool wasWidthChanged = false;
  350. if (e.type == EventType.MouseDown && !rect.Contains(e.mousePosition))
  351. {
  352. var abs = Mathf.Abs(image.Width / 1000 - tempwidth);
  353. if (abs > 0.01f)
  354. {
  355. updatedImage.Width = tempwidth * 1000;
  356. wasWidthChanged = true;
  357. GUI.FocusControl(null);
  358. }
  359. else
  360. {
  361. updatedImage.Width = image.Width;
  362. }
  363. }
  364. else
  365. {
  366. updatedImage.Width = image.Width;
  367. }
  368. //EditorGUILayout.LabelField((image.Width / 1000).ToString(), labelStyle, GUILayout.Height(42), GUILayout.MaxWidth(80f));
  369. GUILayout.Space(5);
  370. EditorGUILayout.LabelField(QualityForDisplay(image.Quality), labelStyle,
  371. GUILayout.Height(42), GUILayout.MaxWidth(80f));
  372. GUILayout.FlexibleSpace();
  373. updatedImage.Texture = EditorGUILayout.ObjectField(image.Texture, typeof(Texture2D), false,
  374. GUILayout.Height(45), GUILayout.Width(45)) as Texture2D;
  375. if (updatedImage.TextureGUID == image.TextureGUID && !wasWidthChanged)
  376. {
  377. updatedImage.Quality = image.Quality;
  378. }
  379. GUILayout.Space(15);
  380. EditorGUILayout.EndHorizontal();
  381. GUILayout.Space(5);
  382. EditorGUILayout.EndVertical();
  383. }
  384. /// <summary> Draw image spacers. </summary>
  385. /// <param name="displayedImageCount"> Number of displayed images.</param>
  386. private void DrawImageSpacers(int displayedImageCount)
  387. {
  388. EditorGUILayout.BeginVertical();
  389. GUILayout.Space((m_PageSize - displayedImageCount) * m_ImageSpacerHeight);
  390. EditorGUILayout.EndVertical();
  391. }
  392. /// <summary> Draw page field. </summary>
  393. /// <param name="imageCount"> Number of images.</param>
  394. private void DrawPageField(int imageCount)
  395. {
  396. var lastPageIndex = Mathf.Max(imageCount - 1, 0) / m_PageSize;
  397. EditorGUILayout.BeginHorizontal();
  398. GUILayout.Space(15);
  399. var labelStyle = new GUIStyle(GUI.skin.label);
  400. labelStyle.alignment = TextAnchor.MiddleLeft;
  401. EditorGUILayout.LabelField(string.Format("{0} Total Images", imageCount), labelStyle,
  402. GUILayout.Height(42), GUILayout.Width(100));
  403. GUILayout.FlexibleSpace();
  404. EditorGUILayout.LabelField("Page", labelStyle, GUILayout.Height(42), GUILayout.Width(30));
  405. var textStyle = new GUIStyle(GUI.skin.textField);
  406. textStyle.margin = new RectOffset(0, 0, 15, 0);
  407. var pageString = EditorGUILayout.TextField((m_PageIndex + 1).ToString(), textStyle, GUILayout.Width(30));
  408. int pageNumber;
  409. int.TryParse(pageString, out pageNumber);
  410. m_PageIndex = Mathf.Clamp(pageNumber - 1, 0, lastPageIndex);
  411. var buttonStyle = new GUIStyle(GUI.skin.button);
  412. buttonStyle.margin = new RectOffset(10, 10, 13, 0);
  413. GUI.enabled = m_PageIndex > 0;
  414. bool moveLeft = GUILayout.Button("<", buttonStyle);
  415. GUI.enabled = m_PageIndex < lastPageIndex;
  416. bool moveRight = GUILayout.Button(">", buttonStyle);
  417. GUI.enabled = true;
  418. m_PageIndex = moveLeft ? m_PageIndex - 1 : m_PageIndex;
  419. m_PageIndex = moveRight ? m_PageIndex + 1 : m_PageIndex;
  420. GUILayout.Space(15);
  421. EditorGUILayout.EndHorizontal();
  422. }
  423. }
  424. }