dettube 0.1.0__tar.gz

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.
dettube-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thirakorn Mokkawes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
dettube-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,388 @@
1
+ Metadata-Version: 2.4
2
+ Name: dettube
3
+ Version: 0.1.0
4
+ Summary: Quick-look tools for detonation-tube shots recorded as LabVIEW TDMS
5
+ Author: Thirakorn Mokkawes
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/the-s-lab/dettube
8
+ Project-URL: Source, https://github.com/the-s-lab/dettube
9
+ Project-URL: Issues, https://github.com/the-s-lab/dettube/issues
10
+ Keywords: detonation,TDMS,LabVIEW,combustion,shock-tube,deflagration
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Physics
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: npTDMS>=1.6
23
+ Requires-Dist: numpy>=1.22
24
+ Requires-Dist: matplotlib>=3.5
25
+ Requires-Dist: tomli>=1.1; python_version < "3.11"
26
+ Provides-Extra: gui
27
+ Dynamic: license-file
28
+
29
+ # dettube — detonation-tube quick-look tools
30
+
31
+ Reads a detonation- or shock-tube shot recorded as LabVIEW TDMS, plots every
32
+ channel, picks the pressure and flame fronts, and exports both to CSV.
33
+
34
+ Nothing about any particular tube is built in. A **rig file** says where the
35
+ gauges are and what thresholds suit them, so the same package serves any tube
36
+ whose DAQ writes TDMS.
37
+
38
+ ## Install
39
+
40
+ ```
41
+ pip install -e . # from this folder, for development
42
+ pip install . # or a plain install
43
+ ```
44
+
45
+ Needs Python 3.9+. Dependencies (npTDMS, numpy, matplotlib) come with it. The
46
+ folder picker uses tkinter, which ships with most Python builds; on some Linux
47
+ distributions it is a separate OS package (`apt install python3-tk`).
48
+
49
+ ## First run
50
+
51
+ ```
52
+ dettube rig --template myrig.toml # write a documented example
53
+ $EDITOR myrig.toml # station positions, channel numbers, thresholds
54
+ export DETTUBE_RIG=$PWD/myrig.toml # set once, or pass --rig every time
55
+ dettube rig # check what got loaded
56
+ ```
57
+
58
+ The rig file is searched for in this order: `--rig PATH`, `$DETTUBE_RIG`,
59
+ `./rig.toml`, `~/.config/dettube/rig.toml`. Without one, every command stops and
60
+ says so rather than guessing — a wrong station position quietly corrupts every
61
+ velocity downstream of it, so guessing is the one thing this must not do.
62
+
63
+ ## Commands
64
+
65
+ ```
66
+ dettube rig [--template FILE]
67
+ dettube plot <shot> [--sensor pt|pdt] [--per-station] [--show]
68
+ dettube velocity <shot> [--sensor pt|pdt] [--show]
69
+ dettube conditions <shot> [--csv FILE]
70
+ dettube export <shot> [--raw] [--full] [--every N]
71
+ ```
72
+
73
+ All of them take `--rig FILE`.
74
+
75
+ Omit `<shot>` to get a folder picker. Each subcommand is also installed on its
76
+ own as `dettube-plot`, `dettube-velocity`, `dettube-conditions` and
77
+ `dettube-export`.
78
+
79
+ | Command | Writes |
80
+ |---|---|
81
+ | `dettube plot <shot>` | `PT_stack_<shot>.png` |
82
+ | `dettube plot <shot> --sensor pdt` | `PDT_stack_<shot>.png` |
83
+ | `dettube plot <shot> --per-station` | six `station_<name>_<x>m_<shot>.png` |
84
+ | `dettube velocity <shot>` | `velocity_PT_<shot>.png` |
85
+ | `dettube velocity <shot> --sensor pdt` | `velocity_PDT_<shot>.png` |
86
+ | `dettube conditions <shot>` | a table on the terminal; `--csv` writes `<shot>_conditions.csv` and `<shot>_events.csv` |
87
+ | `dettube export <shot> --raw` | six CSVs in `<shot>/csv/` |
88
+
89
+ ## Layout
90
+
91
+ ```
92
+ dettube/
93
+ rig.py rig-file format, search path, template
94
+ core.py TDMS reading, ignitor-spike detection, the loaded rig
95
+ analysis.py front picking, segment velocities, causality and sanity checks
96
+ conditions.py pre-ignition readings from the slow process instruments
97
+ plots.py figures
98
+ export.py CSV
99
+ cli.py command line
100
+ ```
101
+
102
+ Everything that describes a tube lives in the rig file and nowhere else. That
103
+ separation is not tidiness: the station positions in the project this grew out of
104
+ were wrong for months because the same numbers were copied into four source files
105
+ and drifted apart. One file, loaded at run time, cannot drift.
106
+
107
+ ## What the plots show
108
+
109
+ ### What it draws
110
+
111
+ Every PT channel at **true amplitude**, offset vertically by its axial position,
112
+ with both channels of each station overlaid. Nothing is normalised, so a station
113
+ reading four times its neighbour looks four times bigger.
114
+
115
+ Colour is by **side of the tube**, not by station:
116
+
117
+ | | Channels | Orientation |
118
+ |---|---|---|
119
+ | **green** | PT-01, 03, 05, 07, 13, 15 (odd) | 45° |
120
+ | **blue** | PT-02, 04, 06, 08, 14, 16 (even) | 225° |
121
+
122
+ The two gauges at a station sit diametrically opposite each other, so in a
123
+ healthy shot green and blue lie on top of one another at every station. Colouring
124
+ by side rather than by station means a fault affecting one whole side of the tube
125
+ shows up as a pattern running down the plot, instead of looking like one odd
126
+ station. Green and blue were checked for colour-vision safety (ΔE 18.0 deutan /
127
+ protan, 18.7 normal vision).
128
+
129
+ Station names, positions and channel numbers all come from the rig file — run
130
+ `dettube rig` to see the ones in force.
131
+
132
+ Two things worth getting right there, because both have caught people out:
133
+
134
+ * **What `x = 0` means.** If the ignitor is not at the closed end, distances from
135
+ the datum are not distances from the ignitor. Velocities are unaffected, since
136
+ they come from differences between stations, but any single distance is.
137
+ * **Channel numbers need not be contiguous.** If a section of tube is removed,
138
+ its channels simply do not appear in the rig file, and the numbering jumps.
139
+
140
+ ### `<< PAIR DISAGREES`
141
+
142
+ Printed in red beside any station whose two gauges differ by more than 2×. Two
143
+ transducers 150 mm apart on one flange ring should read within a few tens of
144
+ percent of each other, so a flag means one of them is not measuring gas
145
+ pressure. It is worth taking seriously. On the rig this was built against, one gauge
146
+ tripped it on every shot — reading 4× to 11× its partner, and timing the front
147
+ 1–2 ms early, which no amount of filtering removed. A median filter heavy enough
148
+ to bring its amplitude down also destroyed the rise it was supposed to be
149
+ measuring. The flag is what found it.
150
+
151
+ ## `dettube_plot.py --per-station` — one file per station, pressure and light together
152
+
153
+ ```
154
+ dettube plot "D:\shots\03.100926" --per-station
155
+ ```
156
+
157
+ Writes **six PNGs**, one per station — `station_B2_3.50m_<shot>.png` and so on.
158
+ Each has the station's pressure pair on the upper panel and its photodiode pair
159
+ on the lower one, sharing a time axis, so you can see what the pressure and the
160
+ light did at the SAME place.
161
+
162
+ Two stacked panels, not two y-axes on one panel: barg and volts share no scale,
163
+ and overlaying them invites reading a crossing as meaningful when it is only an
164
+ artefact of how the two were scaled.
165
+
166
+ This is the view that shows the physics directly. At a healthy mid-tube station
167
+ you see the gentle precursor compression arrive first, then several milliseconds
168
+ later the light jump as the flame itself reaches the viewport, with a local
169
+ pressure spike alongside it. The separation between those two is the thing the
170
+ whole campaign is about.
171
+
172
+ ## Velocity
173
+
174
+ ```
175
+ dettube velocity "D:\shots\03.100926" --sensor pdt
176
+ ```
177
+
178
+ Writes `velocity_<folder>.png` and prints an arrival table and a velocity table.
179
+
180
+ ### How the front is picked
181
+
182
+ `t = 0` is the ignitor's electrical spike. The first **1.5 ms after it are
183
+ blanked**, because the spike lands on every channel at once and would otherwise
184
+ be picked as the front. The arrival is then the first crossing of
185
+ max(0.30 barg, 10σ) that **stays** above for 0.1 ms, so one noisy sample cannot
186
+ trigger it.
187
+
188
+ Where a station's two gauges differ in peak amplitude by more than 2×, the
189
+ **louder one is dropped** and the quieter kept, and the choice is printed:
190
+
191
+ ```
192
+ ! A4 (6.00 m): PT-14 reads 28.6 barg against PT-13's 4.0 — dropped, using PT-13
193
+ ```
194
+
195
+ ## Photodiodes — `--sensor pdt`
196
+
197
+ Both tools take `--sensor pdt`. The photodiode path differs in ways that matter:
198
+
199
+ - **The clocks are different.** Pressure and photodiodes are separate DAQ tasks
200
+ with separate start times; the skew has been as large as 84 ms. The ignitor
201
+ spike is always found in the PRESSURE record, then converted to the photodiode
202
+ clock using each file's own `wf_start_time`. Both files must be present.
203
+ - **No pair-disagreement test.** The rig carries a MIXED population of
204
+ photodiodes, so the two channels of a station are often different sensor types
205
+ and are expected to differ. Instead each channel is judged on its own: it must
206
+ produce at least 0.05 V and hold it for at least 2 ms at half height. That
207
+ separates a real flame glow, which is tens of milliseconds wide, from the
208
+ ignitor's sub-millisecond EMI spike. Channels are marked `usable` or `--`.
209
+ - **The arrival is the PEAK of the glow, not its leading edge.** Some units carry
210
+ a baseline 10–40 % of their own signal, so an edge threshold lands inside the
211
+ noise. The peak is self-consistent between stations, which is what a velocity
212
+ needs, but it times the brightest part of the burning zone rather than its
213
+ front — so **read flame speeds as good to about ±30 %**, not to the two
214
+ figures the fit prints.
215
+ - **Causality filtering.** Stations whose arrival breaks the order of the others
216
+ are dropped and named, keeping the largest self-consistent set. Where several
217
+ sets are the same size, the tie goes to the one with the most light — the
218
+ station that saw the brightest glow is the one most likely to be timing the
219
+ flame. Signal-to-noise is deliberately NOT used for this: a quiet photodiode
220
+ that barely sees the flame scores well on it.
221
+
222
+ ### UNRELIABLE warning
223
+
224
+ The flame path refuses to look confident when it should not be. It says
225
+ UNRELIABLE, on the plot and in the terminal, if either:
226
+
227
+ - **two or more of the six stations had to be dropped** to get a causally
228
+ ordered set, or
229
+ - **any segment exceeds 1000 m/s** — the sound speed of the combustion products,
230
+ and the last step before DDT. In a tube that has produced 100–400 m/s flames,
231
+ a segment above that is far likelier to be a mis-picked glow than a real event.
232
+
233
+ A high R² alongside these warnings means the wrong points fit a line well. It is
234
+ not reassurance.
235
+
236
+ ### CAUSALITY WARNING
237
+
238
+ If the fitted arrivals put the front at a downstream station *before* an
239
+ upstream one, the script says so on the plot and in the terminal. That is
240
+ physically impossible, so it means a pick failed — go back to
241
+ `dettube plot` and look at the raw traces before believing any velocity.
242
+
243
+ ## Conditions
244
+
245
+ ```
246
+ dettube conditions "D:\shots\03.100926"
247
+ ```
248
+
249
+ What the process instruments read before the shot, which is not always what the
250
+ folder is named. In the set this was built against, a folder called `50%H2`
251
+ holds a shot whose analyser read 48.4%. Folder names record the target; the
252
+ instrument records the gas.
253
+
254
+ Each channel gets its pre-ignition median, a median absolute deviation, and the
255
+ full range of the same window. The median and MAD are used deliberately: these
256
+ channels carry impulsive noise — a flow transmitter on a closed line throwing
257
+ single-sample spikes to 9 L/min about 0.4% of the time — and a mean and standard
258
+ deviation over that describe the spikes rather than the reading. The range is
259
+ printed beside the spread so the spikes stay visible instead of being averaged
260
+ out of sight.
261
+
262
+ Three things are flagged.
263
+
264
+ **Drift** compares the last second before ignition with the second before that.
265
+ Near zero means filling had finished and the mixture had settled, so the single
266
+ number means something. Large means it had not, and the number is a snapshot of
267
+ something still moving.
268
+
269
+ **A sustained change after ignition** — a run entirely outside the pre-ignition
270
+ envelope, not an isolated spike, since these channels spike just as often before
271
+ ignition as after. A sustained response arriving in under a millisecond is
272
+ flagged `TOO FAST`: no analyser or sheathed thermocouple responds that quickly,
273
+ so it is the ignitor's electrical pickup or a wiring fault, and only the
274
+ channel's pre-ignition value can be used.
275
+
276
+ **A channel pinned at one end of its span** is marked `*` — or `!` if it is not
277
+ reading at all. The two are told apart by counting distinct values: an input
278
+ still being digitised dithers over hundreds of codes even when the reading it
279
+ carries is a true zero, whereas a channel nobody is sampling repeats one value
280
+ exactly. What this does *not* establish is whether the instrument behind a live
281
+ input is powered and spanned; a transmitter parked at the bottom of its output
282
+ range looks identical to one correctly reporting zero. Only a span check, or a
283
+ shot that actually contains the species, settles that.
284
+
285
+ Add `on` and `note` to a `[[conditions.channel]]` entry in the rig file to carry
286
+ what you know about an instrument into every report. Where it is mounted is
287
+ worth writing down: an analyser on a supply header measures the supply, and that
288
+ equals the tube only once the tube has been purged to equilibrium.
289
+
290
+ ### How the shot was set up
291
+
292
+ If the control system writes a log beside the data, point the rig file at it
293
+ with `[event_log]` and `dettube conditions` reads it too. It is worth doing:
294
+ which valves were open, and how long a spray had been running before ignition,
295
+ are recorded there and **nowhere in the TDMS**. Without it the only surviving
296
+ record of a shot's configuration is its folder name, which states what was
297
+ intended rather than what happened.
298
+
299
+ ```
300
+ How the shot was set up — Ignition Report20260910_110457.csv
301
+ ignition logged at 11:04:58.876
302
+ shut water curtain SV01 (centre of A1, 1.00 m)
303
+ OPEN water curtain SV04 (centre of B3, 4.50 m)
304
+ mist valve opened at 11:04:53.358 5.52 s before ignition
305
+ ```
306
+
307
+ The log's own ignition timestamp is checked against the ignitor spike measured
308
+ in the pressure record, because the two are not the same moment and the gap is
309
+ not small. On the rig this was written for they sit **exactly 8 hours apart** —
310
+ the control system logs local time, the DAQ logs UTC, and nothing in either file
311
+ says so. Anything correlating the two without allowing for it is out by 8 hours
312
+ silently. Past that whole-hour part the logged time still runs 0.9 to 5.3 s early
313
+ from shot to shot, which is why the spike and not the timestamp is t = 0.
314
+
315
+ `glob` accepts a list, so a rig can keep matching an older filename alongside the
316
+ current one and old shots stay readable.
317
+
318
+ ## CSV export
319
+
320
+ ```
321
+ dettube export "D:\shots\03.100926" --raw
322
+ ```
323
+
324
+ Writes into a `csv/` folder beside the data. Results come from the same picking code the plots use — there is no second copy.
325
+
326
+ | File | Contents |
327
+ |---|---|
328
+ | `<shot>_arrivals.csv` | one row per station per sensor: channel used, arrival time, peak, `used`/`dropped`, plus every rejection note as its own row |
329
+ | `<shot>_velocities.csv` | one row per segment per sensor: length, transit time, velocity, fit, R², and any warning |
330
+ | `<shot>_raw_PT.csv` etc. | raw samples, one file per DAQ group |
331
+
332
+ Every raw file carries a `t_ms` column measured **from the ignitor spike**, so
333
+ the four groups line up with one another. They do not line up by themselves: PT,
334
+ PDT, Mixed and Pitot are separate DAQ tasks and the start-time offsets between
335
+ them run from −23 ms to +68 ms across the shots checked.
336
+
337
+ **On size.** The photodiode record is 16 channels at 100 kHz — written whole
338
+ that is ~700 000 rows and well over 100 MB of text. The default is the window
339
+ −20 to +120 ms around the spike, about 1.4 MB. `--full` writes everything and
340
+ `--every N` decimates. Sizes are printed as files are written.
341
+
342
+ ### What it will tell you it could not do
343
+
344
+ Rather than write a silently wrong file, the export skips a group and says why:
345
+
346
+ ```
347
+ Pitot: the ignitor spike falls at 2531 ms but this record is only 1800 ms long — skipped
348
+ Pitot: 'Pitot Tube Outputs' in Pitot Output20260828_104336.tdms has no sampled data — skipped
349
+ ```
350
+
351
+ Both of those are real, from the September shots: **the Pitot data is unusable
352
+ in two of the four** — empty in one, and stopped recording before the shot in
353
+ another. Worth knowing before anyone plans an analysis around it.
354
+
355
+ It also reports when channels in one group have different lengths and it has had
356
+ to trim to the shortest, naming them. That is normal on this rig — the last pair
357
+ usually runs a few thousand samples short — but a group where ten of sixteen
358
+ channels are short is worth a look.
359
+
360
+ ## Two things the tools handle for you
361
+
362
+ **The TDMS files will not open as they are.** The VI pre-allocates each file and
363
+ does not trim it on close, so a few hundred bytes of zero padding sit past the
364
+ last segment; npTDMS stops with `Segment does not start with b'TDSm'`, and the
365
+ rig's own `.tdms.log` files record the same thing as
366
+ `TdsErrNotTdsFile(-2503)`. The reader walks the segment chain and reads up to
367
+ the last complete segment. Nothing is lost. *Worth fixing in the VI so nobody
368
+ has to work around it.*
369
+
370
+ **`Ignition Time` in the CSV is not the moment of ignition.** On the shots
371
+ checked it was 0.5–1.4 s early. The script instead uses the ignitor's own
372
+ electrical spike, which lands on every PT channel simultaneously and is the best
373
+ time reference in the recording. It is reported in the plot title so you can see
374
+ where in the record the shot actually happened.
375
+
376
+ ## Known data-integrity issues to watch
377
+
378
+ - **The TDMS writer looks size-capped.** Every TDMS file is byte-identical in
379
+ size between shots, and for one August shot the Excel export carried 710 000
380
+ samples against 700 000 in the TDMS. A long shot will lose its tail with no
381
+ error. Worth checking before a campaign that matters.
382
+ - **Record lengths are inconsistent** between streams within one shot, and one
383
+ September shot recorded an empty Pitot file.
384
+ - **Water-curtain state lives only in `Ignition Report*.csv`**, not in the TDMS.
385
+ Water flow rate and supply pressure are not recorded anywhere; the nozzle is
386
+ hand-noted in the analysis workbook as "Flat Fan 40deg 005, 2 bar". Adding
387
+ flow and pressure as Mixed-Outputs channels would put them in the TDMS
388
+ automatically.