/* * Copyright (c) 2025 LG Electronics Inc. * SPDX-License-Identifier: LicenseRef-LGE-Proprietary */ using Aurora; using Aurora.Api; using Aurora.Api.DataType; using CogCommon; using VisionCommon; using Hermes; using Cognex.VisionPro; using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Cognex.VisionPro.FGGigE; using Cognex.VisionPro.Exceptions; using VisionCommonUI; using System.Windows; using VisionCommonUI.Resources; using System.Collections.Generic; namespace CogCamera { /// /// Cognex 카메라 디바이스 /// public class CogCamera : CameraBase { private ICogFrameGrabber _CogFrameGrabber; private ICogAcqFifo _CogAcqFifo; private ICogImage _OriginalImage = null; // 원본 이미지 버퍼 private ICogImage _FilteredImage = null; // Flip + Rotation 이미지 버퍼 private ICogImage _FilteredLiveImage = null; // Flip + Rotation 라이브 이미지 버퍼 private ICogImage _CroppedImage = null; // Flip + Rotation + Crop 이미지 버퍼 private ICogImage _CroppedLiveImage = null; // Flip + Rotation + Crop 라이브 이미지 버퍼 private ICogCameraDisplay _CogCameraDisplay; // Fov별로 가지는 CogCameraDisplay를 호출시마다 받아서 사용 private CogImgFlipAndRotationTool _CogImgFlipAndRotationTool; /// /// 생성자 /// /// 디바이스 ID public CogCamera(string deviceId) : base(deviceId) { var subject = string.Format("CogCamera[{0}]", deviceId); LOG = new Logger(subject); TactTimeChecker = new TactTimeChecker(subject); } protected override int DoActivate(IDeviceConfig config) { if (IsActivated) { LOG.FuncI("Already Activated!"); return E1.OK; } CamConfig = config as CamConfig; eCamId camId = CamRecipe.GetCamIdFromDevId(DeviceId); _CogImgFlipAndRotationTool = new CogImgFlipAndRotationTool(camId, CamConfig); var res = Init(); if (res == E1.OK) { CheckImageBufferRotationChanged(); } return res; } #region Init private int Init() { if (!CogEnvironment.Instance.HasValidLicense()) { MessageBoxUtil.ShowMessageBoxError(StrTokens.MSG_E_NoCogLicense); return E1.GENERIC; } if (E1.OK != InitDigitizer()) { MessageBoxUtil.ShowMessageBoxError(StrTokens.MSG_E_DigitizerInitError); return E1.GENERIC; } if (E1.OK != SetImageSize()) { MessageBoxUtil.ShowMessageBoxError(StrTokens.MSG_E_ImageSizeSettingError); return E1.GENERIC; } if (E1.OK != AllocImages()) { MessageBoxUtil.ShowMessageBoxError(StrTokens.MSG_E_ImageNotAllocated); return E1.GENERIC; } if (E1.OK != SetTrigger()) { MessageBoxUtil.ShowMessageBoxError(StrTokens.MSG_E_TriggerSettingError); return E1.GENERIC; } if (E1.OK != SetExposureTimeFromConfig()) { MessageBoxUtil.ShowMessageBoxError(StrTokens.MSG_E_ExposureTimeSettingError); return E1.GENERIC; } return E1.OK; } private int InitDigitizer() { try { string ipAddress = CamConfig.CamConnConfig.IpAddress; if (ipAddress != string.Empty) { // CamConfig에 저장된 IP주소와 매칭되는 카메라를 찾아서 연결 _CogFrameGrabber = CogGrabberManager.Instance.GetCogFrameGrabberFromIpAddress(ipAddress); } else { // CamConfig에 IP주소를 저장하지 않고 동적으로 카메라 연결 _CogFrameGrabber = CogGrabberManager.Instance.GetCogFrameGrabberFromDeviceId(DeviceId); } if (_CogFrameGrabber == null) { LOG.FuncE("GigE camera does not exists."); return E2.NOT_FOUND; } LOG.FuncI($"DeviceId : {DeviceId}"); LOG.FuncI($"_CogFrameGrabber.Name : {_CogFrameGrabber.Name}"); LOG.FuncI($"_CogFrameGrabber.Index : {_CogFrameGrabber.Index}"); LOG.FuncI($"_CogFrameGrabber.AvailablePortsNum : {_CogFrameGrabber.GetNumCameraPorts("Generic GigEVision (Mono)")}"); _CogAcqFifo = _CogFrameGrabber.CreateAcqFifo("Generic GigEVision (Mono)", CogAcqFifoPixelFormatConstants.Format8Grey, 0, true); _CogAcqFifo.Complete += new CogCompleteEventHandler(HookHandler); _CogAcqFifo.OwnedExposureParams.Exposure = (CamConfig.ExposureTime_Ms > 0) ? CamConfig.ExposureTime_Ms : 50; // Default return E1.OK; } catch (Exception e) { LOG.FuncE(e.Message); } return E1.GENERIC; } private int SetImageSize() { int startX = 0; int startY = 0; int width = 0; int height = 0; int band = 1; int bitDepth = 8; try { _CogAcqFifo.OwnedROIParams.GetROIXYWidthHeight(out startX, out startY, out width, out height); CogImagePixelFormatConstants pixelConstants = _CogAcqFifo.AcquiredPixelFormat(); CamConfig.ImageSize.Width = (int)width; CamConfig.ImageSize.Height = (int)height; if (pixelConstants == CogImagePixelFormatConstants.Grey8) { CamConfig.ImageSize.Channel = 1; CamConfig.ImageSize.BitDepth = 8; } else { CamConfig.ImageSize.Channel = 3; CamConfig.ImageSize.BitDepth = 24; } } catch (Exception e) { LOG.E(e.Message); return E1.GENERIC; } return E1.OK; } private int SetExposureTimeFromConfig() { try { SetExposure(CamConfig.ExposureTime_Ms); } catch (Exception e) { LOG.E(e.Message); return E1.GENERIC; } return E1.OK; } protected int AllocImages() { try { var results = new List(); results.Add(AllocOriginalImage(ref _OriginalImage)); results.Add(AllocFilteredImage(ref _FilteredImage)); results.Add(AllocFilteredImage(ref _FilteredLiveImage)); results.Add(AllocCroppedImage(ref _CroppedImage)); results.Add(AllocCroppedImage(ref _CroppedLiveImage)); foreach (int result in results) { if (result != E1.OK) { return E1.GENERIC; } } } catch (Exception e) { LOG.E(e.Message); return E1.GENERIC; } return E1.OK; } private int AllocOriginalImage(ref ICogImage image) { return CogEnvironment.Instance.AllocOriginalImage(CamConfig, ref image); } private int AllocFilteredImage(ref ICogImage image) { if (CamConfig.ImageSize.Channel == 3) { return CogEnvironment.Instance.AllocColorImage(CamConfig, false, ref image); } else { return CogEnvironment.Instance.AllocGrayImage(CamConfig, false, ref image); } } private int AllocCroppedImage(ref ICogImage image) { if (CamConfig.ImageSize.Channel == 3) { return CogEnvironment.Instance.AllocColorImage(CamConfig, true, ref image); } else { return CogEnvironment.Instance.AllocGrayImage(CamConfig, true, ref image); } } /// /// 이미지 버퍼 복제 /// /// 복제 버퍼 public override object CloneImageBuffer() { return CogEnvironment.CloneImage(_CroppedImage); } private int SetTrigger() { try { _CogAcqFifo.OwnedTriggerParams.TriggerEnabled = true; if (CamConfig.TriggerMode.IsSoftware) { return SetSwTrigger(); } else { return SetHwTrigger(); } } catch (Exception e) { LOG.FuncE(e.Message); return E1.GENERIC; } } private int SetHwTrigger() { try { _CogAcqFifo.OwnedTriggerParams.TriggerModel = CogAcqTriggerModelConstants.Auto; #region AcquisitionControl Set var cogGigEAccess = _CogAcqFifo.FrameGrabber.OwnedGigEAccess; var features = cogGigEAccess.GetAvailableFeatures("AcquisitionControl"); foreach (var feature in features) { var cur_key = feature.ToString(); if (cur_key.Equals("TriggerMode")) { if (cogGigEAccess.IsWriteable("TriggerMode")) { cogGigEAccess.SetFeature("TriggerMode", "On"); LOG.FuncI($"TriggerMode On"); } } if (cur_key.Equals("TriggerSource")) { if (cogGigEAccess.IsWriteable("TriggerSource")) { cogGigEAccess.SetFeature("TriggerSource", CamConfig.TrigSrc.ToString()); LOG.FuncI($"TriggerSource {CamConfig.TrigSrc.ToString()}"); } } } #endregion #region DigitalIOControl Set For Strobe features = cogGigEAccess.GetAvailableFeatures("DigitalIOControl"); foreach (var feature in features) { var cur_key = feature.ToString(); // 현장 상황에 맞도록 변경 필요 //if (cur_key.Equals("LineSelector")) { // if (cogGigEAccess.IsWriteable("LineSelector")) { // cogGigEAccess.SetFeature("LineSelector", "Line2"); // LOG.FuncI($"LineSelector Line2"); // } //} //if (cur_key.Equals("LineSource")) { // if (cogGigEAccess.IsWriteable("LineSource")) { // cogGigEAccess.SetFeature("LineSource", "ExposureActive"); // LOG.FuncI($"LineSource ExposureActive"); // } //} } #endregion } catch (Exception e) { LOG.FuncE(e.Message); return E1.GENERIC; } return E1.OK; } private int SetSwTrigger() { try { _CogAcqFifo.OwnedTriggerParams.TriggerModel = CogAcqTriggerModelConstants.Manual; #region AcquisitionControl Set var cogGigEAccess = _CogAcqFifo.FrameGrabber.OwnedGigEAccess; var features = cogGigEAccess.GetAvailableFeatures("AcquisitionControl"); foreach (var feature in features) { var cur_key = feature.ToString(); if (cur_key.Equals("AcquisitionMode")) { if (cogGigEAccess.IsWriteable("AcquisitionMode")) { cogGigEAccess.SetFeature("AcquisitionMode", "Continuous"); LOG.FuncI($"AcquisitionMode Continuous"); } } if (cur_key.Equals("TriggerMode")) { if (cogGigEAccess.IsWriteable("TriggerMode")) { cogGigEAccess.SetFeature("TriggerMode", "Off"); LOG.FuncI($"TriggerMode Off"); } } if (cur_key.Equals("TriggerSource")) { if (cogGigEAccess.IsWriteable("TriggerSource")) { cogGigEAccess.SetFeature("TriggerSource", "Software"); LOG.FuncI($"TriggerSource {CamConfig.TrigSrc.ToString()}"); } } } #endregion } catch (Exception e) { LOG.FuncE(e.Message); return E1.GENERIC; } return E1.OK; } #endregion protected override int DoDeactivate() { if (!IsActivated) { LOG.I("Already Deactivated!"); return E1.OK; } return Dispose(); } #region Dispose private int Dispose() { try { StopBufLive(); FreeImages(); _CogFrameGrabber?.Disconnect(true); CogGrabberManager.Instance.Dispose(); CogEnvironment.Instance.Dispose(); return E1.OK; } catch (Exception e) { LOG.FuncE(e.Message); } return E1.GENERIC; } private void StopBufLive() { if (!IsActivated) return; try { lock (LockThis) { StopLive(); } } catch (Exception e) { LOG.E(e.Message); } } private void FreeImages() { try { CogEnvironment.FreeImage(_OriginalImage); CogEnvironment.FreeImage(_FilteredImage); CogEnvironment.FreeImage(_FilteredLiveImage); } catch (Exception e) { LOG.E(e.Message); } } #endregion /// /// 카메라 연결 유무 /// /// 연결 유무 public override bool IsConnected() { bool cached = false; var status = _CogFrameGrabber?.GetStatus(cached); return status == CogFrameGrabberStatusConstants.Active; } /// /// 카메라 Gain 읽기 /// /// Gain 값 public override int GetGain() => throw new NotSupportedException(); /// /// 카메라 Gain 쓰기 /// /// Gain 값 /// 성공 유무 public override int SetGain(int gain) => throw new NotSupportedException(); /// /// 카메라 노출시간 읽기 /// /// 노출시간 public override double GetExposure() { if (!IsConnected()) { return E2.NOT_READY; } return _CogAcqFifo.OwnedExposureParams.Exposure; } /// /// 카메라 최소 노출시간 읽기 /// /// 최소 노출시간 public override double GetMinExposure() { if (!IsConnected()) { return E2.NOT_READY; } return _CogAcqFifo.OwnedExposureParams.MinExposure; } /// /// 카메라 최대 노출시간 읽기 /// /// 최대 노출시간 public override double GetMaxExposure() => throw new NotSupportedException(); /// /// 카메라 노출시간 쓰기 /// /// 노출시간[ms] /// 성공 유무 public override int SetExposure(double exposureTime_ms) { if (!IsConnected()) { return E2.NOT_READY; } _CogAcqFifo.OwnedExposureParams.Exposure = CamConfig.ExposureTime_Ms; LOG.I($"Exposure Time Set : {exposureTime_ms} ms"); return E1.OK; } /// /// 이미지 반전 & 회전 Config 갱신 /// /// 이미지 반전 & 회전 Config /// 성공 유무 public override int ResetFlipAndRotation(FlipAndRotation flipAndRotation) { _CogImgFlipAndRotationTool.ResetFlipAndRotation(flipAndRotation); return E1.OK; } /// /// 디스플레이 가져오기 /// /// 디스플레이 public override ICameraDisplay GetDisplay() { return _CogCameraDisplay; } /// /// 디스플레이 지정하기 /// /// 디스플레이 public override void SetDisplay(ICameraDisplay cameraDisplay) { _CogCameraDisplay = cameraDisplay as ICogCameraDisplay; } /// /// 이미지 사이즈 가져오기 /// /// 이미지 사이즈 public override ImageSize GetImageSize() { return CogEnvironment.Instance.GetImageSize(CamConfig); } #region Grab /// /// 버퍼로부터 이미지를 꺼내온다. /// /// 디스플레이 /// 이미지 /// 성공 유무 public override int GetGrabImageFromBuffer(ICameraDisplay cameraDisplay, ref object image) { SetDisplay(cameraDisplay as ICogCameraDisplay); return GetGrabImage(ref image, isExtTrig: true); } /// /// 동기식 그랩 /// /// 디스플레이 /// 그랩 이미지 /// 성공 유무 public override int GrabSync(ICameraDisplay cameraDisplay, ref object image) { SetDisplay(cameraDisplay as ICogCameraDisplay); CogEnvironment.ClearImage(image); CogEnvironment.ClearImage(_OriginalImage); if (CamConfig.TriggerMode.IsSoftware) { return GrabSync_SwTrig(ref image); } else { return GrabSync_HwTrig(ref image); } } private int GrabSync_HwTrig(ref object grabImage) { for (int i = 1; i <= CamConfig.GrabRetry; i++) { int res = GrabSync_HwTrig_Internal(ref grabImage); if (res == E1.OK) { return E1.OK; } LOG.I($"Grab Retry : {i}"); } LOG.E("Grab Fail"); return E1.OK; } private int GrabSync_HwTrig_Internal(ref object grabImage) { try { SetHookCompleted(false); TriggerOn(); Thread.Sleep(CamConfig.HwTrigConfig.OffDelay_Ms); TriggerOff(); return GetGrabImage(ref grabImage, isExtTrig: false); } catch (Exception e) { LOG.FuncE(e.Message); } return E1.GENERIC; } private int GrabSync_SwTrig(ref object image) { //TactTimeChecker.Start(TactTimeType.Grab, "GrabSyncWithFiltering"); try { SetHookCompleted(false); lock (LockThis) { int ticket = _CogAcqFifo.StartAcquire(); } //LOG.FuncI($"Grab Ticket : {ticket}"); return GetGrabImage(ref image, isExtTrig: false); } catch (Exception e) { LOG.FuncE(e.Message); } //TactTimeChecker.Stop(TactTimeType.Grab, "GrabSyncWithFiltering"); return E1.GENERIC; } private int GetGrabImage(ref object image, bool isExtTrig) { var stopWatch = Stopwatch.StartNew(); while (stopWatch.ElapsedMilliseconds < CamConfig.GrabTimeout_Ms) { bool hookCompleted = isExtTrig ? IsHookCompletedByExtTrig() : IsHookCompleted(); if (hookCompleted) { //LOG.W($"Grab Completed : {stopWatch.ElapsedMilliseconds} ms"); FlipAndRotateImage(); CropImage(); SetDisplayImage(); image = IsLiveOn() ? _CroppedLiveImage : _CroppedImage; return E1.OK; } Thread.Sleep(10); } LOG.E($"HookHandler Time Out!! [{CamConfig.GrabTimeout_Ms} ms] "); return E1.GENERIC; } #endregion #region Live /// /// 라이브 시작 /// /// 디스플레이 public override void StartLive(ICameraDisplay cameraDisplay) { lock (_LiveLock) { // 이미 라이브가 실행중이면, 디스플레이만 변경한다. SetDisplay(cameraDisplay); if (!IsLiveOn()) { LiveOn(); if (CamConfig.TriggerMode.IsSoftware) { Task.Run(() => { StartLive_SwTrig(); }); } else { Task.Run(() => { StartLive_HwTrig(); }); } } } } /// /// 라이브 종료 /// public override void StopLive() { lock (_LiveLock) { if (IsLiveOn()) { LiveOff(); } } } private void StartLive_HwTrig() { try { while (IsLiveOn()) { SetHookCompleted(false); TriggerOn(); Thread.Sleep(CamConfig.HwTrigConfig.OffDelay_Ms); TriggerOff(); var stopWatch = Stopwatch.StartNew(); while (stopWatch.ElapsedMilliseconds < CamConfig.GrabTimeout_Ms) { if (IsHookCompleted()) { FlipAndRotateImage(); CropImage(); SetDisplayImage(); break; } Thread.Sleep(10); } //Thread.Sleep(CamConfig.LiveDelay); } } catch (Exception e) { LOG.FuncE(e.Message); } } private void StartLive_SwTrig() { try { object tmpImage = null; // 라이브때 리턴 이미지는 필요X while (IsLiveOn()) { GrabSync(_CogCameraDisplay, ref tmpImage); Thread.Sleep(CamConfig.LiveDelay_Ms); } } catch (Exception e) { LOG.FuncE(e.Message); } } #endregion private void TriggerOn() { try { EquipAdapter.Instance.EquipTrigger.TriggerOn(CamConfig); } catch (Exception e) { LOG.FuncE(e.Message); } } private void TriggerOff() { try { EquipAdapter.Instance.EquipTrigger.TriggerOff(CamConfig); } catch (Exception e) { LOG.FuncE(e.Message); } } /// /// 디스플레이에서 이미지 꺼내오기 /// /// 디스플레이 /// 디스플레이 이미지 public override object GetImageFromDisplay(ICameraDisplay cameraDisplay) { // 버퍼 그랩 이후, 디스플레이로부터 이미지를 꺼내오는 부분 // 이미지를 꺼내오기 전에 cameraDisplay와 동일한 _CogCameraDisplay가 사전에 Set되어 있어야 함. var cogCameraDisplay = cameraDisplay as ICogCameraDisplay; if (cogCameraDisplay.IsSetImage()) { return cogCameraDisplay.GetImage(); } LOG.FuncE("Image of cogCameraDisplay is null."); return null; } //public override ICogImage GetGrabImageFromQueue(eGrabTrigSrcType grabTrigSrcType) { // if (_CogImgQueManager.IsImgQueSet(grabTrigSrcType)) // return _CogImgQueManager.Dequeue(); // else // return null; //} private void HookHandler(object sender, CogCompleteEventArgs e) { try { PrintHookLog(); var acqInfo = new CogAcqInfo(); _OriginalImage = ((ICogAcqFifo)sender).CompleteAcquireEx(acqInfo); //var rawImage = ((ICogAcqFifo)sender).CompleteAcquireEx(acqInfo); //CogEnvironment.CopyImage(rawImage, ref _OriginalImage); // 중복 Copy 제거 SetHookCompleted(true); if (CamConfig.TriggerMode.IsExternal) { SetHookCompletedByExtTrig(true); } } catch (Exception ex) { LOG.FuncE(ex.Message); } } private void PrintHookLog() { if (!IsLiveOn()) { LOG.FuncV("[HW Trigger] HookHandler Called."); } } #region For Flying /// /// 플라잉 시작 /// /// FOV 정보 /// 추가 파라미터 /// 성공 유무 public override int StartFlying(FovInfo fovInfo, object param = null) { if (IsFlyingRunning()) { LOG.FuncW("Flying Already Running..."); return E1.GENERIC; } LOG.I("Flying Start."); try { var camDisplay = CameraDisplayManager.GetDisplay(fovInfo); SetDisplay(camDisplay); _FlyingIdx = 0; _IsFlyingRunning = true; //StartMdigProcess(); } catch (Exception e) { LOG.FuncE(e.Message); return E1.GENERIC; } return E1.OK; } /// /// 플라잉 종료 /// /// 추가 파라미터 /// 성공 유무 public override int StopFlying(object param = null) { LOG.I("Flying Stopped."); try { _IsFlyingRunning = false; //StopMdigProcess(); } catch (Exception e) { LOG.FuncE(e.Message); return E1.GENERIC; } return E1.OK; } private void SetFlyingImage() { if (!CamConfig.TriggerMode.IsFlying) { return; } try { FlipAndRotateImage(); CropImage(); _FlyingIdx = (_FlyingIdx < _ProcImages.Length) ? _FlyingIdx : 0; //MilEnvironment.CopyImage(_CroppedImage, (MIL_ID)_ProcImages[_FlyingIdx++]); SetDisplayImage(); } catch (Exception e) { LOG.FuncE(e.Message); } } #endregion #region For Child Image /// /// Child 이미지들로 Parent(원장) 이미지가 정상적으로 만들어지는지 검증 /// /// 디스플레이 /// 원장(부모) 이미지 /// 성공 유무 public override int VerifyChildImage(ICameraDisplay cameraDisplay, ref object image) { try { return E1.OK; } catch (Exception e) { LOG.FuncE(e.Message); } return E1.GENERIC; } #endregion private void SetDisplayImage() { if (_CogCameraDisplay == null) { return; } if (IsLiveOn()) { if (_FilteredLiveImage != null) { _CogCameraDisplay.SetImage(_CroppedLiveImage); } } else { if (_FilteredImage != null) { _CogCameraDisplay.SetImage(_CroppedImage); } } } private void FlipAndRotateImage() { if (_OriginalImage == null) { LOG.FuncE("_OriginalImage is null."); return; } if (IsLiveOn()) { _CogImgFlipAndRotationTool.FlipAndRotation(_OriginalImage, ref _FilteredLiveImage); } else { _CogImgFlipAndRotationTool.FlipAndRotation(_OriginalImage, ref _FilteredImage); } } private void CropImage() { var cropRoi = GetCropRoi(); if (IsLiveOn()) { if (_FilteredLiveImage != null) { _CroppedLiveImage = CogEnvironment.CropImage(_FilteredLiveImage, cropRoi); } } else { if (_FilteredImage != null) { _CroppedImage = CogEnvironment.CropImage(_FilteredImage, cropRoi); } } } private class HookDataStruct { public ICogImage HookDigitizer; public ICogImage HookImage; public int HookCnt; } } }