atspm-detector 1.0.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.
Files changed (83) hide show
  1. atspm_detector-1.0.0/LICENSE +21 -0
  2. atspm_detector-1.0.0/PKG-INFO +186 -0
  3. atspm_detector-1.0.0/README.md +149 -0
  4. atspm_detector-1.0.0/pyproject.toml +69 -0
  5. atspm_detector-1.0.0/setup.cfg +4 -0
  6. atspm_detector-1.0.0/src/atspm_detector/__init__.py +20 -0
  7. atspm_detector-1.0.0/src/atspm_detector/__main__.py +4 -0
  8. atspm_detector-1.0.0/src/atspm_detector/atspm_decode.py +368 -0
  9. atspm_detector-1.0.0/src/atspm_detector/atspm_export.py +218 -0
  10. atspm_detector-1.0.0/src/atspm_detector/blend.py +74 -0
  11. atspm_detector-1.0.0/src/atspm_detector/charts.py +127 -0
  12. atspm_detector-1.0.0/src/atspm_detector/check.py +591 -0
  13. atspm_detector-1.0.0/src/atspm_detector/common.py +46 -0
  14. atspm_detector-1.0.0/src/atspm_detector/data/sample_events.parquet +0 -0
  15. atspm_detector-1.0.0/src/atspm_detector/decode.py +122 -0
  16. atspm_detector-1.0.0/src/atspm_detector/features.py +508 -0
  17. atspm_detector-1.0.0/src/atspm_detector/features_expert.py +291 -0
  18. atspm_detector-1.0.0/src/atspm_detector/features_lag.py +91 -0
  19. atspm_detector-1.0.0/src/atspm_detector/features_partner.py +262 -0
  20. atspm_detector-1.0.0/src/atspm_detector/funcnet.py +223 -0
  21. atspm_detector-1.0.0/src/atspm_detector/function.py +117 -0
  22. atspm_detector-1.0.0/src/atspm_detector/function_stage.py +214 -0
  23. atspm_detector-1.0.0/src/atspm_detector/health.py +69 -0
  24. atspm_detector-1.0.0/src/atspm_detector/health_core.py +1860 -0
  25. atspm_detector-1.0.0/src/atspm_detector/health_v4.py +1095 -0
  26. atspm_detector-1.0.0/src/atspm_detector/health_v4_stats.py +1252 -0
  27. atspm_detector-1.0.0/src/atspm_detector/lanes.py +586 -0
  28. atspm_detector-1.0.0/src/atspm_detector/night_speed.py +186 -0
  29. atspm_detector-1.0.0/src/atspm_detector/pick.py +186 -0
  30. atspm_detector-1.0.0/src/atspm_detector/pipeline.py +1233 -0
  31. atspm_detector-1.0.0/src/atspm_detector/reference/funcnet_reference.parquet +0 -0
  32. atspm_detector-1.0.0/src/atspm_detector/reference/phase_net_reference.parquet +0 -0
  33. atspm_detector-1.0.0/src/atspm_detector/reference/phase_reference.parquet +0 -0
  34. atspm_detector-1.0.0/src/atspm_detector/reference/predict_reference.parquet +0 -0
  35. atspm_detector-1.0.0/src/atspm_detector/reference/predict_reference_m10.parquet +0 -0
  36. atspm_detector-1.0.0/src/atspm_detector/setback.py +566 -0
  37. atspm_detector-1.0.0/src/atspm_detector/similarity.py +190 -0
  38. atspm_detector-1.0.0/src/atspm_detector/stacker.py +127 -0
  39. atspm_detector-1.0.0/src/atspm_detector/streams.py +254 -0
  40. atspm_detector-1.0.0/src/atspm_detector/trees_onnx.py +105 -0
  41. atspm_detector-1.0.0/src/atspm_detector/weights/blend.json +17 -0
  42. atspm_detector-1.0.0/src/atspm_detector/weights/decode_v3.json +53 -0
  43. atspm_detector-1.0.0/src/atspm_detector/weights/decode_v3.onnx +0 -0
  44. atspm_detector-1.0.0/src/atspm_detector/weights/funcnet/manifest.json +58 -0
  45. atspm_detector-1.0.0/src/atspm_detector/weights/funcnet/x100_w32full_head.onnx +0 -0
  46. atspm_detector-1.0.0/src/atspm_detector/weights/funcnet/x100_w32full_pair.onnx +0 -0
  47. atspm_detector-1.0.0/src/atspm_detector/weights/funcnet/x100_w32full_s1_head.onnx +0 -0
  48. atspm_detector-1.0.0/src/atspm_detector/weights/funcnet/x100_w32full_s1_pair.onnx +0 -0
  49. atspm_detector-1.0.0/src/atspm_detector/weights/funcnet/x100_w32full_s2_head.onnx +0 -0
  50. atspm_detector-1.0.0/src/atspm_detector/weights/funcnet/x100_w32full_s2_pair.onnx +0 -0
  51. atspm_detector-1.0.0/src/atspm_detector/weights/function/function229.json +278 -0
  52. atspm_detector-1.0.0/src/atspm_detector/weights/function/function229_s0.onnx +0 -0
  53. atspm_detector-1.0.0/src/atspm_detector/weights/health/health_v4_refs.json +1 -0
  54. atspm_detector-1.0.0/src/atspm_detector/weights/lanes/lane_model.json +837 -0
  55. atspm_detector-1.0.0/src/atspm_detector/weights/lanes/lane_pair_D_s0.onnx +0 -0
  56. atspm_detector-1.0.0/src/atspm_detector/weights/lanes/lane_pair_D_s1.onnx +0 -0
  57. atspm_detector-1.0.0/src/atspm_detector/weights/lanes/lane_pair_D_s2.onnx +0 -0
  58. atspm_detector-1.0.0/src/atspm_detector/weights/model_card.json +77 -0
  59. atspm_detector-1.0.0/src/atspm_detector/weights/phase_lgbm_v5.json +314 -0
  60. atspm_detector-1.0.0/src/atspm_detector/weights/phase_lgbm_v5_s0.onnx +0 -0
  61. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_full_q50_s0.onnx +0 -0
  62. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_h24_q50_s0.onnx +0 -0
  63. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_h6_q50_s0.onnx +0 -0
  64. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_m30_q50_s0.onnx +0 -0
  65. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_model.json +86 -0
  66. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_p50.json +142 -0
  67. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_pair_hgb.json +1 -0
  68. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_q10_s0.onnx +0 -0
  69. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_q10_s1.onnx +0 -0
  70. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_q10_s2.onnx +0 -0
  71. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_q90_s0.onnx +0 -0
  72. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_q90_s1.onnx +0 -0
  73. atspm_detector-1.0.0/src/atspm_detector/weights/setback/setback_q90_s2.onnx +0 -0
  74. atspm_detector-1.0.0/src/atspm_detector/weights/stacker/stacker.json +105 -0
  75. atspm_detector-1.0.0/src/atspm_detector/weights/stacker/stacker_mean3_s0.onnx +0 -0
  76. atspm_detector-1.0.0/src/atspm_detector.egg-info/PKG-INFO +186 -0
  77. atspm_detector-1.0.0/src/atspm_detector.egg-info/SOURCES.txt +81 -0
  78. atspm_detector-1.0.0/src/atspm_detector.egg-info/dependency_links.txt +1 -0
  79. atspm_detector-1.0.0/src/atspm_detector.egg-info/entry_points.txt +3 -0
  80. atspm_detector-1.0.0/src/atspm_detector.egg-info/requires.txt +11 -0
  81. atspm_detector-1.0.0/src/atspm_detector.egg-info/top_level.txt +1 -0
  82. atspm_detector-1.0.0/tests/test_atspm_export.py +242 -0
  83. atspm_detector-1.0.0/tests/test_check.py +6 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Shawn Strasser
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.
@@ -0,0 +1,186 @@
1
+ Metadata-Version: 2.4
2
+ Name: atspm-detector
3
+ Version: 1.0.0
4
+ Summary: Phase, function, lanes, setback, night speed and health of traffic-signal detectors from the controller's hi-res event log (CPU, onnxruntime).
5
+ Author: Shawn Strasser
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ShawnStrasser/atspm-detector
8
+ Project-URL: Documentation, https://github.com/ShawnStrasser/atspm-detector#readme
9
+ Project-URL: Repository, https://github.com/ShawnStrasser/atspm-detector
10
+ Project-URL: Issues, https://github.com/ShawnStrasser/atspm-detector/issues
11
+ Project-URL: Changelog, https://github.com/ShawnStrasser/atspm-detector/blob/main/CHANGELOG.md
12
+ Project-URL: Embedding Guide, https://github.com/ShawnStrasser/atspm-detector/blob/main/docs/embedding.md
13
+ Keywords: traffic signal,ATSPM,detector,high-resolution data,NTCIP
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Scientific/Engineering
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: numpy>=1.24
28
+ Requires-Dist: pandas>=2.0
29
+ Requires-Dist: duckdb>=1.0
30
+ Requires-Dist: onnxruntime>=1.21
31
+ Provides-Extra: charts
32
+ Requires-Dist: matplotlib>=3.7; extra == "charts"
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
35
+ Requires-Dist: build>=1.0; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # atspm-detector
39
+
40
+ Give it a signal controller's hi-res event log; for every vehicle detector it returns the **phase**, **function**,
41
+ **lanes**, **setback** from the stop bar and **health**. No cabinet print needed: the answers come from how each
42
+ detector behaves. Use it to **build** a signal's ATSPM detector configuration, or to **check** the one you have.
43
+
44
+ * **Tested on 115 signals it never trained on:** phase right for **98.0 %** of detectors, function **91.7 %**
45
+ (30 minutes or more of log).
46
+ * **More accurate than hand-labled configuration:** phase wrong on 1.3 % of detectors, against 2.6 % by hand.
47
+ * **Fast and light:** one second per signal on CPU.
48
+
49
+ **Contents:** [Detector functions](#detector-functions) · [Install](#install) · [Usage](#usage) · [Input](#input) ·
50
+ [How much data](#how-much-data) · [How it works](#how-it-works) · [Accuracy](#accuracy) ·
51
+ [Detector health](#detector-health) · [License](#license)
52
+
53
+ ## Detector functions
54
+
55
+ | Function | ATSPM | What it is |
56
+ |---|---|---|
57
+ | Presence | yes | Stop-bar presence zone. The presence zones used in training were about 20 ft long at the stop bar. |
58
+ | Advance | yes | Advance count zone, upstream. Single lane or spanning several lanes. |
59
+ | Count | yes | Stop-bar count zone. Usually set to pulse, one per lane. |
60
+ | Yellow_Red | yes | At the stop bar; may span several lanes. Configure it in the field with a 5 mph speed threshold. |
61
+ | Mid | no | Zone between the advance and the stop bar. A class of its own because it helps the model. |
62
+ | Bike | no | Bicycle detection zone. |
63
+ | Other | no | Everything else that drives operations but is not an ATSPM zone. |
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ pip install atspm-detector # Python 3.10 or newer
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ ```python
74
+ import pandas as pd
75
+ from atspm_detector import predict
76
+
77
+ events = pd.read_csv("events.csv") # or pd.read_parquet (needs pyarrow)
78
+ events.head()
79
+ ```
80
+
81
+ ```text
82
+ DeviceId Timestamp EventId Parameter
83
+ 0 d2d0de72-c8aa-45ca-ae1a-8fc626c5afc9 2024-12-03 12:00:00.000 150 7
84
+ 1 d2d0de72-c8aa-45ca-ae1a-8fc626c5afc9 2024-12-03 12:00:00.000 320 0
85
+ 2 d2d0de72-c8aa-45ca-ae1a-8fc626c5afc9 2024-12-03 12:00:00.000 318 3
86
+ 3 d2d0de72-c8aa-45ca-ae1a-8fc626c5afc9 2024-12-03 12:00:00.000 316 120
87
+ 4 d2d0de72-c8aa-45ca-ae1a-8fc626c5afc9 2024-12-03 12:00:00.600 13 6
88
+ ```
89
+
90
+ ```python
91
+ results = predict(events)
92
+ results[["Detector", "phase_pred", "phase_prob", "function_pred", "function_prob",
93
+ "lanes", "distance_ft", "health_status"]].round(2).head()
94
+ ```
95
+
96
+ ```text
97
+ Detector phase_pred phase_prob function_pred function_prob lanes distance_ft health_status
98
+ 0 2 2 0.99 Advance 0.90 2 164.0 ok
99
+ 1 3 2 1.00 Advance 0.64 1,2 153.0 ok
100
+ 2 4 2 1.00 Presence 0.40 1,2 0.0 ok
101
+ 3 5 2 1.00 Advance 0.53 1 152.0 ok
102
+ 4 6 <NA> NaN NaN NaN NaN NaN not_enough_data
103
+ ```
104
+
105
+ From the bundled 30-minute sample; detector 6 had one actuation, too few to judge. One row per detector; main columns:
106
+
107
+ | Column | What |
108
+ |---|---|
109
+ | `phase_pred`, `phase_prob` | phase and its probability (`phase_2nd`, `phase_2nd_prob`: runner-up) |
110
+ | `function_pred`, `function_prob` | function (table above) and its probability |
111
+ | `lanes`, `phase_n_lanes` | lane(s) the detector covers, number of lanes on its phase |
112
+ | `distance_ft`, `night_speed_mph` | setback from the stop bar (ft); typical night speed on the phase |
113
+ | `health_status`, `health_reason` | ok / watch / suspect / bad / not_enough_data, and why |
114
+ | `status`, `review_flag` | "ok", or why an answer is missing or weak; `True` = worth a look |
115
+
116
+ Under 5 actuations: no answer (guess in `phase_guess`, `function_guess`). `return_phases=True` also returns a phase table.
117
+
118
+ **One signal or many.** `DeviceId` separates signals. Each is processed on its own, so its answers are the same alone
119
+ or in a batch. `predict` also takes a parquet or CSV path or glob, and `start` / `end` times. To build it into an
120
+ application, see [docs/embedding.md](https://github.com/ShawnStrasser/atspm-detector/blob/main/docs/embedding.md).
121
+
122
+ ## Input
123
+
124
+ One row per event, standard Indiana hi-res format: `DeviceId` (any signal id), `Timestamp` (controller local time),
125
+ `EventId`, `Parameter`. Give it the whole log; unused event codes are dropped.
126
+
127
+ ## How much data
128
+
129
+ | Sample length | Phase | Function | Notes |
130
+ |---|---|---|---|
131
+ | 5 minutes | 94.1 % | 86.4 % | works, but weaker; no lanes |
132
+ | 30 minutes | 97.7 % | 91.7 % | **recommended minimum**; lanes from here on |
133
+ | 1 hour | 98.0 % | 91.8 % | |
134
+ | 3 hours | 98.2 % | 92.9 % | best for phase and function (3 to 24 hours) |
135
+ | 24 hours | 98.4 % | 92.5 % | full health check |
136
+
137
+ On the 115 test signals. Health runs on any length; time-of-day checks need a full day, night speed midnight to 5 am.
138
+
139
+ ## How it works
140
+
141
+ * **In:** detector on / off, signal colors and phase calls. The model never sees a phase or channel number; it scores
142
+ each detector against every phase that turned green, from behavior alone.
143
+ * **Models:** gradient-boosted trees and a small neural network score each detector against each phase and function.
144
+ A final step looks at the whole intersection to keep at most one detector of each ATSPM function per lane. Small
145
+ models give lanes and setback. Health is checked last, by rules, and never changes an answer. All on the CPU with
146
+ onnxruntime (8.5 MB of weights).
147
+
148
+ ## Accuracy
149
+
150
+ Measured once, on the final model, on **115 locked signals** never used for training or tuning (30 minutes to 24 hours
151
+ of log). Phase truth: controller timing (21,341 detector samples, 113 signals). Function truth: configuration export
152
+ checked against cabinet prints (87 signals). Detectors with no actuations or no green on their phase are not scored.
153
+ All labels come from one agency; other agencies' conventions may differ.
154
+
155
+ * **Phase: 98.0 %** (95 % interval 97.0-98.9 %). Most errors are between through phases nearly always green together
156
+ (2 and 6, 4 and 8).
157
+ * **Function: 91.7 %** (88.7-94.0 %), ATSPM errors only, excluding detectors whose label the model picked (print vs
158
+ hand label). By true class: Advance 93.9 %, Presence 95.2 %, Count 96.6 %, Yellow_Red 89.5 %, Other 81.8 %.
159
+ * **Lanes:** lane count per phase exact for 82.5 % of phases, within one for 97.5 % (298 phases).
160
+ * **Setback:** median error 36 ft (59.9 % within 50 ft; Advance, 25 signals); 23.9 ft in cross-validation.
161
+ * **Night speed:** an approach speed, not the posted speed; two nights agree to a median of 1.5 mph.
162
+ * **Speed:** 0.8 s per signal for 30 minutes of log, 1.7 s for 24 hours; 250-400 MB of memory.
163
+
164
+ **Against hand labels** (signals not trained on): phase wrong on 1.3 % of 4,636 detectors (hand labels 2.6 %); function
165
+ off the cabinet print on about 6 % of 5,254 (hand labels 9.7 %). Where model and hand label differed, the timing backed
166
+ the model 104 times to 24, the print 398 to 177. Prints can be out of date.
167
+
168
+ **Health:** flagged 19 of 20 detectors a traffic engineer judged faulty, and none of the 12 judged fine.
169
+ Flagged (suspect or bad) per 100 detectors: 0.4 on 30 minutes, 1.2 on 3 hours, 3.9 on 24 hours. Not field-verified.
170
+
171
+ ## Detector health
172
+
173
+ Each detector gets a status, a plain-words reason, the periods affected and the categories, judged against what is
174
+ normal for its type (function x lanes x sample length). Checks: stuck on, goes silent, count drops, misses vehicles at
175
+ night, erratic counts, too-fast actuations, chattering, too many for the traffic, erratic time ON, busy at night,
176
+ unusual daily pattern, Count zone held ON. **Bad** = one strong finding, two checks agreeing, or 4+ hours of the day.
177
+
178
+ ![Stuck on: a Presence detector held ON three times in one day](https://raw.githubusercontent.com/ShawnStrasser/atspm-detector/main/docs/images/health_stuck_on.png)
179
+
180
+ ![Busy at night: a detector ON 62 % of the time from 1 to 5 am](https://raw.githubusercontent.com/ShawnStrasser/atspm-detector/main/docs/images/health_busy_at_night.png)
181
+
182
+ ![Unusual daily pattern: an advance loop goes quiet at midday while its partner keeps counting](https://raw.githubusercontent.com/ShawnStrasser/atspm-detector/main/docs/images/health_unusual_day.png)
183
+
184
+ ## License
185
+
186
+ MIT
@@ -0,0 +1,149 @@
1
+ # atspm-detector
2
+
3
+ Give it a signal controller's hi-res event log; for every vehicle detector it returns the **phase**, **function**,
4
+ **lanes**, **setback** from the stop bar and **health**. No cabinet print needed: the answers come from how each
5
+ detector behaves. Use it to **build** a signal's ATSPM detector configuration, or to **check** the one you have.
6
+
7
+ * **Tested on 115 signals it never trained on:** phase right for **98.0 %** of detectors, function **91.7 %**
8
+ (30 minutes or more of log).
9
+ * **More accurate than hand-labled configuration:** phase wrong on 1.3 % of detectors, against 2.6 % by hand.
10
+ * **Fast and light:** one second per signal on CPU.
11
+
12
+ **Contents:** [Detector functions](#detector-functions) · [Install](#install) · [Usage](#usage) · [Input](#input) ·
13
+ [How much data](#how-much-data) · [How it works](#how-it-works) · [Accuracy](#accuracy) ·
14
+ [Detector health](#detector-health) · [License](#license)
15
+
16
+ ## Detector functions
17
+
18
+ | Function | ATSPM | What it is |
19
+ |---|---|---|
20
+ | Presence | yes | Stop-bar presence zone. The presence zones used in training were about 20 ft long at the stop bar. |
21
+ | Advance | yes | Advance count zone, upstream. Single lane or spanning several lanes. |
22
+ | Count | yes | Stop-bar count zone. Usually set to pulse, one per lane. |
23
+ | Yellow_Red | yes | At the stop bar; may span several lanes. Configure it in the field with a 5 mph speed threshold. |
24
+ | Mid | no | Zone between the advance and the stop bar. A class of its own because it helps the model. |
25
+ | Bike | no | Bicycle detection zone. |
26
+ | Other | no | Everything else that drives operations but is not an ATSPM zone. |
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install atspm-detector # Python 3.10 or newer
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ```python
37
+ import pandas as pd
38
+ from atspm_detector import predict
39
+
40
+ events = pd.read_csv("events.csv") # or pd.read_parquet (needs pyarrow)
41
+ events.head()
42
+ ```
43
+
44
+ ```text
45
+ DeviceId Timestamp EventId Parameter
46
+ 0 d2d0de72-c8aa-45ca-ae1a-8fc626c5afc9 2024-12-03 12:00:00.000 150 7
47
+ 1 d2d0de72-c8aa-45ca-ae1a-8fc626c5afc9 2024-12-03 12:00:00.000 320 0
48
+ 2 d2d0de72-c8aa-45ca-ae1a-8fc626c5afc9 2024-12-03 12:00:00.000 318 3
49
+ 3 d2d0de72-c8aa-45ca-ae1a-8fc626c5afc9 2024-12-03 12:00:00.000 316 120
50
+ 4 d2d0de72-c8aa-45ca-ae1a-8fc626c5afc9 2024-12-03 12:00:00.600 13 6
51
+ ```
52
+
53
+ ```python
54
+ results = predict(events)
55
+ results[["Detector", "phase_pred", "phase_prob", "function_pred", "function_prob",
56
+ "lanes", "distance_ft", "health_status"]].round(2).head()
57
+ ```
58
+
59
+ ```text
60
+ Detector phase_pred phase_prob function_pred function_prob lanes distance_ft health_status
61
+ 0 2 2 0.99 Advance 0.90 2 164.0 ok
62
+ 1 3 2 1.00 Advance 0.64 1,2 153.0 ok
63
+ 2 4 2 1.00 Presence 0.40 1,2 0.0 ok
64
+ 3 5 2 1.00 Advance 0.53 1 152.0 ok
65
+ 4 6 <NA> NaN NaN NaN NaN NaN not_enough_data
66
+ ```
67
+
68
+ From the bundled 30-minute sample; detector 6 had one actuation, too few to judge. One row per detector; main columns:
69
+
70
+ | Column | What |
71
+ |---|---|
72
+ | `phase_pred`, `phase_prob` | phase and its probability (`phase_2nd`, `phase_2nd_prob`: runner-up) |
73
+ | `function_pred`, `function_prob` | function (table above) and its probability |
74
+ | `lanes`, `phase_n_lanes` | lane(s) the detector covers, number of lanes on its phase |
75
+ | `distance_ft`, `night_speed_mph` | setback from the stop bar (ft); typical night speed on the phase |
76
+ | `health_status`, `health_reason` | ok / watch / suspect / bad / not_enough_data, and why |
77
+ | `status`, `review_flag` | "ok", or why an answer is missing or weak; `True` = worth a look |
78
+
79
+ Under 5 actuations: no answer (guess in `phase_guess`, `function_guess`). `return_phases=True` also returns a phase table.
80
+
81
+ **One signal or many.** `DeviceId` separates signals. Each is processed on its own, so its answers are the same alone
82
+ or in a batch. `predict` also takes a parquet or CSV path or glob, and `start` / `end` times. To build it into an
83
+ application, see [docs/embedding.md](https://github.com/ShawnStrasser/atspm-detector/blob/main/docs/embedding.md).
84
+
85
+ ## Input
86
+
87
+ One row per event, standard Indiana hi-res format: `DeviceId` (any signal id), `Timestamp` (controller local time),
88
+ `EventId`, `Parameter`. Give it the whole log; unused event codes are dropped.
89
+
90
+ ## How much data
91
+
92
+ | Sample length | Phase | Function | Notes |
93
+ |---|---|---|---|
94
+ | 5 minutes | 94.1 % | 86.4 % | works, but weaker; no lanes |
95
+ | 30 minutes | 97.7 % | 91.7 % | **recommended minimum**; lanes from here on |
96
+ | 1 hour | 98.0 % | 91.8 % | |
97
+ | 3 hours | 98.2 % | 92.9 % | best for phase and function (3 to 24 hours) |
98
+ | 24 hours | 98.4 % | 92.5 % | full health check |
99
+
100
+ On the 115 test signals. Health runs on any length; time-of-day checks need a full day, night speed midnight to 5 am.
101
+
102
+ ## How it works
103
+
104
+ * **In:** detector on / off, signal colors and phase calls. The model never sees a phase or channel number; it scores
105
+ each detector against every phase that turned green, from behavior alone.
106
+ * **Models:** gradient-boosted trees and a small neural network score each detector against each phase and function.
107
+ A final step looks at the whole intersection to keep at most one detector of each ATSPM function per lane. Small
108
+ models give lanes and setback. Health is checked last, by rules, and never changes an answer. All on the CPU with
109
+ onnxruntime (8.5 MB of weights).
110
+
111
+ ## Accuracy
112
+
113
+ Measured once, on the final model, on **115 locked signals** never used for training or tuning (30 minutes to 24 hours
114
+ of log). Phase truth: controller timing (21,341 detector samples, 113 signals). Function truth: configuration export
115
+ checked against cabinet prints (87 signals). Detectors with no actuations or no green on their phase are not scored.
116
+ All labels come from one agency; other agencies' conventions may differ.
117
+
118
+ * **Phase: 98.0 %** (95 % interval 97.0-98.9 %). Most errors are between through phases nearly always green together
119
+ (2 and 6, 4 and 8).
120
+ * **Function: 91.7 %** (88.7-94.0 %), ATSPM errors only, excluding detectors whose label the model picked (print vs
121
+ hand label). By true class: Advance 93.9 %, Presence 95.2 %, Count 96.6 %, Yellow_Red 89.5 %, Other 81.8 %.
122
+ * **Lanes:** lane count per phase exact for 82.5 % of phases, within one for 97.5 % (298 phases).
123
+ * **Setback:** median error 36 ft (59.9 % within 50 ft; Advance, 25 signals); 23.9 ft in cross-validation.
124
+ * **Night speed:** an approach speed, not the posted speed; two nights agree to a median of 1.5 mph.
125
+ * **Speed:** 0.8 s per signal for 30 minutes of log, 1.7 s for 24 hours; 250-400 MB of memory.
126
+
127
+ **Against hand labels** (signals not trained on): phase wrong on 1.3 % of 4,636 detectors (hand labels 2.6 %); function
128
+ off the cabinet print on about 6 % of 5,254 (hand labels 9.7 %). Where model and hand label differed, the timing backed
129
+ the model 104 times to 24, the print 398 to 177. Prints can be out of date.
130
+
131
+ **Health:** flagged 19 of 20 detectors a traffic engineer judged faulty, and none of the 12 judged fine.
132
+ Flagged (suspect or bad) per 100 detectors: 0.4 on 30 minutes, 1.2 on 3 hours, 3.9 on 24 hours. Not field-verified.
133
+
134
+ ## Detector health
135
+
136
+ Each detector gets a status, a plain-words reason, the periods affected and the categories, judged against what is
137
+ normal for its type (function x lanes x sample length). Checks: stuck on, goes silent, count drops, misses vehicles at
138
+ night, erratic counts, too-fast actuations, chattering, too many for the traffic, erratic time ON, busy at night,
139
+ unusual daily pattern, Count zone held ON. **Bad** = one strong finding, two checks agreeing, or 4+ hours of the day.
140
+
141
+ ![Stuck on: a Presence detector held ON three times in one day](https://raw.githubusercontent.com/ShawnStrasser/atspm-detector/main/docs/images/health_stuck_on.png)
142
+
143
+ ![Busy at night: a detector ON 62 % of the time from 1 to 5 am](https://raw.githubusercontent.com/ShawnStrasser/atspm-detector/main/docs/images/health_busy_at_night.png)
144
+
145
+ ![Unusual daily pattern: an advance loop goes quiet at midday while its partner keeps counting](https://raw.githubusercontent.com/ShawnStrasser/atspm-detector/main/docs/images/health_unusual_day.png)
146
+
147
+ ## License
148
+
149
+ MIT
@@ -0,0 +1,69 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "atspm-detector"
7
+ dynamic = ["version"]
8
+ description = "Phase, function, lanes, setback, night speed and health of traffic-signal detectors from the controller's hi-res event log (CPU, onnxruntime)."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [
14
+ {name = "Shawn Strasser"}
15
+ ]
16
+ keywords = ["traffic signal", "ATSPM", "detector", "high-resolution data", "NTCIP"]
17
+ classifiers = [
18
+ "Development Status :: 5 - Production/Stable",
19
+ "Intended Audience :: Science/Research",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Scientific/Engineering",
28
+ ]
29
+
30
+ dependencies = [
31
+ "numpy>=1.24",
32
+ "pandas>=2.0",
33
+ "duckdb>=1.0",
34
+ "onnxruntime>=1.21",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ charts = ["matplotlib>=3.7"]
39
+ dev = [
40
+ "pytest>=7.0.0",
41
+ "build>=1.0",
42
+ ]
43
+
44
+ [project.scripts]
45
+ atspm-detector = "atspm_detector.pipeline:main"
46
+ atspm-detector-check = "atspm_detector.check:cli"
47
+
48
+ [project.urls]
49
+ Homepage = "https://github.com/ShawnStrasser/atspm-detector"
50
+ Documentation = "https://github.com/ShawnStrasser/atspm-detector#readme"
51
+ Repository = "https://github.com/ShawnStrasser/atspm-detector"
52
+ Issues = "https://github.com/ShawnStrasser/atspm-detector/issues"
53
+ Changelog = "https://github.com/ShawnStrasser/atspm-detector/blob/main/CHANGELOG.md"
54
+ "Embedding Guide" = "https://github.com/ShawnStrasser/atspm-detector/blob/main/docs/embedding.md"
55
+
56
+ [tool.setuptools.dynamic]
57
+ version = {attr = "atspm_detector.__version__"}
58
+
59
+ [tool.setuptools]
60
+ include-package-data = false
61
+
62
+ [tool.setuptools.packages.find]
63
+ where = ["src"]
64
+
65
+ [tool.setuptools.package-data]
66
+ atspm_detector = ["weights/*.json", "weights/*.onnx", "weights/*/*.json", "weights/*/*.onnx", "data/*.parquet", "reference/*.parquet"]
67
+
68
+ [tool.pytest.ini_options]
69
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ """atspm_detector: phase, function, lanes, setback, night speed and health of every detector of a traffic signal,
2
+ from the controller's high-resolution event log alone (CPU, onnxruntime; no channel-to-phase table).
3
+
4
+ from atspm_detector import predict, to_atspm_config
5
+ out = predict("events.parquet") # one row per detector
6
+ out, phases = predict(df, return_phases=True) # + the per-phase lane table
7
+ config = to_atspm_config(out) # detector configuration for the atspm package
8
+
9
+ Check an install with `python -m atspm_detector.check` (or the `atspm-detector-check` command).
10
+ """
11
+ from __future__ import annotations
12
+
13
+ __version__ = "1.0.0"
14
+
15
+ from .atspm_export import atspm_coverage, to_atspm_config # noqa: E402
16
+ from .pipeline import (DEFAULT_MODEL_DIR, EXTRA_COLS, OUT_COLS, PHASE_COLS, PROB_COLS, # noqa: E402
17
+ list_signals, main, predict, run)
18
+
19
+ __all__ = ["predict", "list_signals", "run", "main", "to_atspm_config", "atspm_coverage", "OUT_COLS", "EXTRA_COLS",
20
+ "PHASE_COLS", "PROB_COLS", "DEFAULT_MODEL_DIR", "__version__"]
@@ -0,0 +1,4 @@
1
+ """python -m atspm_detector --events ... --out ... (the predict command line)."""
2
+ from .pipeline import main
3
+
4
+ main()