Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 72 additions & 28 deletions deapi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def _initialize_attributes(self):
PropertyCollection(client=self, name=collection, properties=props),
)

def connect(self, host: str = "127.0.0.1", port: int = 13240, read_only=False):
def connect(self, host: str = "127.0.0.1", port: int = 13240, read_only=None):
"""Connect to DE-Server

Parameters
Expand All @@ -160,20 +160,12 @@ def connect(self, host: str = "127.0.0.1", port: int = 13240, read_only=False):
The host to connect to, by default "127.0.0.1" for local connection
port : int, optional
The port to connect to, by default 13240
read_only : bool, optional
read_only : bool, optional (deprecated for command version >= 17)
If True, the client will be in read-only mode, by default False
For command version >= 17, the read_only parameter is ignored and
the client will query the server to determine whether it should be
read-only or read-write.
"""
self.read_only = read_only
if not read_only and (host == "localhost" or host == "127.0.0.1"):
tcp_no_delay = 0 # on loopback interface, nodelay causes delay

if self.usingMmf:
self.mmf = mmap.mmap(0, MMF_DATA_BUFFER_SIZE, "ImageFileMappingObject")
self.mmf[0] = True
else:
self.usingMmf = False # Disabled MMF if connected remotely
tcp_no_delay = 1

if logLevel == logging.DEBUG:
log.debug("Connecting to server: %s", host)

Expand All @@ -183,7 +175,6 @@ def connect(self, host: str = "127.0.0.1", port: int = 13240, read_only=False):
self.socket.connect(
(host, port)
) # Connect to server reading port for sending data
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, tcp_no_delay)
self.socket.setblocking(False)
self.socket.settimeout(2)

Expand All @@ -200,15 +191,15 @@ def connect(self, host: str = "127.0.0.1", port: int = 13240, read_only=False):
self.port = port
log.info("Connected to server: %s, port: %d", host, port)

if cVersion >= 12:
self.set_client_read_only(read_only)

server_version = self.GetProperty("Server Software Version")
server_version = re.findall(r"\d+", server_version)

version = [int(part) for part in server_version[:4]]
temp = version[2] + version[1] * 1000 + version[0] * 1000000
if temp >= 2008000 and version[3] >= 12073:
if temp >= 2008003 and version[3] >= 12436:
## version 2.8.3 build 12436+
self.commandVersion = 17
elif temp >= 2008000 and version[3] >= 12073:
## version 2.8.0 build 12073+ — virtual image buffer support (SDK 5.3.0)
self.commandVersion = 16
elif (temp >= 2007005 and version[3] < 11274) or temp >= 2008000:
Expand All @@ -234,6 +225,25 @@ def connect(self, host: str = "127.0.0.1", port: int = 13240, read_only=False):
self.commandVersion = 3
else:
self.commandVersion = commandVersion

if self.commandVersion >= 12 and self.commandVersion < 17:
if read_only is None:
read_only = False
self.set_client_read_only(read_only)
elif self.commandVersion >= 17:
if read_only is not None:
log.warning(
"The `read_only` argument to `connect()` is ignored for servers "
"with command version >= 17 (DE-MC version >= 2.8.3.12436). "
"The client now determines read-only status automatically."
)
self.get_client_read_only()

self.SelectImageTransferMode()
self.socket.setsockopt(
socket.IPPROTO_TCP, socket.TCP_NODELAY, self.tcp_no_delay
)

log.info(f"Command Version: {self.commandVersion}")
self._initialize_attributes()
self.update_scan_size()
Expand All @@ -245,10 +255,19 @@ def connect(self, host: str = "127.0.0.1", port: int = 13240, read_only=False):

def set_client_read_only(self, read_only):
self.read_only = read_only
command = self._addSingleCommand(self.SET_CLIENT_READ_ONLY, None, [read_only])
command = self._addSingleCommand(
self.SET_CLIENT_READ_ONLY_DEPRECATED, None, [read_only]
)
response = self._sendCommand(command)
return response

def get_client_read_only(self):
command = self._addSingleCommand(self.GET_CLIENT_READ_ONLY, None)
response = self._sendCommand(command)
if response != False:
self.read_only = self.__getParameters(response.acknowledge[0])[0]
return response

