public static Vector3 GetRandomWorldPosistionInBoundaryClosest(GameObject element, GameObject container, GameObject target) { DatasetObjectInfo elementInfo = element.GetComponent <DatasetObjectInfo>(); DatasetObjectInfo containterInfo = container.GetComponent <DatasetObjectInfo>(); DatasetObjectInfo targetInfo = target.GetComponent <DatasetObjectInfo>(); var elementSize = elementInfo.GetBoundarySize(); var containerMin = containterInfo.GetMinBoundaryPoint(); var containerMax = containterInfo.GetMaxBoundaryPoint(); Vector3 transformedCenterOffset = element.transform.TransformDirection(elementInfo.center); Vector3 currentElementPosition = element.transform.position; float x = Random.Range(containerMin.x + elementSize.x / 2f - transformedCenterOffset.x, containerMax.x - elementSize.x / 2f - transformedCenterOffset.x); float y = Random.Range(containerMin.y + elementSize.y / 2f - transformedCenterOffset.y, containerMax.y - elementSize.y / 2f - transformedCenterOffset.y); float z = Random.Range(containerMin.z + elementSize.z / 2f - transformedCenterOffset.z, containerMax.z - elementSize.z / 2f - transformedCenterOffset.z); if (-(containerMin.y + elementSize.y / 2f - transformedCenterOffset.y) + (containerMax.y - elementSize.y / 2f - transformedCenterOffset.y) < 0) { y = containerMin.y + elementSize.y / 2f - transformedCenterOffset.y; } Vector3 endPoint = target.transform.TransformPoint(targetInfo.testPoints[Random.Range(0, targetInfo.testPoints.Count)]); float t = 0f; float p = 0f, k = 1f, s; for (int i = 0; i < 10; i++)//Bin search { s = (p + k) / 2f; element.transform.position = Vector3.Lerp(new Vector3(x, y, z), endPoint, s); if (!BoundaryIsColliding(element, target) && IsInside(element, container)) { p = s; t = s; } else { k = s; t = p; } } float offset = Settings.config.datasetOptions.closestMaxDistanceOffset; float dt = 1f / (new Vector3(x, y, z) - endPoint).magnitude * offset * Random.value; element.transform.position = Vector3.Lerp(new Vector3(x, y, z), endPoint, t - dt); if (!BoundaryIsColliding(element, target) && IsInside(element, container)) { element.transform.position = currentElementPosition; return(Vector3.Lerp(new Vector3(x, y, z), endPoint, t - dt)); } else { element.transform.position = currentElementPosition; return(Vector3.Lerp(new Vector3(x, y, z), endPoint, t)); } }
public static bool AreColliding(GameObject elementA, GameObject elementB) {//TODO CHECK DatasetObjectInfo infoA = elementA.GetComponent <DatasetObjectInfo>(); DatasetObjectInfo infoB = elementB.GetComponent <DatasetObjectInfo>(); Vector3 aMin = infoA.GetMinBoundaryPoint(); Vector3 aMax = infoA.GetMaxBoundaryPoint(); Vector3 bMin = infoB.GetMinBoundaryPoint(); Vector3 bMax = infoB.GetMaxBoundaryPoint(); return((aMin.x <= bMax.x && aMax.x >= bMin.x) && (aMin.y <= bMax.y && aMax.y >= bMin.y) && (aMin.z <= bMax.z && aMax.z >= bMin.z)); }
public static Vector3 GetRandomWorldPosistionInBoundary(GameObject element, GameObject container) { DatasetObjectInfo elementInfo = element.GetComponent <DatasetObjectInfo>(); DatasetObjectInfo containterInfo = container.GetComponent <DatasetObjectInfo>(); var elementSize = elementInfo.GetBoundarySize(); var containerMin = containterInfo.GetMinBoundaryPoint(); var containerMax = containterInfo.GetMaxBoundaryPoint(); Vector3 transformedCenterOffset = element.transform.TransformDirection(elementInfo.center); float x = Random.Range(containerMin.x + elementSize.x / 2f - transformedCenterOffset.x, containerMax.x - elementSize.x / 2f - transformedCenterOffset.x); float y = Random.Range(containerMin.y + elementSize.y / 2f - transformedCenterOffset.y, containerMax.y - elementSize.y / 2f - transformedCenterOffset.y); float z = Random.Range(containerMin.z + elementSize.z / 2f - transformedCenterOffset.z, containerMax.z - elementSize.z / 2f - transformedCenterOffset.z); if (-(containerMin.y + elementSize.y / 2f - transformedCenterOffset.y) + (containerMax.y - elementSize.y / 2f - transformedCenterOffset.y) < 0) { y = containerMin.y + elementSize.y / 2f - transformedCenterOffset.y; } return(new Vector3(x, y, z)); }
public static bool BoundaryIsColliding(GameObject boundaryObject, GameObject pointObject) { DatasetObjectInfo boundaryInfo = boundaryObject.GetComponent <DatasetObjectInfo>(); DatasetObjectInfo pointInfo = pointObject.GetComponent <DatasetObjectInfo>(); Vector3 aMin = boundaryInfo.GetMinBoundaryPoint(); Vector3 aMax = boundaryInfo.GetMaxBoundaryPoint(); foreach (var pointRaw in pointInfo.testPoints) { var point = pointObject.transform.TransformPoint(pointRaw); if ((aMin.x <= point.x && aMax.x >= point.x) && (aMin.y <= point.y && aMax.y >= point.y) && (aMin.z <= point.z && aMax.z >= point.z)) { return(true); } } return(false); }
public static Vector3 GetRandomWorldPosistionInBoundary(GameObject element, GameObject container, GameObject target) { DatasetObjectInfo elementInfo = element.GetComponent <DatasetObjectInfo>(); DatasetObjectInfo containterInfo = container.GetComponent <DatasetObjectInfo>(); var elementSize = elementInfo.GetBoundarySize(); var containerMin = containterInfo.GetMinBoundaryPoint(); var containerMax = containterInfo.GetMaxBoundaryPoint(); Vector3 transformedCenterOffset = element.transform.TransformDirection(elementInfo.center); List <Vector3> positions = new List <Vector3>(10); List <float> distancesToElement = new List <float>(positions.Count); for (int i = 0; i < positions.Capacity; i++) { float x = Random.Range(containerMin.x + elementSize.x / 2f - transformedCenterOffset.x, containerMax.x - elementSize.x / 2f - transformedCenterOffset.x); float y = Random.Range(containerMin.y + elementSize.y / 2f - transformedCenterOffset.y, containerMax.y - elementSize.y / 2f - transformedCenterOffset.y); float z = Random.Range(containerMin.z + elementSize.z / 2f - transformedCenterOffset.z, containerMax.z - elementSize.z / 2f - transformedCenterOffset.z); if (-(containerMin.y + elementSize.y / 2f - transformedCenterOffset.y) + (containerMax.y - elementSize.y / 2f - transformedCenterOffset.y) < 0) { y = containerMin.y + elementSize.y / 2f - transformedCenterOffset.y; } positions.Add(new Vector3(x, y, z)); distancesToElement.Add((target.transform.position - positions[positions.Count - 1]).sqrMagnitude); } int index = -1; float smallestDistance = Mathf.Infinity; for (int i = 0; i < distancesToElement.Count; i++) { if (distancesToElement[i] < smallestDistance) { index = i; smallestDistance = distancesToElement[i]; } } return(positions[index]); }
public void PlaceRobotInStartZone() { var startZones = GameObject.FindGameObjectsWithTag("StartZone"); if (startZones.Length > 0) { System.Random r = new System.Random(); int i = r.Next(0, startZones.Length); var startZone = startZones[i]; DatasetObjectInfo elementInfo = robot.GetComponent <DatasetObjectInfo>(); var elementSize = elementInfo.GetBoundarySize(); var bounds = startZone.GetComponent <Collider>().bounds; var containerMin = bounds.min; var containerMax = bounds.max; Vector3 transformedCenterOffset = robot.transform.TransformDirection(elementInfo.center); float x = Random.Range(containerMin.x + elementSize.x / 2f - transformedCenterOffset.x, containerMax.x - elementSize.x / 2f - transformedCenterOffset.x); float y = Random.Range(containerMin.y + elementSize.y / 2f - transformedCenterOffset.y, containerMax.y - elementSize.y / 2f - transformedCenterOffset.y); float z = Random.Range(containerMin.z + elementSize.z / 2f - transformedCenterOffset.z, containerMax.z - elementSize.z / 2f - transformedCenterOffset.z); if (-(containerMin.y + elementSize.y / 2f - transformedCenterOffset.y) + (containerMax.y - elementSize.y / 2f - transformedCenterOffset.y) < 0) { y = containerMin.y + elementSize.y / 2f - transformedCenterOffset.y; } robot.transform.position = new Vector3(x, 1, z); float direction = startZone.GetComponent <StartZoneController>().angle; float fov = startZone.GetComponent <StartZoneController>().fov; robot.transform.rotation = Quaternion.Euler(0, Random.Range(direction - fov / 2, direction + fov / 2), 0); } foreach (var cp in GameObject.FindGameObjectsWithTag("Checkpoint")) { cp.GetComponent <CheckpointController>().reached = false; } robotController.rb.velocity = Vector3.zero; robotController.rb.angularVelocity = Vector3.zero; }
public override void OnInspectorGUI() { DrawDefaultInspector(); DatasetObjectInfo script = (DatasetObjectInfo)target; if (GUILayout.Button("Clear test points") && EditorUtility.DisplayDialog("Confirmation", "Do you want to clear test points?", "Clear", "Cancel")) { script.testPoints = null; } if (GUILayout.Button("Regenerate uniform points") && EditorUtility.DisplayDialog("Confirmation", "Do you want to regenerate?", "Regenerate", "Cancel")) { script.GenerateTestPointsUniform(script.testPointsNum); EditorUtility.DisplayDialog("Generation", "Done regenerating test points", "0k"); } if (GUILayout.Button("Regenerate non-uniform points") && EditorUtility.DisplayDialog("Confirmation", "Do you want to regenerate?", "Regenerate", "Cancel")) { script.GenerateTestPointsNonUniform(script.testPointsNum); EditorUtility.DisplayDialog("Generation", "Done regenerating test points", "0k"); } if (script.testPoints == null || script.testPoints.Count == 0) { EditorGUILayout.HelpBox("Test points should be generated", MessageType.Info); } int triangles = 0; foreach (MeshFilter meshFilter in script.gameObject.GetComponentsInChildren <MeshFilter>()) { triangles += meshFilter.sharedMesh.triangles.Length; } if (triangles > 9000) { EditorGUILayout.HelpBox("Triangles count over 9000: " + triangles + "\nPregeneration is essential!", MessageType.Warning); } }
public static bool IsInside(GameObject element, GameObject container) { DatasetObjectInfo elementInfo = element.GetComponent <DatasetObjectInfo>(); DatasetObjectInfo containterInfo = container.GetComponent <DatasetObjectInfo>(); var elementSize = elementInfo.GetBoundarySize(); var containerMin = containterInfo.GetMinBoundaryPoint(); var containerMax = containterInfo.GetMaxBoundaryPoint(); Vector3 transformedCenterOffset = element.transform.TransformDirection(elementInfo.center); Vector3 currentElementPosition = element.transform.position; float x = element.transform.position.x; float y = element.transform.position.y; float z = element.transform.position.z; float minX = containerMin.x + elementSize.x / 2f - transformedCenterOffset.x; float maxX = containerMax.x - elementSize.x / 2f - transformedCenterOffset.x; float minY = containerMin.y + elementSize.y / 2f - transformedCenterOffset.y; float maxY = containerMax.y - elementSize.y / 2f - transformedCenterOffset.y; float minZ = containerMin.z + elementSize.z / 2f - transformedCenterOffset.z; float maxZ = containerMax.z - elementSize.z / 2f - transformedCenterOffset.z; return(x >= minX && x <= maxX && y >= minY && y <= maxY && z >= minZ && z <= maxZ); }
void Update() { if (debugWaitedFrames++ == debugNumFramesToWait) { debugWaitedFrames = 0; } else { return; } if (Settings.config.datasetOptions.debugOptions.drawDetectionRect) { cameraObject.GetComponent <DetectionRectDraw>().doDrawRect = true; } switch (generationState) { case GenerationState.SingleObjectsSetup: { ClearSceneFromObjects(); if (Settings.config.datasetOptions.classObstacleFrameNum == 0 && Settings.config.datasetOptions.classDetectedFrameNum == 0) { generationState = GenerationState.MultipleObjectsSetup; return; } Instantiate(selectedObjects[currentObjectIndex]); Debug.Log("Clearing scene for S.O. Creating object index: " + currentObjectIndex); generationState = GenerationState.SingleObjects; var info = selectedObjects[currentObjectIndex].GetComponent <DatasetObjectInfo>(); if (IncludeInDataset(selectedObjects[currentObjectIndex])) { counter = Settings.config.datasetOptions.classDetectedFrameNum / objectsNumInClass[info.className]; } else { counter = Settings.config.datasetOptions.classObstacleFrameNum * Settings.config.datasetOptions.classNames.Count / objectsNumInClass["OBSTACLE"]; } for (int i = 0; i < selectedObjects.Count; i++) { string className = selectedObjects[i].GetComponent <DatasetObjectInfo>().className; if (IncludeInDataset(selectedObjects[i])) { Directory.CreateDirectory(Settings.config.datasetOptions.datasetDirPath + @"\" + className); } else { Directory.CreateDirectory(Settings.config.datasetOptions.datasetDirPath + @"\" + "obstacle"); } } break; } case GenerationState.SingleObjects: { GameObject singleObject = GameObject.FindGameObjectWithTag("ToDetect"); string className = singleObject.GetComponent <DatasetObjectInfo>().className; if (!IncludeInDataset(singleObject)) { className = "obstacle"; } singleObject.transform.rotation = Quaternion.Euler(Random.Range(0f, 360f), Random.Range(0f, 360f), Random.Range(0f, 0f)); singleObject.transform.position = DatasetObjectInfo.GetRandomWorldPosistionInBoundary(singleObject, selectedWaterContainer); if (Random.value < Settings.config.datasetOptions.percentClosest) { cameraObject.transform.position = DatasetObjectInfo.GetRandomWorldPosistionInBoundaryClosest(cameraObject, selectedWaterContainer, singleObject); } else { cameraObject.transform.position = DatasetObjectInfo.GetRandomWorldPosistionInBoundary(cameraObject, selectedWaterContainer); } LookAtTargetRandomly(singleObject, cameraObject, Settings.config.datasetOptions.cameraLookAtObjectPitchOffset, Settings.config.datasetOptions.cameraLookAtObjectYawOffset, Settings.config.datasetOptions.cameraLookAtObjectRollOffset); var detector = new Detector(); detector.Detect(cameraObject.GetComponent <Camera>()); if (detector.detection[0].visible && detector.detection[0].fill >= Settings.config.datasetOptions.minObjectFill && (singleObject.transform.position - cameraObject.transform.position).magnitude >= Settings.config.datasetOptions.minCameraDistanceToObject && (singleObject.transform.position - cameraObject.transform.position).magnitude <= Settings.config.datasetOptions.maxCameraDistanceToObject && !DatasetObjectInfo.BoundaryIsColliding(cameraObject, singleObject)) { string datasetRootPath = Settings.config.datasetOptions.datasetDirPath; string txtFileName = datasetRootPath + @"/" + className + @"/" + createdFramesPerClass[className].ToString() + ".txt"; string pngFileName = datasetRootPath + @"/" + className + @"/" + createdFramesPerClass[className].ToString() + ".png"; string trainFileName = datasetRootPath + @"/train.txt"; if (IncludeInDataset(detector.detection[0].gameObject)) { File.WriteAllText(txtFileName, detector.detection[0].GetTextInfo()); } else { File.WriteAllText(txtFileName, ""); } File.AppendAllText(trainFileName, pngFileName + System.Environment.NewLine); if (!Settings.config.datasetOptions.debugOptions.disableScreenshot) { ScreenCapture.CaptureScreenshot(System.IO.Directory.GetCurrentDirectory() + "/" + pngFileName); } createdFramesPerClass[className]++; // Debug.Log("detected " + createdFramesPerClass[className] + "/" + Settings.config.datasetOptions.objectVisibleFrameNum * selectedObjects.Count); Debug.Log("detected " + createdFramesPerClass[className] + "/" + 7 * selectedObjects.Count); createdTypeFrames++; counter--; if (Settings.config.datasetOptions.debugOptions.logDetection) { File.AppendAllText(datasetRootPath + @"/log.dat", detector.detection[0].distance + " " + detector.detection[0].fill + System.Environment.NewLine); } } StartCoroutine(RandomizeGraphics(cameraObject.GetComponent <Camera>())); if (counter <= 0) { Debug.Log("Finished object index:" + currentObjectIndex + " " + (currentObjectIndex + 1) + "/" + selectedObjects.Count); generationState = GenerationState.SingleObjectsSetup; if (++currentObjectIndex >= selectedObjects.Count) { Debug.Log("Finished all single objects"); generationState = GenerationState.MultipleObjectsSetup; } } } break; case GenerationState.MultipleObjectsSetup: { ClearSceneFromObjects(); // if (Settings.config.datasetOptions.objectMultipleFrameNum == 0) /* if (1 == 0) * { * generationState = GenerationState.NoObjectsSetup; * return; TODO * }*/ Debug.Log("Cleared scene for M.O."); randomObjectsInScene = new List <GameObject>(); int TODOrandomObjectsNum = 20; for (int i = 0; i < TODOrandomObjectsNum; i++) { var randomObject = Instantiate(selectedObjects[Random.Range(0, selectedObjects.Count)]); randomObject.transform.rotation = Quaternion.Euler(Random.Range(0f, 360f), Random.Range(0f, 360f), Random.Range(0f, 0f)); randomObject.transform.position = DatasetObjectInfo.GetRandomWorldPosistionInBoundary(randomObject, selectedWaterContainer); randomObjectsInScene.Add(randomObject); } counter = Settings.config.datasetOptions.classMultipleFrameNum * Settings.config.datasetOptions.classNames.Count; if (!Directory.Exists(Settings.config.datasetOptions.datasetDirPath + @"/multiple")) { Directory.CreateDirectory(Settings.config.datasetOptions.datasetDirPath + @"/multiple"); } generationState = GenerationState.MultipleObjects; } break; case GenerationState.MultipleObjects: { if (counter <= 0) { generationState = GenerationState.NoObjectsSetup; return; } // int detectedVisibleObjectsNum = 0; foreach (var obj in randomObjectsInScene) { obj.SetActive(Random.value > 0.5f); if (!obj.activeSelf) { continue; } obj.transform.rotation = Quaternion.Euler(Random.Range(0f, 360f), Random.Range(0f, 360f), Random.Range(0f, 0f)); obj.transform.position = DatasetObjectInfo.GetRandomWorldPosistionInBoundary(obj, selectedWaterContainer); } cameraObject.transform.position = DatasetObjectInfo.GetRandomWorldPosistionInBoundary(cameraObject, selectedWaterContainer); LookRandomly(cameraObject, 20f, 180f, 30f); StartCoroutine(RandomizeGraphics(cameraObject.GetComponent <Camera>())); var detector = new Detector(); string detectedObjectsText = ""; foreach (var info in detector.Detect(cameraObject.GetComponent <Camera>())) { if (!info.visible || info.fill < Settings.config.datasetOptions.minObjectFill || DatasetObjectInfo.BoundaryIsColliding(cameraObject, info.gameObject)) { continue; //TODO sprawdz nową kolizję } if (IncludeInDataset(info.gameObject)) { detectedObjectsText += info.GetTextInfo() + System.Environment.NewLine; } detectedVisibleObjectsNum++; } if (detectedVisibleObjectsNum >= Settings.config.datasetOptions.minVisibleMultipleObjectsNum) { string datasetRootPath = Settings.config.datasetOptions.datasetDirPath; string txtFileName = datasetRootPath + @"/" + "multiple" + @"/" + createdFramesPerClass["multiple"].ToString() + ".txt"; string pngFileName = datasetRootPath + @"/" + "multiple" + @"/" + createdFramesPerClass["multiple"].ToString() + ".png"; string trainFileName = datasetRootPath + @"/train.txt"; File.WriteAllText(txtFileName, detectedObjectsText); File.AppendAllText(trainFileName, pngFileName + System.Environment.NewLine); ScreenCapture.CaptureScreenshot(System.IO.Directory.GetCurrentDirectory() + "/" + pngFileName); createdFramesPerClass["multiple"]++; counter--; } break; } case GenerationState.NoObjectsSetup: { ClearSceneFromObjects(); if (Settings.config.datasetOptions.classBlankFrameNum == 0) { generationState = GenerationState.Finished; return; } counter = Settings.config.datasetOptions.classBlankFrameNum * Settings.config.datasetOptions.classNames.Count; generationState = GenerationState.NoObjects; if (!Directory.Exists(Settings.config.datasetOptions.datasetDirPath + @"/blank")) { Directory.CreateDirectory(Settings.config.datasetOptions.datasetDirPath + @"/blank"); } break; } case GenerationState.NoObjects: { StartCoroutine(RandomizeGraphics(cameraObject.GetComponent <Camera>())); cameraObject.transform.position = DatasetObjectInfo.GetRandomWorldPosistionInBoundary(cameraObject, selectedWaterContainer); LookRandomly(cameraObject, Settings.config.datasetOptions.cameraLookRandomlyPitchOffset, Random.Range(0f, 360f), Settings.config.datasetOptions.cameraLookRandomlyRollOffset); string datasetRootPath = Settings.config.datasetOptions.datasetDirPath; string txtFileName = datasetRootPath + @"/" + "blank" + @"/" + createdFramesPerClass["blank"].ToString() + ".txt"; string pngFileName = datasetRootPath + @"/" + "blank" + @"/" + createdFramesPerClass["blank"].ToString() + ".png"; string trainFileName = datasetRootPath + @"/train.txt"; File.WriteAllText(txtFileName, ""); File.AppendAllText(trainFileName, pngFileName + System.Environment.NewLine); ScreenCapture.CaptureScreenshot(System.IO.Directory.GetCurrentDirectory() + "/" + pngFileName); createdFramesPerClass["blank"]++; } // if (createdFramesPerClass["blank"] >= Settings.config.datasetOptions.objectBlankFrameNum) if (--counter <= 0) { Debug.Log("Finished all no objects frames"); generationState = GenerationState.Finished; } break; case GenerationState.Finished: { ClearSceneFromObjects(); Destroy(selectedWaterContainer); #if UNITY_EDITOR UnityEditor.EditorApplication.isPlaying = false; #else Application.Quit(); #endif break; } default: break; } }
void Start() { QualitySettings.vSyncCount = 0; // VSync must be disabled Application.targetFrameRate = 35; mainThreadUpdateWorkers = new ConcurrentQueue <MainThreadUpdateWorker>(); if (Settings.config == null || Settings.config.mode == null) { SceneManager.LoadScene("Start"); return;//TODO take config } string selectedWaterContainer = Settings.config.simulationOptions.selectedWaterContainer; if (TryGetObjectByTypeName(selectedWaterContainer, out GameObject waterContainerPrefab)) { waterContainer = Instantiate(waterContainerPrefab, Vector3.zero, Quaternion.identity); } robot = Instantiate(robotPrefab, new Vector3(0, 2, 0), Quaternion.identity); robotController = robot.GetComponent <RobotController>(); PlaceRobotInStartZone(); List <string> selectedRandomObjectsNames = Settings.config.simulationOptions.selectedRandomObjects; foreach (string randomObjectName in selectedRandomObjectsNames) { if (TryGetObjectByTypeName(randomObjectName, out GameObject randomObjectPrefab)) { var randomObject = Instantiate(randomObjectPrefab); randomObject.transform.rotation = Quaternion.Euler(UnityEngine.Random.Range(0f, 360f), UnityEngine.Random.Range(0f, 360f), UnityEngine.Random.Range(0f, 0f)); randomObject.transform.position = DatasetObjectInfo.GetRandomWorldPosistionInBoundary(randomObject, waterContainer); } else { Debug.LogError(randomObjectName); } } Time.fixedDeltaTime = Settings.config.simulationOptions.fixedDeltaTime; /* * camera = Camera.main; * postDebug = cameraObject.GetComponent<PostProcessDebug>(); * postLayer = cameraObject.GetComponent<PostProcessLayer>(); * postVolume = cameraObject.GetComponent<PostProcessVolume>(); * SetGraphics();*/ bool TryGetObjectByTypeName(string typeName, out GameObject obj) { obj = null; for (int i = 0; i < knownObjects.Count; i++) { if (knownObjects[i].GetComponent <DatasetObjectInfo>().typeName.ToLower() == typeName.ToLower()) { obj = knownObjects[i]; return(true); } } return(false); } StartCoroutine(StartCapture()); wapiThread = new Thread(WAPIRecv) { IsBackground = true }; wapiThread.Start(); JsonSerializer.ToJsonString(GetDetection()); //No idea why first first call takes 1 second, leave it for performance }