New to PLR, but happy to now finally be able to connect some of our instruments. I was able to get some responses out of the cytation 1, using the cytation 5 backend (eg. firmware, temperature, open, close, filters), but the objectives info send_commands (eg. “i”, “h2”) do not seem to respond in the same way as the cytation 5 would do. Any thoughts on that?
welcome to the forum!
i will test on our cytation1
can you share the specific response you get?
Sure. A proper response is when querying the filters:
await self.send_command(“i”, “q1”)
Returns filter part number (1225100)
But for the objectives it gives:
await self.send_command(“i”, “h2”)
Returns 2101
h1, h3, etc give the same. The objectives we have are 1220519: Objective.O_4x_PLAN_FLUORITE and 1220539: Objective.O_2p5x_PLAN_FLUORITE
Ah, the supported objectives are actually very limited:
annulus_part_number2objective = {
1320520: Objective.O_4x_PL_FL_PHASE,
1320521: Objective.O_20x_PL_FL_PHASE,
1322026: Objective.O_40x_PL_FL_PHASE,
}
could that be the issue?
note that these are annulus part numbers, not part number of the objective. The easiest way to figure out the annulus number is to query it from the firmware, the second easiest way is to go into gen5.exe and export objective information as xml, then look in that file.
That is indeed an issue, but I fixed that indeed by getting all the part numbers for the available lenses.
annulus_part_number2objective = {
# **PL Ach Objectives**
1220562: Objective.O_1p25x_PL_ACH, # 1.25x PL Ach
1220549: Objective.O_2p5x_PL_ACH, # 2.5x PL Ach
1220574: Objective.O_2x_PL_ACH, # 2x PL Ach
1320546: Objective.O_4x_PL_ACH, # 4x PL Ach
1450539: Objective.O_100x_PL_ACH_OIL, # 100x PL Ach Oil
1450538: Objective.O_60x_PL_ACH_OIL, # 60x PL Ach Oil
2210007: Objective.O_40x_PL_ACH_WATER, # 40x PL Ach Water
2210008: Objective.O_60x_PL_ACH_WATER, # 60x PL Ach Water
# **PL Extended Apoch Objectives**
1220573: Objective.O_4x_PL_EXT_APOCH,
1450569: Objective.O_40x_PL_EXT_APOCH,
1450568: Objective.O_20x_PL_EXT_APOCH,
# **PL FL Objectives**
1220519: Objective.O_4x_PL_FL,
1220517: Objective.O_20x_PL_FL,
1220539: Objective.O_2p5x_PL_FL,
1220518: Objective.O_10x_PL_FL,
1220544: Objective.O_40x_PL_FL,
1220545: Objective.O_60x_PL_FL,
# **PL FL Oil Immersion Objectives**
1450549: Objective.O_100x_PL_FL_OIL,
1450548: Objective.O_60x_PL_FL_OIL,
# **PL FL Phase Objectives (Phase Contrast)**
1320518: Objective.O_40x_PL_FL_PHASE,
1320517: Objective.O_20x_PL_FL_PHASE,
1320516: Objective.O_10x_PL_FL_PHASE,
1320515: Objective.O_4x_PL_FL_PHASE,
# **Other Objectives)**
1320520: Objective.O_4x_PL_FL_PHASE, # 4x Olympus PL FL Phase
1320521: Objective.O_20x_PL_FL_PHASE, # 20x Olympus PL FL Phase
1322026: Objective.O_40x_PL_FL_PHASE, # 40x Olympus PL FL Phase
1650200: Objective.O_UNKNOWN_NEW_OBJECTIVE, # Placeholder for newer objectives
}
From: https://www.fishersci.co.uk/webfiles/uk/web-docs/11445_Cytation%20Imaging%20Primer_FS.pdf (page 31)
I also changed the Objectives Class accordingly, but my issue is that the instruction seems to be different for querying the objective information from the Cytation 1.
That is:
The response here is 2101. Or the raw Response: b'2101\x03', Hex: 3231303103
This is likely some kind of error code, because i get it also when sending other commands.
wait, actually, these ones are objective numbers, not annulus numbers, so I’ll look them up later and correct. But the issue still stands—I first have to query the numbers using send_command(someting, something)
before I can map them to the Objective class.
when looking at the wireshark capture, i am 99% confident these are the packets that load the objective information. I haven’t figured out how to decode them to part number / objective identifier. Also, in the gen5.exe xml exports of the objectives we have in the cytation1 (4x pl fl and 20x pl fl), there is no annulus part number. However, when i export 20x fl pl phase from this same gen5.exe installation, i do see the annulus part number.
> b'i'
< b'\x06'
> b'o1'
< b'040000013047445745485351 53484438'
< b'4446002056 0000\x03'
> b'i'
< b'\x06'
> b'o2'
< b'200000045047445745485351 445335484'
< b'438444600181656 0000\x03'
the middle part decodes to
# first (4x):
>>> [chr(x) for x in bytes.fromhex("534844384446002056")]
['S', 'H', 'D', '8', 'D', 'F', '\x00', ' ', 'V']
# second (20x):
>>> [chr(x) for x in bytes.fromhex("445335484438444600181656")]
['D', 'S', '5', 'H', 'D', '8', 'D', 'F', '\x00', '\x18', '\x16', 'V']
doesn’t ring a bell for me
ah, in the first part:
040000013047445745485351
200000045047445745485351
^^ numerical aperature
^^^^^ ?
^^ magnification
rest could decode to
>>> [chr(x) for x in bytes.fromhex("47445745485351")]
['G', 'D', 'W', 'E', 'H', 'S', 'Q']
but unsure
putting part number (from xml file) above middle byte:
# 4x
UPLFLN 4x
SHD8DF
# 20x
LUCPLFLN 20x
DS5HD8DF
it’s not clear why, but it’s consistent. wonder if it’s the same on your setup
(send_command("i", "o1")
)
this is great!
We have the same objective in slot1:
# response ("i", "o1"):
Hex:
30343030303030313330343734343537343534383533353120202020202020202020202020202020202035333438343433383434343630303230353620202020202020202020202020203030303003
Decoded:
040000013047445745485351
534844384446002056
0000
But different objective in slot2 (i can also check two more lenses i think):
# response ("i", "o2"):
Hex:
30323734303030313230353833373431353135312020202020202020202020202020202020202020202033383434353333333530303031383134323135363135313631343137313820203030303003
Decoded:
02740001205837415151
384453335000181421561516141718
0000
We have the Zeiss 2.5x lens in slot2 (#1220539), so:
02740001205837415151
0274 = magnifcation (2.74x)
0120 = numerical aperture (0.12)
the middle hex part gives:
384453335000181421561516141718
8DS3P\x00\x18\x14!V\x15\x16\x14\x17\x18
I have to check the xml files to see the code for that, maybe it is zeiss specific. The 2.5x lens is also a planar fluorite objective, but does not have the HD8DF code apparently
the code HD8DF decodes to
FLU??
8DS3P
based on the above translation table.
The part number in the xml file is actually ‘fluar’, which matches!
there is no obvious mapping, but here’s what we know so far:
8 -> F
D -> L
S -> U
3 -> A
P -> R
S -> U
H -> P
5 -> C
F -> N
???
actually also looking at the non-alphanumeric bytes:
FLUAR 2 . 5 x / 0 . 1 2
8DS3P \x00 \x18 \x14 ! V \x15 \x16 \x14 \x17 \x18
UPLFLN 4 x
SHD8DF \x00 \x32 V
LUCPLFLN 2 0 x ? ?
DS5HD8DF \x00 \x18 \x16 V H @
there is no clear numbering (0x15 = 1, 0x16 = 2 gives that impression), but
>>> ord("S") - ord("H")
11
>>> ord("U") - ord("P")
5
actually, decoding the alphanumeric chars to hex (using normal ascii, we decoded to ascii before) it becomes clear:
0x00 -> space
0x21 -> 5
0x56 -> X
0x14 -> .
0x15 -> /
0x16 -> 0
0x17 -> 1
0x18 -> 2
0x33 -> A
0x35 -> C
0x38 -> F
0x44 -> L
0x50 -> R
0x53 -> U
0x46 -> N
0x48 -> P
inferring the rest of the encoding structure:
\x19 -> 3 # know
\x20 -> 4 # know
\x21 -> 5 # know 5 = 0x1, break the hex pattern, they are bytes but only 0-9 are used
\x22 -> 6
\x23 -> 7
\x24 -> 8
\x25 -> 9
\x33 -> A # know
\x34 -> B
\x35 -> C # know
\x36 -> D
\x37 -> E
\x38 -> F
\x39 -> G
\x40 -> H
\x41 -> I
\x42 -> J
\x43 -> K
\x44 -> L # know
\x45 -> M
\x46 -> N
\x47 -> O
\x48 -> P
\x49 -> Q
\x50 -> R
\x51 -> S
\x52 -> T
\x53 -> U
\x54 -> V
\x55 -> W
\x56 -> X
\x57 -> Y
\x58 -> Z
special chars are a little harder, but there are only so many
this is all objectives we have:
UPLSAPO 40X2: 40X PL APO
LUCPLFLN 60X: 60X PL FL
UPLFLN 4X: 4X PL FL
LUCPLFLN 20XPh: 20X PL FL Phase
LUCPLFLN 40XPh: 40X PL FL Phase
U Plan: 2.5X PL ACH Meiji
UPLFLN 10XPh: 10X PL FL Phase
PLAPON 1.25X: 1.25X PL APO
UPLFLN 10X: 10X PL FL
UPLFLN 60XOI: 60X OIL PL FL
PLN 4X: 4X PL ACH
PLN 40X: 40X PL ACH
LUCPLFLN 40X: 40X PL FL
EC-H-Plan/2x: 2X PL ACH Motic
UPLFLN 100XO2: 100X OIL PL FL
UPLFLN 4XPh: 4X PL FL Phase
LUCPLFLN 20X: 20X PL FL
PLN 20X: 20X PL ACH
Fluar 2.5x/0.12: 2.5X FL Zeiss
UPLSAPO 100XO: 100X OIL PL APO
PLAPON 60XO: 60X OIL PL APO
UPLSAPO 20X: 20X PL APO
here’s a branch i just whipped together cytation1 objective loading by rickwierenga · Pull Request #432 · PyLabRobot/pylabrobot · GitHub (haven’t tested on actual machine, will do tmrw when back in lab)
yes this is solved!
Only the zeiss objective name should be capitols:
"FLUAR 2.5X/0.12": Objective.O_2_5X_FL_Zeiss,
instead of
"Fluar 2.5x/0.12"
now will continue capturing images. Have some issues there but need to figure out where it goes wrong.
great! i confirmed it here and merged the branch. if another issue comes up please make a new thread