def update_scan_size(self):
self.scan_sizex = self["Scan - Size X"]
self.scan_sizey = self["Scan - Size Y"]
Expand Down Expand Up @@ -435,9 +454,11 @@ def get_property_specifications(self, property_name):
The name of the property to get the specifications for
"""
if self.commandVersion < 13:
log.error("get_property_specifications is only supported for server version 2.7.5 and above.")
log.error(
"get_property_specifications is only supported for server version 2.7.5 and above."
)
return None

command = self._addSingleCommand(
self.GET_PROPERTY_SPECIFICATIONS, property_name
)
Expand All @@ -448,9 +469,11 @@ def get_property_specifications(self, property_name):
values = self.__getParameters(response.acknowledge[0])

if not values or len(values) == 0:
log.error(f"get_property_specifications({property_name}) failed, parameter size not matched.")
log.error(
f"get_property_specifications({property_name}) failed, parameter size not matched."
)
return None

prop_spec = PropertySpecifications()

param_id = 0
Expand All @@ -463,9 +486,11 @@ def get_property_specifications(self, property_name):
elif data_type == "Integer":
prop_spec.prop_type = PropertyType.Integer
else:
log.error(f"get_property_specifications({property_name}) failed, property type not matched.")
log.error(
f"get_property_specifications({property_name}) failed, property type not matched."
)
return None

prop_allowable_type = values[param_id]
param_id += 1

Expand All @@ -477,15 +502,19 @@ def get_property_specifications(self, property_name):
prop_spec.max_value = values[param_id]
param_id += 1
else:
log.error(f"get_property_specifications({property_name}) failed, cannot read the min/max value.")
log.error(
f"get_property_specifications({property_name}) failed, cannot read the min/max value."
)
return None
elif prop_allowable_type == "Set":
prop_spec.prop_allowable_type = PropertyAllowableType.Set
prop_spec.values = list(values[param_id:-4])
elif prop_allowable_type == "AllowAll":
prop_spec.prop_allowable_type = PropertyAllowableType.AllowAll
else:
log.error(f"get_property_specifications({property_name}) failed, unknown allowable type.")
log.error(
f"get_property_specifications({property_name}) failed, unknown allowable type."
)
return None

prop_spec.category = values[-4]
Expand Down Expand Up @@ -2823,6 +2852,19 @@ def __del__(self):
if self.connected:
self.disconnect()

def SelectImageTransferMode(self):
if not self.read_only and (
self.host == "localhost" or self.host == "127.0.0.1"
):
self.tcp_no_delay = 0 # on loopback interface, nodelay causes delay

if self.usingMmf:
self.mmf = mmap.mmap(0, MMF_DATA_BUFFER_SIZE, "ImageFileMappingObject")
self.mmf[0] = True
else:
self.usingMmf = False # Disabled MMF if connected remotely
self.tcp_no_delay = 1

# get multiple parameters from a single acknowledge packet
def __getParameters(self, single_acknowledge=None):
output = []
Expand Down Expand Up @@ -3162,6 +3204,7 @@ def ParseChangedProperties(self, changedProperties, response):
height = 0
mmf = 0
usingMmf = True
tcp_no_delay = 0
debugImagesFolder = "D:\\DebugImages\\"
connected = False
camera = ""
Expand Down Expand Up @@ -3201,7 +3244,7 @@ def ParseChangedProperties(self, changedProperties, response):
SET_SCAN_ROI = 28
SET_SCAN_SIZE_AND_GET_CHANGED_PROPERTIES = 29
SET_SCAN_ROI__AND_GET_CHANGED_PROPERTIES = 30
SET_CLIENT_READ_ONLY = 31
SET_CLIENT_READ_ONLY_DEPRECATED = 31
SET_SCAN_XY_ARRAY = 32
SET_ADAPTIVE_ROI = 33
SET_ADAPTIVE_ROI_AND_GET_CHANGED_PROPERTIES = 34
Expand All @@ -3213,6 +3256,7 @@ def ParseChangedProperties(self, changedProperties, response):
LIST_REGISTERS = 40
GET_VIRTUAL_IMAGE_INFO = 41
GET_VIRTUAL_IMAGE = 42
GET_CLIENT_READ_ONLY = 43


MMF_DATA_HEADER_SIZE = 24
Expand Down
4 changes: 2 additions & 2 deletions deapi/data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,8 +665,8 @@ def __init__(

prop_type = None # Undef | String | Float | Integer
prop_allowable_type = None # Range | Set | AllowAll
min_value = None # Minimum value for Range type
max_value = None # Maximum value for Range type
min_value = None # Minimum value for Range type
max_value = None # Maximum value for Range type
values = None # List of values for Set allowable type
category = None # "Alias" | "Advanced" | "Basic" | "Deprecated" | "Engineering" | Obsolete"
default_value = None # Default value
Expand Down
22 changes: 20 additions & 2 deletions deapi/simulated_server/fake_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def __init__(self, dataset="grains", socket=None):
self.fake_data = None
self.socket = socket
self.current_movie_index = 0
self.is_read_only = False

with open(inp_file) as f:
values = json.load(f)
Expand Down Expand Up @@ -309,9 +310,14 @@ def _respond_to_command(self, command=None):
return self._fake_list_cameras(command)
elif (
command.command[0].command_id
== self.SET_CLIENT_READ_ONLY + commandVersion * 100
== self.SET_CLIENT_READ_ONLY_DEPRECATED + commandVersion * 100
):
return self._fake_set_client_read_only(command)
elif (
command.command[0].command_id
== self.GET_CLIENT_READ_ONLY + commandVersion * 100
):
return self._fake_get_client_read_only(command)
elif (
command.command[0].command_id
== self.SET_VIRTUAL_MASK + commandVersion * 100
Expand Down Expand Up @@ -343,6 +349,17 @@ def _fake_set_client_read_only(self, command):
ack1.command_id = command.command[0].command_id
return (acknowledge_return,)

def _fake_get_client_read_only(self, command):
acknowledge_return = pb.DEPacket()
acknowledge_return.type = pb.DEPacket.P_ACKNOWLEDGE
ack1 = acknowledge_return.acknowledge.add()
ack1.command_id = command.command[0].command_id
bool_param = ack1.parameter.add()
bool_param.type = pb.AnyParameter.P_BOOL
bool_param.p_bool = False
self.is_read_only = bool_param.p_bool
return (acknowledge_return,)

def _fake_set_virtual_mask(self, command):
acknowledge_return = pb.DEPacket()
acknowledge_return.type = pb.DEPacket.P_ACKNOWLEDGE
Expand Down Expand Up @@ -919,6 +936,7 @@ def _fake_get_virtual_image(self, command):
SET_SCAN_ROI = 28
SET_SCAN_SIZE_AND_GET_CHANGED_PROPERTIES = 29
SET_SCAN_ROI__AND_GET_CHANGED_PROPERTIES = 30
SET_CLIENT_READ_ONLY = 31
SET_CLIENT_READ_ONLY_DEPRECATED = 31
GET_VIRTUAL_IMAGE_INFO = 41
GET_VIRTUAL_IMAGE = 42
GET_CLIENT_READ_ONLY = 43
21 changes: 14 additions & 7 deletions deapi/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,17 +179,24 @@ def test_property_specifications_set(self, client, bin_sw):
sp = client.get_property_specifications("Binning Y")
assert isinstance(sp, PropertySpecifications)
assert sp.current_value == str(bin_sw)
assert (
sp.values
== ['1', '2', '4', '8', '16', '32', '64', '128', '256', '512', '1024']
)
assert sp.values == [
"1",
"2",
"4",
"8",
"16",
"32",
"64",
"128",
"256",
"512",
"1024",
]
client.set_property("Hardware Binning X", 2)
client.set_property("Hardware Binning Y", 2)
sp = client.get_property_specifications("Binning Y")
assert sp.current_value == str(bin_sw)
assert (
sp.values == ['1', '2', '4', '8', '16', '32', '64', '128', '256', '512']
)
assert sp.values == ["1", "2", "4", "8", "16", "32", "64", "128", "256", "512"]

@pytest.mark.parametrize("bin", [1, 2])
@pytest.mark.parametrize("offsetx", [0, 512])
Expand Down
14 changes: 8 additions & 6 deletions deapi/tests/test_scanning/test_virtual_image_buffers.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ def test_streaming_all_virtual_buffers(self, client):

while not finished:
for buf_id in range(NUM_VIRTUAL_BUFFERS):
status, frame_index, pattern_index, image = client.get_virtual_image_buffer(
buf_id, virtual_image_info=info
status, frame_index, pattern_index, image = (
client.get_virtual_image_buffer(buf_id, virtual_image_info=info)
)
print(
f"buf_id={buf_id} frame={frame_index} pattern={pattern_index} status={status} image shape: {image.shape if image is not None else None}"
Expand Down Expand Up @@ -164,8 +164,8 @@ def test_streaming_all_virtual_buffers_one_off(self, client):

while not finished:
for buf_id in range(NUM_VIRTUAL_BUFFERS):
status, frame_index, pattern_index, image = client.get_virtual_image_buffer(
buf_id, virtual_image_info=info
status, frame_index, pattern_index, image = (
client.get_virtual_image_buffer(buf_id, virtual_image_info=info)
)
print(
f"buf_id={buf_id} frame={frame_index} pattern={pattern_index} status={status} image shape: {image.shape if image is not None else None}"
Expand Down Expand Up @@ -253,8 +253,10 @@ def test_streaming_multiple_xy_arrays(self, client):
for buf_id in range(NUM_VIRTUAL_BUFFERS):
got_frame = False
while not got_frame:
status, frame_index, pattern_index, image = client.get_virtual_image_buffer(
buf_id, virtual_image_info=info, timeout_msec=1000 # 1 sec
status, frame_index, pattern_index, image = (
client.get_virtual_image_buffer(
buf_id, virtual_image_info=info, timeout_msec=1000 # 1 sec
)
)
if status == MovieBufferStatus.OK:
received_frames.append(
Expand Down
5 changes: 3 additions & 2 deletions deapi/version.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
version = "5.3.0"
versionInfo = list(map(int, version.split(".")))
commandVersion = 16
commandVersion = 17

# Maps each commandVersion to a representative server software version string.
# Used by FakeServer so its reported "Server Software Version" always matches
# the commandVersion used for dispatch, making tests version-agnostic.
_command_version_to_server_version = {
17: "2.8.3.12436",
16: "2.8.0.12073",
15: "2.7.5.1000",
13: "2.7.4.10590",
Expand All @@ -18,5 +19,5 @@

# The server version string that corresponds to the current commandVersion.
fake_server_software_version = _command_version_to_server_version.get(
commandVersion, "2.8.0.12073"
commandVersion, "2.8.3.12436"
)
Loading