py-alaska 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,391 @@
1
+ """IMI 카메라 제어 클래스 (Neptune API)"""
2
+
3
+ from ast import Global
4
+ import ctypes
5
+ from email.mime import image
6
+ import logging
7
+ import multiprocessing
8
+ import os
9
+ import sys
10
+ import time
11
+ from pathlib import Path
12
+ from typing import Optional
13
+
14
+ import cv2
15
+ import numpy as np
16
+ from PySide6.QtCore import QObject, Signal
17
+ from PySide6.QtGui import QImage, QPixmap
18
+
19
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1])) # Dynamic2 폴더
20
+
21
+ from camera.SmBlock import SmBlock
22
+ from utils.config_log import logger as log
23
+ from ConfigReader import GlobalConfig, banner
24
+ from camera.Neptune_API import *
25
+ from threading import Thread
26
+
27
+
28
+ class CameraStatus(Thread, QObject):
29
+ image_received = Signal(np.ndarray)
30
+ status_changed = Signal(str)
31
+ def __init__(self):
32
+ QObject.__init__(self)
33
+ Thread.__init__(self)
34
+ self.running = True
35
+ self.smblock = SmBlock(name="SmBlock", shape=(2048,2448, 3), maxsize=100, create=True, lock=multiprocessing.Lock())
36
+ self.rx_image = multiprocessing.Queue()
37
+ self.rx_cmdq = multiprocessing.Queue()
38
+ self.is_connected = False
39
+ self.count_invalid = 0
40
+ self.count_rx = 0
41
+
42
+ def set_value(self, key, value):
43
+ print(f"Set {key} = {value}")
44
+
45
+ def put_cmd(self, cmd):
46
+ self.rx_cmdq.put(cmd)
47
+
48
+ def put_image(self, rx_frame, pImage):
49
+ #print(f"put_image called self.count_rx={self.count_rx} count_invalid={self.count_invalid}")
50
+ if rx_frame is None:
51
+ self.rx_image.put(None)
52
+ return
53
+ image = (cv2.cvtColor(rx_frame.reshape(pImage.uiHeight, pImage.uiWidth), cv2.COLOR_GRAY2BGR)
54
+ if pImage.uiBitDepth != 8
55
+ else cv2.cvtColor(rx_frame.reshape(pImage.uiHeight, pImage.uiWidth, -1), 63))
56
+ index = self.smblock.malloc2(image)
57
+ self.rx_image.put((index, self.count_rx))
58
+
59
+ def put_cmd(self, cmd):
60
+ self.rx_cmdq.put(cmd)
61
+
62
+ def run(self):
63
+ self.running = True
64
+ reason = "normal_exit"
65
+ while self.running:
66
+ if not self.rx_image.empty():
67
+ image_id, frame_id = self.rx_image.get()
68
+ if image_id is None:
69
+ reason = "image_none"
70
+ break
71
+ #print(f"Received index:{image_id} rx={self.count_rx} invalid={self.count_invalid} is_connected = {self.is_connected}")
72
+ image = self.smblock.get(image_id)
73
+ self.image_received.emit(image.copy()) # 빨리 Frame들어오면 문제가 생김
74
+ self.smblock.mfree(image_id)
75
+ if not self.rx_cmdq.empty():
76
+ cmd = self.rx_cmdq.get()
77
+ banner(f"[CAM.SIGANL] <{cmd}> received")
78
+ if cmd == "close":
79
+ self.running = False
80
+ self.is_connected = False
81
+ reason = "cmd_close"
82
+ break
83
+ if cmd == "connected":
84
+ reason = "cmd_connected"
85
+ self.reopen()
86
+ self.is_connected = True
87
+ self.status_changed.emit("connected")
88
+ if cmd == "disconnected":
89
+ reason = "cmd_disconnected"
90
+ self.is_connected = False
91
+ self.status_changed.emit("disconnected")
92
+ time.sleep(0.01)
93
+ banner(f"** CameraSignal thread stopped. reason: {reason} **")
94
+
95
+ @staticmethod
96
+ def numpy_to_pixmap( image: np.ndarray) -> QPixmap:
97
+ if image.ndim == 2:
98
+ height, width = image.shape
99
+ q_image = QImage(image.data, width, height, width, QImage.Format_Grayscale8)
100
+ else:
101
+ height, width, channels = image.shape
102
+ format_map = {3: QImage.Format_BGR888, 4: QImage.Format_RGBA8888}
103
+ if channels not in format_map:
104
+ raise ValueError(f"Unsupported channel number: {channels}")
105
+ q_image = QImage(image.data, width, height, channels * width, format_map[channels])
106
+
107
+ return QPixmap.fromImage(q_image)
108
+
109
+ class imi_camera(CameraStatus):
110
+ """IMI 카메라 제어 클래스"""
111
+
112
+ DriverInit = False
113
+ HEARTBEAT_TIME = 500
114
+ DEFAULT_MAC = "192.168.1.100"
115
+
116
+
117
+ def __init__(self):
118
+ super().__init__()
119
+ self.handle = ctypes.c_void_p(0)
120
+ self.mac = self.DEFAULT_MAC
121
+
122
+
123
+ log.info("[ImiCamera] 인스턴스 생성")
124
+
125
+ # self.flow_thread = CameraSignal(camera=self)
126
+
127
+ @staticmethod
128
+ def get_mac_list() -> list[str]:
129
+ """연결된 카메라의 MAC 주소 목록 반환"""
130
+ numberOfCamera = ctypes.c_uint32(0)
131
+ err = ntcGetCameraCount(ctypes.pointer(numberOfCamera))
132
+ # banner(f"get_mac_list: err= {err}, count= {self.numberOfCamera.value}")
133
+ if err != ENeptuneError.NEPTUNE_ERR_Success.value or numberOfCamera.value == 0:
134
+ log.error(f"[get_mac_list] 카메라를 찾을 수 없음 / 관리자권한, 케이블점검 err = {err} count= {numberOfCamera.value}")
135
+ return []
136
+
137
+ cam_count = numberOfCamera.value
138
+ info = (NEPTUNE_CAM_INFO * cam_count)()
139
+ err = ntcGetCameraInfo(info, cam_count)
140
+
141
+ if err != ENeptuneError.NEPTUNE_ERR_Success.value:
142
+ log.error(f"[get_mac_list] ntcGetCameraInfo 실패: err={err}")
143
+ return []
144
+
145
+ mac_list = [info[i].strMAC.decode('utf-8') for i in range(cam_count)]
146
+ log.info(f"[get_mac_list] 카메라 {cam_count}대 발견: {mac_list}")
147
+ return mac_list
148
+
149
+ def close(self) -> None:
150
+ banner(f"[CAM.CLOSE] {self.is_connected}")
151
+ if not self.is_connected:
152
+ return
153
+ try:
154
+ self.put_cmd("close")
155
+ ntcClose(self.handle)
156
+ count = 0
157
+ while self.rx_image.qsize() > 0:
158
+ image_id,frame_id = self.rx_image.get()
159
+ self.smblock.mfree(image_id)
160
+ count = count + 1
161
+ banner(f"CAMEAR. CLOSE -------------------------------droped frames: {count}")
162
+ self.put_cmd("disconnected")
163
+ except Exception as e:
164
+ log.exception(f"[ImiCamera] 종료 중 오류: {e}")
165
+ self.put_cmd("disconnected")
166
+
167
+ def open(self) -> bool:
168
+ """카메라 연결"""
169
+ banner("CAMEAR. OPEN ---------------------------------")
170
+ if self.is_connected:
171
+ log.warning("[ImiCamera] 이미 연결되어 있음")
172
+ return True
173
+
174
+ # 드라이버 초기화
175
+ if not imi_camera.DriverInit:
176
+ if ntcInit() != ENeptuneError.NEPTUNE_ERR_Success.value:
177
+ log.error("[ImiCamera] 드라이버 초기화 실패")
178
+ return False
179
+ imi_camera.DriverInit = True
180
+ log.info("[ImiCamera] 드라이버 초기화 완료")
181
+ banner(f"connect: DriverInit= {imi_camera.DriverInit} success")
182
+
183
+ # 기존 핸들 정리
184
+ if self.handle:
185
+ ntcClose(self.handle)
186
+ self.handle = ctypes.c_void_p(0)
187
+
188
+ # 카메라 검색 및 연결
189
+ mac_list = imi_camera.get_mac_list()
190
+ if not mac_list:
191
+ log.error("[ImiCamera] 사용 가능한 카메라가 없음")
192
+ return False
193
+ self.mac = mac_list[0]
194
+ self.reopen()
195
+
196
+ # if ntcOpen(bytes(self.mac, encoding='utf-8'), ctypes.byref(self.handle)) != ENeptuneError.NEPTUNE_ERR_Success.value:
197
+ # log.error(f"[ImiCamera] ntcOpen 실패: {self.mac}")
198
+ # return False
199
+
200
+ # if ntcSetHeartbeatTime(self.handle, self.HEARTBEAT_TIME) != ENeptuneError.NEPTUNE_ERR_Success.value:
201
+ # log.error("[ImiCamera] ntcSetHeartbeatTime 실패")
202
+ # return False
203
+
204
+ # self.set_trigger_mode(False) # 처음 패킷막기
205
+
206
+ # # 콜백 설정
207
+ # self.callback_func = ctypes.CFUNCTYPE(None, NEPTUNE_IMAGE, ctypes.c_void_p)(self.RecvFrameCallBack)
208
+ # ntcSetFrameCallback(self.handle, self.callback_func, self.handle)
209
+
210
+ # self.callback_drop_func = ctypes.CFUNCTYPE(None, ctypes.c_void_p)(self.RecvFrameDropCallBack)
211
+ # ntcSetFrameDropCallback(self.handle, self.callback_drop_func, self.handle)
212
+
213
+ # self.callback_device_check_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p)(self.RecvDeviceCheckCallBack)
214
+ # ntcSetDeviceCheckCallback(self.callback_device_check_func, self.handle)
215
+
216
+ # # Acquisition 초기화
217
+ # for i, state in enumerate([False, True, False, True]):
218
+ # ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value if state else ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)
219
+
220
+ self.thread = Thread(target=self.run, daemon=True)
221
+ self.thread.start()
222
+ self.put_cmd("connected")
223
+ log.info(f"[ImiCamera] 카메라 연결 성공: {self.mac}")
224
+ return True
225
+
226
+ def reopen(self):
227
+ banner("CAMEAR. RE-OPEN ---------------------------------")
228
+
229
+ if ntcOpen(bytes(self.mac, encoding='utf-8'), ctypes.byref(self.handle)) != ENeptuneError.NEPTUNE_ERR_Success.value:
230
+ log.error(f"[ImiCamera] ntcOpen 실패: {self.mac}")
231
+ return False
232
+
233
+ if ntcSetHeartbeatTime(self.handle, self.HEARTBEAT_TIME) != ENeptuneError.NEPTUNE_ERR_Success.value:
234
+ log.error("[ImiCamera] ntcSetHeartbeatTime 실패")
235
+ return False
236
+
237
+ nExposeure = GlobalConfig.instance().getConfigValue("camera.exposure", 15000)
238
+ exposure = ctypes.c_uint32(nExposeure)
239
+ if ntcSetExposureTime(self.handle, exposure.value) != ENeptuneError.NEPTUNE_ERR_Success.value:
240
+ log.error(f"[ImiCamera] ntcSetExposureTime 실패")
241
+ return False
242
+
243
+ self.set_trigger_mode(False) # 처음 패킷막기
244
+
245
+ # 콜백 설정
246
+ self.callback_func = ctypes.CFUNCTYPE(None, NEPTUNE_IMAGE, ctypes.c_void_p)(self.RecvFrameCallBack)
247
+ ntcSetFrameCallback(self.handle, self.callback_func, self.handle)
248
+
249
+ self.callback_drop_func = ctypes.CFUNCTYPE(None, ctypes.c_void_p)(self.RecvFrameDropCallBack)
250
+ ntcSetFrameDropCallback(self.handle, self.callback_drop_func, self.handle)
251
+
252
+ self.callback_device_check_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p)(self.RecvDeviceCheckCallBack)
253
+ ntcSetDeviceCheckCallback(self.callback_device_check_func, self.handle)
254
+
255
+ # Acquisition 초기화
256
+ for i, state in enumerate([False, True, False, True]):
257
+ ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value if state else ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)
258
+
259
+ def set_frame_rate(self, frame_rate: float) -> bool:
260
+ emFPS = ctypes.c_uint32(0)
261
+ dbFPS = ctypes.c_double(frame_rate)
262
+ if ntcSetFrameRate(self.handle, emFPS.value, dbFPS.value) != ENeptuneError.NEPTUNE_ERR_Success.value:
263
+ log.error(f"[ImiCamera] ntcSetFrameRate 실패")
264
+ return False
265
+
266
+ def set_trigger_mode(self, enabled: bool) -> bool:
267
+ self.count_invalid = 0
268
+ self.count_rx = 0
269
+ st_trigger = NEPTUNE_TRIGGER()
270
+ st_trigger.Source = ENeptuneTriggerSource.NEPTUNE_TRIGGER_SOURCE_LINE1.value
271
+ st_trigger.Mode = ENeptuneTriggerMode.NEPTUNE_TRIGGER_MODE_0.value
272
+ st_trigger.Polarity = ENeptunePolarity.NEPTUNE_POLARITY_FALLINGEDGE.value
273
+ if enabled == True:
274
+ st_trigger.OnOff = ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value
275
+ self.set_frame_rate(300.0)
276
+ else :
277
+ st_trigger.OnOff = ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value
278
+ self.set_frame_rate(5.0)
279
+ err = ntcSetTrigger(self.handle, st_trigger)
280
+ if err == ENeptuneError.NEPTUNE_ERR_Success.value:
281
+ self.get_trigger_mode()
282
+ return True
283
+ else:
284
+ banner(f"ntcSetTrigger err={err}")
285
+ return False
286
+
287
+ def get_trigger_mode(self):
288
+ """ 카메라의 트리거 모드 정보 GET """
289
+ neptune_trigger = NEPTUNE_TRIGGER()
290
+ err = ntcGetTrigger(self.handle,ctypes.pointer(neptune_trigger))
291
+
292
+ if err != 0:
293
+ return (False, None)
294
+ else :
295
+ # 성공한 경우 값을 출력
296
+ print(f"Trigger OnOff : {neptune_trigger.OnOff}"
297
+ f" Mode : {neptune_trigger.Mode}"
298
+ f" Polarity : {neptune_trigger.Polarity}"
299
+ f"Trigger nParam : {neptune_trigger.nParam}")
300
+
301
+ if neptune_trigger.OnOff == ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value:
302
+ return (True, True)
303
+ else :
304
+ return (True, False)
305
+
306
+ def set_exposure(self, exposure: int) -> bool:
307
+ banner(f"set_exposure: {exposure}")
308
+ GlobalConfig.instance().setConfigValue("camera.exposure", exposure)
309
+ pui_micro_sec = ctypes.c_uint32(exposure)
310
+ err = ntcSetExposureTime(self.handle, pui_micro_sec)
311
+ if err != 0:
312
+ return False
313
+ return True
314
+
315
+ def get_exposure(self) -> int:
316
+ """카메라 노출값 반환 (config에서)"""
317
+ return GlobalConfig.instance().getConfigValue("camera.exposure", 15000)
318
+
319
+ def clear_image(self):
320
+ for i, state in enumerate([False, True, False, True]):
321
+ ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value if state else ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)
322
+
323
+ def stop_stream(self):
324
+ ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)
325
+
326
+ def start_stream(self):
327
+ for i, state in enumerate([False, True, False, True]):
328
+ ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value if state else ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)
329
+
330
+
331
+
332
+ def RecvDeviceCheckCallBack(self, state: int, pContext: Optional[ctypes.c_void_p] = None) -> None:
333
+ log.warning(f"[ImiCamera] 디바이스 {'추가' if state == 0 else '제거'}")
334
+ if state == 0:
335
+ self.put_cmd("connected")
336
+ for s in [False, True, False, True]:
337
+ ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value if s else ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)
338
+ else:
339
+ self.put_cmd("disconnected")
340
+ return None
341
+
342
+
343
+
344
+ def RecvFrameCallBack(self, pImage: NEPTUNE_IMAGE, pContext: Optional[ctypes.c_void_p] = None) -> Optional[np.ndarray]:
345
+ try:
346
+ self.count_rx += 1
347
+
348
+ rx_frame = np.frombuffer(pImage.pData[0:pImage.uiSize], dtype=np.uint8)
349
+ self.put_image(rx_frame, pImage)
350
+ except Exception as e:
351
+ log.exception(f"[ImiCamera] 프레임 처리 오류: {e}")
352
+ return None
353
+
354
+ def RecvFrameDropCallBack(self, pContext: Optional[ctypes.c_void_p] = None) -> None:
355
+ self.count_invalid += 1
356
+
357
+
358
+
359
+ def main() -> int:
360
+ """테스트 메인 함수"""
361
+ logging.basicConfig(
362
+ level=logging.INFO,
363
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
364
+ )
365
+
366
+ camera = imi_camera()
367
+ try:
368
+ if not camera.open():
369
+ log.error("카메라 연결 실패")
370
+ return 1
371
+ camera.set_trigger_mode(True)
372
+ time.sleep(20)
373
+ camera.close()
374
+ log.info(f"최종 수신된 프레임 수: {camera.count_rx}")
375
+ return 0
376
+
377
+ except KeyboardInterrupt:
378
+ log.info("사용자 중단 (Ctrl+C)")
379
+ return 0
380
+ except Exception as e:
381
+ log.error(f"예외 발생: {e}")
382
+ import traceback
383
+ traceback.print_exc()
384
+ return 1
385
+ finally:
386
+ camera.close()
387
+ log.info("프로그램 종료")
388
+
389
+
390
+ if __name__ == "__main__":
391
+ exit(main())