Repository navigation
Expand file tree
/
Copy pathREPORT.html
More file actions
349 lines (324 loc) · 27.4 KB
/
Copy pathREPORT.html
File metadata and controls
349 lines (324 loc) · 27.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
<title>ThorImage CS505MUP1 — Reverse-engineering dossier</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root {
--bg: #0e1419; --panel: #161d24; --panel2: #1b242d; --line: #2a3742;
--ink: #e7edf1; --ink-dim: #9fb0bd; --ink-faint: #6b7c89;
--amber: #e8a33d; --amber-soft: #f0c583; --cyan: #58b6c9; --cyan-soft: #8fd3e0;
--good: #6fcf8e; --bad: #e8775f;
--mono: ui-monospace, "SF Mono", "SFMono-Regular", Menlo, Consolas, monospace;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
--maxw: 1000px;
}
@media (prefers-color-scheme: light) {
:root {
--bg: #eef1f3; --panel: #ffffff; --panel2: #f4f7f9; --line: #d7dee3;
--ink: #19242c; --ink-dim: #4a5a66; --ink-faint: #7b8a95;
--amber: #b9741a; --amber-soft: #8a560f; --cyan: #167d92; --cyan-soft: #0f5e6e;
--good: #2e8c50; --bad: #c04a30;
}
}
:root[data-theme="dark"] {
--bg: #0e1419; --panel: #161d24; --panel2: #1b242d; --line: #2a3742;
--ink: #e7edf1; --ink-dim: #9fb0bd; --ink-faint: #6b7c89;
--amber: #e8a33d; --amber-soft: #f0c583; --cyan: #58b6c9; --cyan-soft: #8fd3e0;
--good: #6fcf8e; --bad: #e8775f;
}
:root[data-theme="light"] {
--bg: #eef1f3; --panel: #ffffff; --panel2: #f4f7f9; --line: #d7dee3;
--ink: #19242c; --ink-dim: #4a5a66; --ink-faint: #7b8a95;
--amber: #b9741a; --amber-soft: #8a560f; --cyan: #167d92; --cyan-soft: #0f5e6e;
--good: #2e8c50; --bad: #c04a30;
}
* { box-sizing: border-box; }
body {
margin: 0; background: var(--bg); color: var(--ink);
font-family: var(--sans); line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 20px 100px; }
/* Header */
header { padding: 56px 0 28px; border-bottom: 1px solid var(--line); }
.eyebrow {
font-family: var(--mono); font-size: 12px; letter-spacing: .18em;
text-transform: uppercase; color: var(--amber); margin: 0 0 14px;
}
h1 {
font-size: clamp(28px, 5vw, 44px); line-height: 1.08; margin: 0 0 14px;
font-weight: 680; letter-spacing: -.015em; text-wrap: balance;
}
h1 .sub { color: var(--ink-dim); font-weight: 500; }
.lede { color: var(--ink-dim); max-width: 64ch; margin: 0; font-size: 17px; }
/* Readout panel */
.readout {
margin: 28px 0 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
}
.cell { background: var(--panel); padding: 14px 16px; }
.cell .k { font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint); }
.cell .v { font-family: var(--mono); font-size: 18px; color: var(--ink); margin-top: 4px; font-variant-numeric: tabular-nums; }
.cell .v.accent { color: var(--amber); }
/* Sections */
section { padding: 48px 0 0; }
h2 {
font-size: 13px; font-family: var(--mono); letter-spacing: .16em; text-transform: uppercase;
color: var(--amber); margin: 0 0 6px; display: flex; align-items: baseline; gap: 12px;
}
h2 .n { color: var(--ink-faint); font-weight: 400; }
.h2sub { font-size: 24px; font-weight: 640; letter-spacing: -.01em; margin: 0 0 20px; text-wrap: balance; }
h3 { font-size: 16px; margin: 28px 0 10px; font-weight: 640; }
p { margin: 0 0 14px; }
p.note { color: var(--ink-dim); font-size: 15px; }
a { color: var(--cyan); text-underline-offset: 3px; }
/* Stack diagram */
.stack { display: flex; flex-direction: column; gap: 0; border-radius: 10px; overflow: hidden; border: 1px solid var(--line); }
.layer {
background: var(--panel); padding: 14px 18px; border-bottom: 1px solid var(--line);
display: flex; gap: 16px; align-items: baseline; flex-wrap: wrap;
}
.layer:last-child { border-bottom: 0; }
.layer .tag { font-family: var(--mono); font-size: 11px; padding: 2px 8px; border-radius: 20px; white-space: nowrap; }
.tag.managed { color: var(--cyan); border: 1px solid color-mix(in srgb, var(--cyan) 45%, transparent); }
.tag.native { color: var(--amber); border: 1px solid color-mix(in srgb, var(--amber) 45%, transparent); }
.layer .name { font-family: var(--mono); font-size: 14px; color: var(--ink); font-weight: 600; }
.layer .desc { color: var(--ink-dim); font-size: 14px; }
.layer.hot { background: color-mix(in srgb, var(--amber) 8%, var(--panel)); }
/* Tables */
.scroll { overflow-x: auto; border: 1px solid var(--line); border-radius: 10px; }
table { border-collapse: collapse; width: 100%; font-size: 14px; }
th, td { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--line); white-space: nowrap; }
thead th { background: var(--panel2); font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-faint); font-weight: 600; }
tbody tr:last-child td { border-bottom: 0; }
td.mono, th.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
td .ep { color: var(--amber); font-weight: 600; }
td .epin { color: var(--cyan); font-weight: 600; }
tbody tr { background: var(--panel); }
/* Code / transcript */
pre {
margin: 0; background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
padding: 16px 18px; overflow-x: auto; font-family: var(--mono); font-size: 13px; line-height: 1.7; color: var(--ink);
}
pre.transcript .out { color: var(--amber); }
pre.transcript .in { color: var(--cyan); }
pre.transcript .ev { color: var(--ink-faint); }
code.inline { font-family: var(--mono); font-size: .88em; background: var(--panel2); padding: 1px 6px; border-radius: 5px; color: var(--amber-soft); }
/* command chips */
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 0; }
.chip { font-family: var(--mono); font-size: 12.5px; padding: 3px 9px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); color: var(--ink-dim); }
.chip.key { color: var(--amber); border-color: color-mix(in srgb, var(--amber) 40%, transparent); }
/* two-col */
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
@media (max-width: 720px) { .cols { grid-template-columns: 1fr; } }
figure { margin: 0; }
figure img { width: 100%; max-width: 420px; border-radius: 10px; border: 1px solid var(--line); display: block; filter: contrast(1.02); }
figcaption { font-family: var(--mono); font-size: 12px; color: var(--ink-faint); margin-top: 8px; }
ul.manifest { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 2px; }
ul.manifest li { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 11px 14px; font-size: 14px; }
ul.manifest .path { font-family: var(--mono); color: var(--amber); font-size: 13px; }
ul.manifest .what { color: var(--ink-dim); display: block; margin-top: 2px; }
.callout { border: 1px solid color-mix(in srgb, var(--cyan) 35%, var(--line)); background: color-mix(in srgb, var(--cyan) 7%, var(--panel)); border-radius: 10px; padding: 16px 18px; }
.callout .lab { font-family: var(--mono); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--cyan); }
.status-ok { color: var(--good); font-weight: 600; }
.status-pending { color: var(--amber); font-weight: 600; }
footer { margin-top: 56px; padding-top: 20px; border-top: 1px solid var(--line); color: var(--ink-faint); font-size: 13px; font-family: var(--mono); }
</style>
<div class="wrap">
<header>
<p class="eyebrow">Reverse-engineering · scientific camera</p>
<h1>ThorLabs CS505MUP1 <span class="sub">— from the Windows software to Python on macOS</span></h1>
<p class="lede">Complete dossier: decompilation of ThorImageCAM (.NET), identification of the native SDK, then
capture and full decoding of the USB protocol — all the way to acquisition in pure Python, without any
ThorLabs library.</p>
<div class="readout">
<div class="cell"><div class="k">Model</div><div class="v accent">CS505MUP1</div></div>
<div class="cell"><div class="k">Serial no.</div><div class="v">18658</div></div>
<div class="cell"><div class="k">Sensor</div><div class="v">2448 × 2048</div></div>
<div class="cell"><div class="k">Depth</div><div class="v">12 bits</div></div>
<div class="cell"><div class="k">USB VID:PID</div><div class="v">1313:4001</div></div>
<div class="cell"><div class="k">Type</div><div class="v">Mono polar.</div></div>
</div>
</header>
<section>
<h2><span class="n">01</span> The software stack</h2>
<p class="h2sub">Four layers, from the Windows interface down to the silicon</p>
<p class="note">ThorImageCAM is a .NET application. By decompiling it, we descend layer by layer
until we discover that the "SDK" is only a thin wrapper on top of a text-based USB protocol.</p>
<div class="stack">
<div class="layer">
<span class="tag managed">.NET</span>
<span class="name">CameraControl.dll</span>
<span class="desc">WPF UI (Live / Snap / Capture buttons). Delegates everything to the ViewModel.</span>
</div>
<div class="layer">
<span class="tag managed">.NET</span>
<span class="name">ThorImageCAMShared.dll</span>
<span class="desc">Core: <code class="inline">ThorlabsCamera</code> + <code class="inline">CaptureService</code>. 93 P/Invoke calls into the native layer.</span>
</div>
<div class="layer hot">
<span class="tag native">native</span>
<span class="name">CameraLib.dll / thorlabs_tsi_camera_sdk.dll</span>
<span class="desc">ThorLabs TSI SDK: C API <code class="inline">SMNC_*</code> (93) & <code class="inline">tl_camera_*</code> (132).</span>
</div>
<div class="layer hot">
<span class="tag native">USB</span>
<span class="name">EP 0x02 / 0x82 / 0x83 — text protocol</span>
<span class="desc">★ The real entry point: ASCII commands over bulk endpoints. Replayable anywhere.</span>
</div>
</div>
</section>
<section>
<h2><span class="n">02</span> The USB protocol</h2>
<p class="h2sub">A self-documenting ASCII text protocol</p>
<p>The camera (Cypress FX3, vendor class <code class="inline">0xFF</code>, resident firmware) does not use
a binary protocol: it exposes a <strong>text</strong> interface over five bulk endpoints. You write an
ASCII command, you read an ASCII response, and the pixels arrive on a dedicated endpoint.</p>
<h3>Endpoints</h3>
<div class="scroll">
<table>
<thead><tr><th class="mono">EP</th><th>Dir</th><th>Type</th><th class="mono">maxpkt</th><th class="mono">burst</th><th>Role</th></tr></thead>
<tbody>
<tr><td class="mono"><span class="ep">0x02</span></td><td>OUT</td><td class="mono">BULK</td><td class="mono">1024</td><td class="mono">1</td><td>ASCII commands</td></tr>
<tr><td class="mono"><span class="epin">0x82</span></td><td>IN</td><td class="mono">BULK</td><td class="mono">1024</td><td class="mono">1</td><td>ASCII responses</td></tr>
<tr><td class="mono"><span class="epin">0x83</span></td><td>IN</td><td class="mono">BULK</td><td class="mono">1024</td><td class="mono">16</td><td>Image data (16 KB chunks)</td></tr>
<tr><td class="mono">0x01</td><td>OUT</td><td class="mono">BULK</td><td class="mono">1024</td><td class="mono">16</td><td>Secondary OUT (unused)</td></tr>
<tr><td class="mono">0x81</td><td>IN</td><td class="mono">INTR</td><td class="mono">128</td><td class="mono">—</td><td>Events / status</td></tr>
</tbody>
</table>
</div>
<h3>Framing</h3>
<p class="note">1024-byte buffer on <code class="inline">0x02</code>: ASCII command, <code class="inline">\0</code>, padding.
<code class="inline">\0</code>-terminated response on <code class="inline">0x82</code>. Errors:
<code class="inline">error <code>,<message></code>.</p>
<h3>Complete vocabulary (returned by <code class="inline">discover</code>)</h3>
<div class="chips">
<span class="chip key">exp</span><span class="chip key">gain</span><span class="chip key">roi</span>
<span class="chip key">hbin</span><span class="chip key">vbin</span><span class="chip key">nframes</span>
<span class="chip key">hwtrig</span><span class="chip key">arm</span><span class="chip key">start</span>
<span class="chip key">stop</span><span class="chip key">disarm</span>
<span class="chip">id</span><span class="chip">info</span><span class="chip">ver</span><span class="chip">ping</span>
<span class="chip">echo</span><span class="chip">model name</span><span class="chip">sernum</span><span class="chip">unlock</span>
<span class="chip">camera_radix</span><span class="chip">hot_pixel_correction</span><span class="chip">state</span>
<span class="chip">statusled</span><span class="chip">temperature</span><span class="chip">image_dims</span>
<span class="chip">image_width</span><span class="chip">image_height</span><span class="chip">pixel_bit_depth</span>
<span class="chip">pixel_size</span><span class="chip">pixel_width_nm</span><span class="chip">pixel_height_nm</span>
<span class="chip">pixel_byte_ordering</span><span class="chip">vflip</span><span class="chip">hflip</span>
<span class="chip">blklvl</span><span class="chip">imgpack</span><span class="chip">debug_radix</span><span class="chip">gpo</span>
<span class="chip">time</span><span class="chip">frametime</span><span class="chip">color</span><span class="chip">color_filter_phase</span>
<span class="chip">ir_filter_type</span><span class="chip">color_correction_matrix</span><span class="chip">output_color_space</span>
<span class="chip">default_white_balance_matrix</span><span class="chip">polar_phase</span><span class="chip">sensor_readout_time</span>
<span class="chip">tags</span><span class="chip">nvstor</span><span class="chip">sfr</span><span class="chip">pixel_clock_count_base</span>
</div>
<p class="note" style="margin-top:14px">Each command <code class="inline">X</code> has an
<code class="inline">X.disc</code> variant that returns its syntax in JSON. The camera thus documents its own API:</p>
<pre>> gain.disc
{"gain": {"desc": "applies gain factor to image",
"syntax": "gain [dB]",
"min": 0, "max": 480, "inc": 1, "default": 0,
"flags": ["w"], "response": ["integer"]}}</pre>
<h3>Acquisition sequence (observed)</h3>
<div class="cols">
<pre class="transcript"><span class="out">> id</span> <span class="in">000000f648…</span>
<span class="out">> sernum</span> <span class="in">18658</span>
<span class="out">> stop</span> <span class="in">stopped</span>
<span class="out">> discover</span> <span class="in"><cmd list></span>
<span class="out">> exp 20000</span> <span class="in">20000</span>
<span class="out">> hwtrig none</span> <span class="in">none hi</span>
<span class="out">> nframes 1</span> <span class="in">1</span>
<span class="out">> image_width</span> <span class="in">2448</span>
<span class="out">> image_height</span> <span class="in">2048</span>
<span class="out">> pixel_bit_depth</span> <span class="in">12</span>
<span class="out">> arm</span> <span class="in">armed</span>
<span class="out">> start</span> <span class="in">running</span>
<span class="ev"> ‹‹ 2448×2048×2 bytes on EP 0x83 ››</span>
<span class="out">> stop</span> <span class="in">stopped</span></pre>
<div>
<div class="callout">
<div class="lab">Pixel format</div>
<p class="note" style="margin:8px 0 0">12 significant bits in <strong>uint16 little-endian</strong>
(2 bytes/pixel). Frame = width × height × 2 = <strong>10,027,008 bytes</strong>, read in 16,384-byte chunks
on <code class="inline">0x83</code>.</p>
</div>
<p class="note" style="margin-top:16px">In <code class="inline">hwtrig none</code> (software) mode,
<code class="inline">start</code> is itself the trigger — no separate trigger command.
Since the image is a mosaic of micro-polarizers, the de-mosaicing (intensity / DoLP / azimuth) is done
host-side.</p>
</div>
</div>
</section>
<section>
<h2><span class="n">03</span> The native SDK</h2>
<p class="h2sub">Two C ABIs documented by the decompilation</p>
<p class="note">The .NET layer drives the native layer through P/Invoke. These signatures (all extracted) are directly
transposable to <code class="inline">ctypes</code>. <code class="inline">CameraLib.dll</code> (<code class="inline">SMNC_*</code>)
is an in-house wrapper; <code class="inline">thorlabs_tsi_camera_sdk.dll</code> (<code class="inline">tl_camera_*</code>)
is the official TSL SDK — the one used by the Python binding <code class="inline">thorlabs_tsi_sdk</code>.</p>
<div class="scroll">
<table>
<thead><tr><th>ABI</th><th class="mono"># functions</th><th>Groups covered</th></tr></thead>
<tbody>
<tr><td class="mono"><span class="ep">SMNC_*</span> <span style="color:var(--ink-faint)">(CameraLib.dll)</span></td><td class="mono">93</td><td>List/Open/Close · exposure & auto-exp · gain · ROI/bin · trigger/EEP · data rate · color/polar · LED · hot-pixel · cooling · TIFF/JPEG</td></tr>
<tr><td class="mono"><span class="epin">tl_camera_*</span> <span style="color:var(--ink-faint)">(TSI SDK)</span></td><td class="mono">132</td><td>open_sdk/discover/open_camera · arm/disarm · issue_software_trigger · get_pending_frame · set_exposure/roi/gain · sensor info · frame callbacks</td></tr>
<tr><td class="mono">p2d_* <span style="color:var(--ink-faint)">(p2d_lib.dll)</span></td><td class="mono">52</td><td>image buffers · color conversion · histogram · profiles · min/max · resize</td></tr>
</tbody>
</table>
</div>
<p class="note" style="margin-top:14px">Full signatures in <code class="inline">docs/CameraLib_SMNC_pinvoke.txt</code>,
<code class="inline">docs/tsi_camera_sdk_exports.txt</code>, <code class="inline">docs/p2d_lib_pinvoke.txt</code>.</p>
</section>
<section>
<h2><span class="n">04</span> Proof</h2>
<p class="h2sub">A real frame, acquired via the SDK in Python</p>
<div class="cols">
<figure>
<img src="data:image/jpeg;base64,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" alt="12-bit monochrome frame acquired from the CS505MUP1 camera">
<figcaption>exp 20 ms · min 43 / max 2532 / mean 1286 · 2448×2048</figcaption>
</figure>
<div>
<p>Lab scene captured via <code class="inline">tl_camera_*</code> in <code class="inline">ctypes</code>,
then displayed (optical setup, cables, a grating at the top right). The full chain works:
<code class="inline">open_sdk → discover → open_camera → set_exposure → arm → software_trigger → get_pending_frame</code>.</p>
<p class="note">It is this same sequence that the pyusb driver replays directly at the USB level, without
depending on any ThorLabs binary.</p>
</div>
</div>
</section>
<section>
<h2><span class="n">05</span> Status & deliverables</h2>
<p class="h2sub">What is proven, what remains</p>
<div class="scroll" style="margin-bottom:22px">
<table>
<thead><tr><th>Phase</th><th>Status</th></tr></thead>
<tbody>
<tr><td>.NET decompilation & SDK map</td><td class="status-ok">✓ done</td></tr>
<tr><td>Acquisition via TSI SDK in Python (ctypes)</td><td class="status-ok">✓ proven (real image)</td></tr>
<tr><td>USB characterization (FX3, resident firmware)</td><td class="status-ok">✓ done</td></tr>
<tr><td>Capture & decoding of the USB protocol</td><td class="status-ok">✓ 100%</td></tr>
<tr><td>macOS pyusb driver (<code class="inline">camera_usb.py</code>)</td><td class="status-ok">✓ written</td></tr>
<tr><td>Hardware test on macOS</td><td class="status-pending">⋯ to do (camera to be routed to the Mac)</td></tr>
</tbody>
</table>
</div>
<ul class="manifest">
<li><span class="path">macos/camera_usb.py</span><span class="what">Pure-Python pyusb driver — the target code</span></li>
<li><span class="path">macos/README.md</span><span class="what">Install (brew libusb, pyusb) + macOS usage</span></li>
<li><span class="path">docs/PROTOCOL.md</span><span class="what">Complete USB protocol (endpoints, commands, sequence, pixels)</span></li>
<li><span class="path">docs/SDK_MAP.md</span><span class="what">.NET architecture + native APIs (SMNC_ / tl_camera_ / p2d_)</span></li>
<li><span class="path">docs/*_pinvoke.txt · *_exports.txt</span><span class="what">Full signatures: 93 SMNC_ · 132 tl_camera_ · 52 p2d_</span></li>
<li><span class="path">captures/cam_session.pcap · transcript.txt</span><span class="what">Raw USB capture + decoded ASCII transcript</span></li>
<li><span class="path">src/ThorImageCAMShared/ …</span><span class="what">Decompiled .NET assemblies (C#)</span></li>
<li><span class="path">tools/</span><span class="what">RE scripts: capture, parsing, TSI grab, display</span></li>
</ul>
</section>
<footer>
ThorImageRE · /Users/dccote/ThorImageRE · reverse-engineering for interoperability (driving one's own hardware)
</footer>
</div>
<script>
(function () {
var root = document.documentElement;
try {
var t = localStorage.getItem('theme');
if (t) root.setAttribute('data-theme', t);
} catch (e) {}
})();
</script>