/****************************************************************************
* Copyright 2019 Nreal Techonology Limited. All rights reserved.
*
* This file is part of NRSDK.
*
* https://www.nreal.ai/
*
*****************************************************************************/
namespace NRKernal
{
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using LitJson;
/// A tracking image database inspector.
[CustomEditor(typeof(NRTrackingImageDatabase))]
public class TrackingImageDatabaseInspector : Editor
{
/// Height of the image spacer.
private const float m_ImageSpacerHeight = 55f;
/// Size of the page.
private const int m_PageSize = 5;
/// Height of the header.
private const float m_HeaderHeight = 30f;
/// The container start.
private static readonly Vector2 m_ContainerStart = new Vector2(14f, 87f);
/// The quality background executor.
private static BackgroundJobExecutor m_QualityBackgroundExecutor = new BackgroundJobExecutor();
/// The database for quality jobs.
private static NRTrackingImageDatabase m_DatabaseForQualityJobs = null;
/// The updated quality scores.
private static Dictionary m_UpdatedQualityScores = new Dictionary();
/// Dictionary of temporary widths.
private static Dictionary m_TempWidthDict = new Dictionary();
/// Zero-based index of the page.
private int m_PageIndex = 0;
/// The default width.
private const float defaultWidth = 0.4f;
public override void OnInspectorGUI()
{
NRTrackingImageDatabase database = target as NRTrackingImageDatabase;
if (database == null)
{
return;
}
RunDirtyQualityJobs(database);
m_PageIndex = Mathf.Min(m_PageIndex, database.Count / m_PageSize);
DrawTitle();
DrawContainer();
DrawColumnNames();
int displayedImageCount = 0;
int removeAt = -1;
int pageStartIndex = m_PageIndex * m_PageSize;
int pageEndIndex = Mathf.Min(database.Count, pageStartIndex + m_PageSize);
for (int i = pageStartIndex; i < pageEndIndex; i++, displayedImageCount++)
{
NRTrackingImageDatabaseEntry updatedImage;
bool wasRemoved;
DrawImageField(database[i], out updatedImage, out wasRemoved);
if (wasRemoved)
{
removeAt = i;
}
else if (!database[i].Equals(updatedImage))
{
database[i] = updatedImage;
}
}
if (removeAt > -1)
{
database.RemoveAt(removeAt);
}
DrawImageSpacers(displayedImageCount);
DrawPageField(database.Count);
}
/// Executes the 'dirty quality jobs' operation.
/// The database.
private static void RunDirtyQualityJobs(NRTrackingImageDatabase database)
{
if (database == null)
{
NRDebugger.Info("database is null");
return;
}
if (m_DatabaseForQualityJobs != database)
{
// If another database is already running quality evaluation,
// stop all pending jobs to prioritise the current database.
if (m_DatabaseForQualityJobs != null)
{
m_QualityBackgroundExecutor.RemoveAllPendingJobs();
}
m_DatabaseForQualityJobs = database;
}
UpdateDatabaseQuality(database);
// Set database dirty to refresh inspector UI for each frame that there are still pending jobs.
// Otherwise if there exists one frame with no newly finished jobs, the UI will never get refreshed.
// EditorUtility.SetDirty can only be called from main thread.
if (m_QualityBackgroundExecutor.PendingJobsCount > 0)
{
EditorUtility.SetDirty(database);
return;
}
List dirtyEntries = database.GetDirtyQualityEntries();
if (dirtyEntries.Count == 0)
{
return;
}
string cliBinaryPath;
if (!NRTrackingImageDatabase.FindCliBinaryPath(out cliBinaryPath))
{
return;
}
string outpath = NRTools.GetTrackingImageDataGenPath() + database.GUID + "/";
if (!Directory.Exists(outpath))
{
Directory.CreateDirectory(outpath);
}
var resultjson = database.TrackingImageDataPath + "markers.json";
for (int i = 0; i < dirtyEntries.Count; ++i)
{
NRTrackingImageDatabaseEntry image = dirtyEntries[i];
var imagePath = AssetDatabase.GetAssetPath(image.Texture);
imagePath = Application.dataPath.Substring(0, Application.dataPath.Length - 6) + imagePath;
m_QualityBackgroundExecutor.PushJob(() =>
{
BuildImage(cliBinaryPath, image, imagePath, outpath, resultjson);
});
}
}
/// Builds data base.
/// The database.
public static void BuildDataBase(NRTrackingImageDatabase database)
{
NRDebugger.Info("Start to build database...");
if (database == null)
{
NRDebugger.Info("database is null");
return;
}
List dirtyEntries = database.GetDirtyQualityEntries();
if (database.isCliUpdated)
{
dirtyEntries = database.GetAllEntries();
}
if (dirtyEntries.Count == 0)
{
return;
}
NRDebugger.Info("dirtyEntries count:" + dirtyEntries.Count);
string cliBinaryPath;
if (!NRTrackingImageDatabase.FindCliBinaryPath(out cliBinaryPath))
{
return;
}
string outpath = NRTools.GetTrackingImageDataGenPath() + database.GUID + "/";
if (!Directory.Exists(outpath))
{
Directory.CreateDirectory(outpath);
}
var resultjson = database.TrackingImageDataPath + "markers.json";
for (int i = 0; i < dirtyEntries.Count; ++i)
{
NRTrackingImageDatabaseEntry image = dirtyEntries[i];
var imagePath = AssetDatabase.GetAssetPath(image.Texture);
BuildImage(cliBinaryPath, image, imagePath, outpath, resultjson);
}
if (File.Exists(resultjson))
{
var json_data = File.ReadAllText(resultjson);
var json_obj = JsonMapper.ToObject(json_data);
for (int i = 0; i < dirtyEntries.Count; i++)
{
NRTrackingImageDatabaseEntry image = dirtyEntries[i];
var textureGUID = image.TextureGUID;
//NRDebugger.Info("update quality dict " + image.Name);
var image_info = json_obj[image.Name];
m_UpdatedQualityScores.Remove(textureGUID);
m_UpdatedQualityScores.Add(textureGUID, image_info);
}
UpdateDatabaseQuality(database);
for (int i = 0; i < database.Count; i++)
{
NRTrackingImageDatabaseEntry image = database[i];
NRDebugger.Info(image.ToString());
}
}
}
/// Builds an image.
/// Full pathname of the CLI binary file.
/// The image.
/// The imagepath.
/// The outpath.
/// The resultjson.
private static void BuildImage(string cliBinaryPath, NRTrackingImageDatabaseEntry image, string imagepath, string outpath, string resultjson)
{
var textureGUID = image.TextureGUID;
if (image.Width < float.Epsilon)
{
image.Width = image.Height = (int)(defaultWidth * 1000);
}
string param = string.Format("-image_path=\"{0}\" -save_dir=\"{1}\" -width=\"{2}\"",
imagepath, outpath, image.Width).Trim();
string result = string.Empty;
string error = string.Empty;
ShellHelper.RunCommand(cliBinaryPath, param, out result, out error);
if (File.Exists(resultjson))
{
var json_data = File.ReadAllText(resultjson);
var json_obj = JsonMapper.ToObject(json_data);
var image_info = json_obj[image.Name];
lock (m_UpdatedQualityScores)
{
if (!m_UpdatedQualityScores.ContainsKey(textureGUID))
{
m_UpdatedQualityScores.Add(textureGUID, image_info);
}
}
}
//if (!string.IsNullOrEmpty(error))
//{
// NRDebugger.Info("BuildImage error :" + error);
//}
}
/// Updates the database quality described by database.
/// The database.
private static void UpdateDatabaseQuality(NRTrackingImageDatabase database)
{
lock (m_UpdatedQualityScores)
{
if (m_UpdatedQualityScores.Count == 0)
{
return;
}
for (int i = 0; i < database.Count; ++i)
{
if (m_UpdatedQualityScores.ContainsKey(database[i].TextureGUID))
{
NRTrackingImageDatabaseEntry updatedImage = database[i];
var image_info = m_UpdatedQualityScores[updatedImage.TextureGUID];
updatedImage.Quality = float.Parse(image_info["train_score"].ToString()).ToString("#");
updatedImage.Width = float.Parse(float.Parse(image_info["physical_width"].ToString()).ToString("#"));
updatedImage.Height = float.Parse(float.Parse(image_info["physical_height"].ToString()).ToString("#"));
database[i] = updatedImage;
//NRDebugger.Info("UpdateDatabaseQuality :" + updatedImage.Name);
}
}
m_UpdatedQualityScores.Clear();
// For refreshing inspector UI as new jobs have been enqueued.
EditorUtility.SetDirty(database);
}
// For refreshing inspector UI for updated quality scores.
EditorUtility.SetDirty(database);
}
private void DrawTitle()
{
const string TITLE_STRING = "Images in Database";
GUIStyle titleStyle = new GUIStyle();
titleStyle.alignment = TextAnchor.MiddleCenter;
titleStyle.stretchWidth = true;
titleStyle.fontSize = 14;
titleStyle.normal.textColor = UnityEngine.Color.white;
titleStyle.padding.bottom = 15;
EditorGUILayout.BeginVertical();
GUILayout.Space(15);
EditorGUILayout.LabelField(TITLE_STRING, titleStyle);
GUILayout.Space(5);
EditorGUILayout.EndVertical();
}
private void DrawContainer()
{
var containerRect = new Rect(m_ContainerStart.x, m_ContainerStart.y, EditorGUIUtility.currentViewWidth - 30,
(m_PageSize * m_ImageSpacerHeight) + m_HeaderHeight);
GUI.Box(containerRect, string.Empty);
}
private void DrawColumnNames()
{
EditorGUILayout.BeginVertical();
GUILayout.Space(5);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(45);
var style = new GUIStyle(GUI.skin.label);
style.alignment = TextAnchor.MiddleLeft;
GUILayoutOption[] options = { GUILayout.Height(m_HeaderHeight - 10), GUILayout.MaxWidth(80f) };
EditorGUILayout.LabelField("Name", style, options);
GUILayout.Space(5);
EditorGUILayout.LabelField("Width(m)", style, options);
GUILayout.Space(5);
EditorGUILayout.LabelField("Quality", style, options);
GUILayout.FlexibleSpace();
GUILayout.Space(60);
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
/// Quality for display.
/// The quality.
/// A string.
private string QualityForDisplay(string quality)
{
if (string.IsNullOrEmpty(quality))
{
return "Calculating...";
}
if (quality == "?")
{
return "?";
}
return quality + "/100";
}
/// Draw image field.
/// The image.
/// [out] The updated image.
/// [out] True if was removed.
private void DrawImageField(NRTrackingImageDatabaseEntry image, out NRTrackingImageDatabaseEntry updatedImage, out bool wasRemoved)
{
updatedImage = new NRTrackingImageDatabaseEntry();
EditorGUILayout.BeginVertical();
GUILayout.Space(5);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(15);
var buttonStyle = new GUIStyle(GUI.skin.button);
buttonStyle.margin = new RectOffset(0, 0, 13, 0);
wasRemoved = GUILayout.Button("X", buttonStyle);
var labelStyle = new GUIStyle(GUI.skin.label);
labelStyle.alignment = TextAnchor.MiddleLeft;
var textFieldStyle = new GUIStyle(GUI.skin.textField);
textFieldStyle.margin = new RectOffset(5, 5, 15, 0);
//updatedImage.Name = EditorGUILayout.TextField(image.Name, textFieldStyle, GUILayout.MaxWidth(80f));
updatedImage.Name = image.Name;
EditorGUILayout.LabelField(image.Name, labelStyle, GUILayout.Height(42), GUILayout.MaxWidth(80f));
GUILayout.Space(5);
float tempwidth;
string key = m_DatabaseForQualityJobs == null ? image.Name : m_DatabaseForQualityJobs.GUID + image.Name;
if (!m_TempWidthDict.TryGetValue(key, out tempwidth))
{
if (image.Width < float.Epsilon)
{
image.Width = defaultWidth * 1000;
}
//tempwidth = defaultWidth;
tempwidth = image.Width / 1000;
m_TempWidthDict.Add(key, tempwidth);
}
tempwidth = EditorGUILayout.FloatField(tempwidth, textFieldStyle, GUILayout.MaxWidth(80f));
m_TempWidthDict[key] = tempwidth;
var rect = GUILayoutUtility.GetLastRect();
var e = Event.current;
bool wasWidthChanged = false;
if (e.type == EventType.MouseDown && !rect.Contains(e.mousePosition))
{
var abs = Mathf.Abs(image.Width / 1000 - tempwidth);
if (abs > 0.01f)
{
updatedImage.Width = tempwidth * 1000;
wasWidthChanged = true;
GUI.FocusControl(null);
}
else
{
updatedImage.Width = image.Width;
}
}
else
{
updatedImage.Width = image.Width;
}
//EditorGUILayout.LabelField((image.Width / 1000).ToString(), labelStyle, GUILayout.Height(42), GUILayout.MaxWidth(80f));
GUILayout.Space(5);
EditorGUILayout.LabelField(QualityForDisplay(image.Quality), labelStyle,
GUILayout.Height(42), GUILayout.MaxWidth(80f));
GUILayout.FlexibleSpace();
updatedImage.Texture = EditorGUILayout.ObjectField(image.Texture, typeof(Texture2D), false,
GUILayout.Height(45), GUILayout.Width(45)) as Texture2D;
if (updatedImage.TextureGUID == image.TextureGUID && !wasWidthChanged)
{
updatedImage.Quality = image.Quality;
}
GUILayout.Space(15);
EditorGUILayout.EndHorizontal();
GUILayout.Space(5);
EditorGUILayout.EndVertical();
}
/// Draw image spacers.
/// Number of displayed images.
private void DrawImageSpacers(int displayedImageCount)
{
EditorGUILayout.BeginVertical();
GUILayout.Space((m_PageSize - displayedImageCount) * m_ImageSpacerHeight);
EditorGUILayout.EndVertical();
}
/// Draw page field.
/// Number of images.
private void DrawPageField(int imageCount)
{
var lastPageIndex = Mathf.Max(imageCount - 1, 0) / m_PageSize;
EditorGUILayout.BeginHorizontal();
GUILayout.Space(15);
var labelStyle = new GUIStyle(GUI.skin.label);
labelStyle.alignment = TextAnchor.MiddleLeft;
EditorGUILayout.LabelField(string.Format("{0} Total Images", imageCount), labelStyle,
GUILayout.Height(42), GUILayout.Width(100));
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField("Page", labelStyle, GUILayout.Height(42), GUILayout.Width(30));
var textStyle = new GUIStyle(GUI.skin.textField);
textStyle.margin = new RectOffset(0, 0, 15, 0);
var pageString = EditorGUILayout.TextField((m_PageIndex + 1).ToString(), textStyle, GUILayout.Width(30));
int pageNumber;
int.TryParse(pageString, out pageNumber);
m_PageIndex = Mathf.Clamp(pageNumber - 1, 0, lastPageIndex);
var buttonStyle = new GUIStyle(GUI.skin.button);
buttonStyle.margin = new RectOffset(10, 10, 13, 0);
GUI.enabled = m_PageIndex > 0;
bool moveLeft = GUILayout.Button("<", buttonStyle);
GUI.enabled = m_PageIndex < lastPageIndex;
bool moveRight = GUILayout.Button(">", buttonStyle);
GUI.enabled = true;
m_PageIndex = moveLeft ? m_PageIndex - 1 : m_PageIndex;
m_PageIndex = moveRight ? m_PageIndex + 1 : m_PageIndex;
GUILayout.Space(15);
EditorGUILayout.EndHorizontal();
}
}
}