@@ -514,27 +514,16 @@ def patch_basler_sdk(monkeypatch, fake_pylon_module):
514514
515515
516516# -----------------------------------------------------------------------------
517- < << << << cy / upgrade - gentl - backend
518517# Fake GenTL / harvesters SDK (SDK-free) + fixtures for strict lifecycle tests
519- == == == =
520- # Fake GenTL / harvesters SDK (open/read/close capable) + fixtures
521- >> >> >> > cy / pre - release - fixes - 2.0
522518# -----------------------------------------------------------------------------
523519
524520
525521class FakeGenTLTimeoutException (TimeoutError ):
526- < << << << cy / upgrade - gentl - backend
527522 """Fake timeout/error type used as HarvesterTimeoutError in backend tests."""
528- == == == =
529- """
530- Representative timeout: Harvesters often surfaces GenTL TimeoutException semantics.
531- """
532- >> >> >> > cy / pre - release - fixes - 2.0
533523
534524 pass
535525
536526
537- < << << << cy / upgrade - gentl - backend
538527def _info_get (info : Any , key : str , default = None ):
539528 """Read a device-info field from dict-like or attribute-like entries."""
540529 try :
@@ -555,46 +544,6 @@ def _info_get(info: Any, key: str, default=None):
555544
556545class _FakeNode :
557546 """Minimal GenICam node: .value plus optional constraints and symbolics."""
558- == == == =
559- class _DeviceInfoAdapter :
560- """
561- Make device_info_list entries behave whether they're dict-like or object-like.
562- """
563-
564- def __init__ (self , payload ):
565- self ._payload = payload
566-
567- def get (self , key , default = None ):
568- if isinstance (self ._payload , dict ):
569- return self ._payload .get (key , default )
570- return getattr (self ._payload , key , default )
571-
572- @property
573- def serial_number (self ):
574- return self .get ("serial_number" , "" )
575-
576- @property
577- def vendor (self ):
578- return self .get ("vendor" , "" )
579-
580- @property
581- def model (self ):
582- return self .get ("model" , "" )
583-
584- @property
585- def display_name (self ):
586- return self .get ("display_name" , "" )
587-
588-
589- class _FakeNode :
590- """
591- Minimal GenICam-style node with .value and optional constraints.
592- Harvesters exposes nodes as objects; your backend uses:
593- - node.value
594- - node.min / node.max / node.inc (for Width/Height)
595- - PixelFormat.symbolics (for allowed formats)
596- """
597- > >> >> >> cy / pre - release - fixes - 2.0
598547
599548 def __init__ (self , value = None , * , min = None , max = None , inc = 1 , symbolics = None ):
600549 self .value = value
@@ -605,7 +554,6 @@ def __init__(self, value=None, *, min=None, max=None, inc=1, symbolics=None):
605554
606555
607556class _FakeNodeMap :
608- < << << << cy / upgrade - gentl - backend
609557 """Node map with the attributes your GenTLCameraBackend touches."""
610558
611559 def __init__ (
@@ -627,23 +575,11 @@ def __init__(
627575 self .DeviceDisplayName = _FakeNode (display )
628576
629577 # Pixel format node
630- == == == =
631- """Provides attribute access for nodes used by GenTLCameraBackend."""
632-
633- def __init__ (self , * , width = 1920 , height = 1080 , fps = 30.0 , exposure = 10000.0 , gain = 0.0 , pixel_format = "Mono8" ):
634- # Identification / label fields your _resolve_device_label() tries
635- self .DeviceModelName = _FakeNode ("FakeGenTLModel" )
636- self .DeviceSerialNumber = _FakeNode ("FAKE-GENTL-0" )
637- self .DeviceDisplayName = _FakeNode ("FakeGenTLDisplay" )
638-
639- # Format + acquisition nodes
640- >> >> >> > cy / pre - release - fixes - 2.0
641578 self .PixelFormat = _FakeNode (
642579 pixel_format ,
643580 symbolics = ["Mono8" , "Mono16" , "RGB8" , "BGR8" ],
644581 )
645582
646- < << << << cy / upgrade - gentl - backend
647583 # Width/Height constraints for increment alignment logic
648584 self .Width = _FakeNode (int (width ), min = 64 , max = 4096 , inc = 2 )
649585 self .Height = _FakeNode (int (height ), min = 64 , max = 4096 , inc = 2 )
@@ -654,19 +590,6 @@ def __init__(self, *, width=1920, height=1080, fps=30.0, exposure=10000.0, gain=
654590 self .ResultingFrameRate = _FakeNode (float (fps ))
655591
656592 # Exposure / gain
657- == == == =
658- # Width/Height with constraints for increment alignment logic
659- self .Width = _FakeNode (int (width ), min = 64 , max = 4096 , inc = 2 )
660- self .Height = _FakeNode (int (height ), min = 64 , max = 4096 , inc = 2 )
661-
662- # FPS related nodes (backend may set AcquisitionFrameRate)
663- self .AcquisitionFrameRateEnable = _FakeNode (True )
664- self .AcquisitionFrameRate = _FakeNode (float (fps ))
665- # backend tries ResultingFrameRate for actual FPS; provide it
666- self .ResultingFrameRate = _FakeNode (float (fps ))
667-
668- # Exposure/Gain
669- >> >> >> > cy / pre - release - fixes - 2.0
670593 self .ExposureAuto = _FakeNode ("Off" )
671594 self .ExposureTime = _FakeNode (float (exposure ))
672595 self .GainAuto = _FakeNode ("Off" )
@@ -679,39 +602,21 @@ def __init__(self, node_map: _FakeNodeMap):
679602
680603
681604class _FakeComponent :
682- < << << << cy / upgrade - gentl - backend
683605 """
684606 Component with .data, .width, .height like Harvesters component2D image.
685607 Your backend does np.asarray(component.data) and reshape using height/width.
686608 """
687609
688- == == == =
689- >> >> >> > cy / pre - release - fixes - 2.0
690610 def __init__ (self , width : int , height : int , channels : int , dtype = np .uint8 ):
691611 self .width = int (width )
692612 self .height = int (height )
693613 self ._channels = int (channels )
694- < << << << cy / upgrade - gentl - backend
695-
696- == == == =
697- self ._dtype = dtype
698614
699- # Create a deterministic image payload
700- >> >> >> > cy / pre - release - fixes - 2.0
701615 n = self .width * self .height * self ._channels
702616 if dtype == np .uint8 :
703617 arr = (np .arange (n ) % 255 ).astype (np .uint8 )
704618 else :
705- < << << << cy / upgrade - gentl - backend
706- arr = (np .arange (n ) % 65535 ).astype (np .uint16 )
707- == == == =
708- # e.g., uint16
709619 arr = (np .arange (n ) % 65535 ).astype (np .uint16 )
710-
711- # Harvesters often exposes component.data as a buffer-like object;
712- # your backend does np.asarray(component.data) and may fall back to frombuffer(bytes(...)).
713- # A numpy array works fine for both.
714- >> >> >> > cy / pre - release - fixes - 2.0
715620 self .data = arr
716621
717622
@@ -721,14 +626,7 @@ def __init__(self, component: _FakeComponent):
721626
722627
723628class _FakeFetchedBufferCtx :
724- < << << << cy / upgrade - gentl - backend
725629 """Context manager returned by fetch(). Must have .payload."""
726- == == == =
727- """
728- Context manager returned by FakeImageAcquirer.fetch().
729- Must provide .payload with components.
730- """
731- >> >> >> > cy / pre - release - fixes - 2.0
732630
733631 def __init__ (self , payload : _FakePayload ):
734632 self .payload = payload
@@ -740,7 +638,6 @@ def __exit__(self, exc_type, exc, tb):
740638 return False
741639
742640
743- < << << << cy / upgrade - gentl - backend
744641@dataclass
745642class FakeImageAcquirer :
746643 """
@@ -784,48 +681,11 @@ def _enqueue_default_frame(self):
784681 channels , dtype = 1 , np .uint16
785682 else :
786683 channels , dtype = 1 , np .uint8
787- == == == =
788- class FakeImageAcquirer :
789- """
790- Minimal Harvesters image acquirer:
791- - remote_device.node_map
792- - start()/stop()/destroy()
793- - fetch(timeout=...) -> context manager
794- - node_map shortcut (your backend uses self._acquirer.node_map in read())
795- """
796-
797- def __init__ (self , * , serial = "FAKE-GENTL-0" , width = 1920 , height = 1080 , pixel_format = "Mono8" ):
798- self .serial = serial
799- self ._started = False
800- self ._destroyed = False
801-
802- # Node map used by open() and read()
803- self .remote_device = _FakeRemoteDevice (_FakeNodeMap (width = width , height = height , pixel_format = pixel_format ))
804- self .node_map = self .remote_device .node_map
805-
806- # Simple FIFO of frames (buffers)
807- self ._queue : list [_FakePayload ] = []
808- self ._populate_default_frames ()
809-
810- def _populate_default_frames (self ):
811- # Make one frame available by default
812- pf = str (self .node_map .PixelFormat .value or "Mono8" )
813- if pf in ("RGB8" , "BGR8" ):
814- channels = 3
815- dtype = np .uint8
816- elif pf == "Mono16" :
817- channels = 1
818- dtype = np .uint16
819- else :
820- channels = 1
821- dtype = np .uint8
822- > >> >> >> cy / pre - release - fixes - 2.0
823684
824685 comp = _FakeComponent (self .node_map .Width .value , self .node_map .Height .value , channels , dtype = dtype )
825686 self ._queue .append (_FakePayload (comp ))
826687
827688 def start (self ):
828- < << << << cy / upgrade - gentl - backend
829689 self .start_calls += 1
830690 self ._started = True
831691
@@ -846,30 +706,13 @@ def fetch(self, timeout: float = 2.0):
846706
847707 if not self ._queue :
848708 raise FakeGenTLTimeoutException (f"timeout after { timeout } s" )
849- == == == =
850- self ._started = True
851-
852- def stop (self ):
853- self ._started = False
854-
855- def destroy (self ):
856- self ._destroyed = True
857-
858- def fetch (self , timeout : float = 2.0 ):
859- if not self ._started :
860- raise FakeGenTLTimeoutException ("Acquirer not started" )
861-
862- if not self ._queue :
863- raise FakeGenTLTimeoutException (f"Timeout after { timeout } s" )
864- >> >> >> > cy / pre - release - fixes - 2.0
865709
866710 payload = self ._queue .pop (0 )
867711 return _FakeFetchedBufferCtx (payload )
868712
869713
870714class FakeHarvester :
871715 """
872- <<<<<<< cy/upgrade-gentl-backend
873716 Minimal Harvester:
874717 - add_file/update/reset
875718 - device_info_list
@@ -1001,95 +844,11 @@ def patch_gentl_sdk(monkeypatch, fake_harvester_factory):
1001844 monkeypatch .setattr (gb , "HarvesterTimeoutError" , FakeGenTLTimeoutException , raising = False )
1002845
1003846 # Avoid filesystem CTI searching
1004- == == == =
1005- Minimal fake for 'from harvesters.core import Harvester' supporting :
1006- - add_file / update / reset
1007- - device_info_list for enumeration
1008- - create ()/ create_image_acquirer () returning FakeImageAcquirer
1009-
1010- This enables GenTLCameraBackend .open / read / close paths .
1011- """
1012-
1013- def __init__(self):
1014- self.device_info_list = []
1015- self._files = []
1016- self._acquirers: list[FakeImageAcquirer] = []
1017-
1018- def add_file(self, file_path: str):
1019- self._files.append(str(file_path))
1020-
1021- def update(self):
1022- # Harvesters tutorial output shows dict-like device entries.
1023- self.device_info_list = [
1024- {
1025- "display_name": "TLSimuMono (FAKE-GENTL-0)",
1026- "model": "FakeGenTLModel",
1027- "vendor": "FakeVendor",
1028- "serial_number": "FAKE-GENTL-0",
1029- "id_": "FakeDeviceId",
1030- "tl_type": "Custom",
1031- "user_defined_name": "Center",
1032- "version": "1.0.0",
1033- }
1034- ]
1035-
1036- def reset(self):
1037- # "release" resources
1038- self.device_info_list = []
1039- self._files = []
1040- self._acquirers = []
1041-
1042- def create(self, selector=None, index: int | None = None, *args, **kwargs):
1043- serial = None
1044-
1045- # Selector dict commonly used: {"serial_number": "..."} [1](https://github.com/genicam/harvesters/issues/454)
1046- if isinstance(selector, dict):
1047- serial = selector.get("serial_number")
1048-
1049- if serial is None and index is None:
1050- index = 0
1051-
1052- if not self.device_info_list:
1053- self.update()
1054-
1055- if serial is None:
1056- if index is None:
1057- index = 0
1058- if index < 0 or index >= len(self.device_info_list):
1059- raise RuntimeError("Index out of range")
1060- info = _DeviceInfoAdapter(self.device_info_list[index])
1061- serial = info.serial_number or "FAKE-GENTL-0"
1062-
1063- acq = FakeImageAcquirer(serial=serial)
1064- self._acquirers.append(acq)
1065- return acq
1066-
1067- def create_image_acquirer(self, *args, **kwargs):
1068- # Alias used by some Harvesters versions; just delegate to create()
1069- return self.create(*args, **kwargs)
1070-
1071-
1072- @pytest.fixture()
1073- def fake_harvester_class():
1074- """ Provides FakeHarvester class for patching GenTL backend ."""
1075- return FakeHarvester
1076-
1077-
1078- @pytest.fixture()
1079- def patch_gentl_sdk(monkeypatch, fake_harvester_class):
1080- import dlclivegui.cameras.backends.gentl_backend as gb
1081-
1082- monkeypatch.setattr(gb, "Harvester", fake_harvester_class, raising=False)
1083- monkeypatch.setattr(gb, "HarvesterTimeoutError", FakeGenTLTimeoutException, raising=False)
1084-
1085- # Prevent CTI searching from blocking open/get_device_count
1086- >>>>>>> cy/pre-release-fixes-2.0
1087847 monkeypatch .setattr (gb .GenTLCameraBackend , "_find_cti_file" , lambda self : "dummy.cti" , raising = False )
1088848 monkeypatch .setattr (
1089849 gb .GenTLCameraBackend , "_search_cti_file" , staticmethod (lambda patterns : "dummy.cti" ), raising = False
1090850 )
1091851
1092- <<<<<<< cy/upgrade-gentl-backend
1093852 return gb
1094853
1095854
@@ -1128,9 +887,6 @@ def _make(
1128887 )
1129888
1130889 return _make
1131- =======
1132- return fake_harvester_class
1133- >>>>>>> cy/pre-release-fixes-2.0
1134890
1135891
1136892# -----------------------------------------------------------------------------
0 commit comments