mcdonald 0.2.4__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 (71) hide show
  1. mcdonald-0.2.4/LICENSE +28 -0
  2. mcdonald-0.2.4/MANIFEST.in +3 -0
  3. mcdonald-0.2.4/PKG-INFO +102 -0
  4. mcdonald-0.2.4/README-technical.md +441 -0
  5. mcdonald-0.2.4/README.md +68 -0
  6. mcdonald-0.2.4/docs/agents.md +406 -0
  7. mcdonald-0.2.4/docs/install.md +112 -0
  8. mcdonald-0.2.4/docs/method.md +313 -0
  9. mcdonald-0.2.4/pyproject.toml +69 -0
  10. mcdonald-0.2.4/setup.cfg +4 -0
  11. mcdonald-0.2.4/setup.py +23 -0
  12. mcdonald-0.2.4/src/mcdonald/__init__.py +33 -0
  13. mcdonald-0.2.4/src/mcdonald/actions.py +266 -0
  14. mcdonald-0.2.4/src/mcdonald/autolink.py +701 -0
  15. mcdonald-0.2.4/src/mcdonald/case_cli.py +56 -0
  16. mcdonald-0.2.4/src/mcdonald/catalog.py +200 -0
  17. mcdonald-0.2.4/src/mcdonald/cli.py +112 -0
  18. mcdonald-0.2.4/src/mcdonald/clip.py +345 -0
  19. mcdonald-0.2.4/src/mcdonald/comotion.py +317 -0
  20. mcdonald-0.2.4/src/mcdonald/figures.py +360 -0
  21. mcdonald-0.2.4/src/mcdonald/find_qt.py +325 -0
  22. mcdonald-0.2.4/src/mcdonald/flicker.py +340 -0
  23. mcdonald-0.2.4/src/mcdonald/forensics.py +885 -0
  24. mcdonald-0.2.4/src/mcdonald/groups.py +386 -0
  25. mcdonald-0.2.4/src/mcdonald/gui.py +135 -0
  26. mcdonald-0.2.4/src/mcdonald/icons/mcdonald-128.png +0 -0
  27. mcdonald-0.2.4/src/mcdonald/icons/mcdonald-16.png +0 -0
  28. mcdonald-0.2.4/src/mcdonald/icons/mcdonald-24.png +0 -0
  29. mcdonald-0.2.4/src/mcdonald/icons/mcdonald-256.png +0 -0
  30. mcdonald-0.2.4/src/mcdonald/icons/mcdonald-32.png +0 -0
  31. mcdonald-0.2.4/src/mcdonald/icons/mcdonald-48.png +0 -0
  32. mcdonald-0.2.4/src/mcdonald/icons/mcdonald-512.png +0 -0
  33. mcdonald-0.2.4/src/mcdonald/icons/mcdonald-64.png +0 -0
  34. mcdonald-0.2.4/src/mcdonald/icons/mcdonald-small.svg +15 -0
  35. mcdonald-0.2.4/src/mcdonald/icons/mcdonald.icns +0 -0
  36. mcdonald-0.2.4/src/mcdonald/icons/mcdonald.ico +0 -0
  37. mcdonald-0.2.4/src/mcdonald/icons/mcdonald.svg +19 -0
  38. mcdonald-0.2.4/src/mcdonald/integrity.py +810 -0
  39. mcdonald-0.2.4/src/mcdonald/kinematics.py +559 -0
  40. mcdonald-0.2.4/src/mcdonald/kinematics_cli.py +103 -0
  41. mcdonald-0.2.4/src/mcdonald/layers.py +500 -0
  42. mcdonald-0.2.4/src/mcdonald/look.py +411 -0
  43. mcdonald-0.2.4/src/mcdonald/mark.py +639 -0
  44. mcdonald-0.2.4/src/mcdonald/mark_qt.py +2908 -0
  45. mcdonald-0.2.4/src/mcdonald/measure_qt.py +763 -0
  46. mcdonald-0.2.4/src/mcdonald/progress.py +116 -0
  47. mcdonald-0.2.4/src/mcdonald/propose.py +902 -0
  48. mcdonald-0.2.4/src/mcdonald/pursue_videos.csv +145 -0
  49. mcdonald-0.2.4/src/mcdonald/readme_cli.py +53 -0
  50. mcdonald-0.2.4/src/mcdonald/reel.py +201 -0
  51. mcdonald-0.2.4/src/mcdonald/report.py +540 -0
  52. mcdonald-0.2.4/src/mcdonald/run.py +83 -0
  53. mcdonald-0.2.4/src/mcdonald/scale.py +175 -0
  54. mcdonald-0.2.4/src/mcdonald/setup_cli.py +174 -0
  55. mcdonald-0.2.4/src/mcdonald/stages.py +626 -0
  56. mcdonald-0.2.4/src/mcdonald/storage.py +86 -0
  57. mcdonald-0.2.4/src/mcdonald/symbology.py +725 -0
  58. mcdonald-0.2.4/src/mcdonald/tracksheet.py +230 -0
  59. mcdonald-0.2.4/src/mcdonald/update.py +250 -0
  60. mcdonald-0.2.4/src/mcdonald.egg-info/PKG-INFO +102 -0
  61. mcdonald-0.2.4/src/mcdonald.egg-info/SOURCES.txt +69 -0
  62. mcdonald-0.2.4/src/mcdonald.egg-info/dependency_links.txt +1 -0
  63. mcdonald-0.2.4/src/mcdonald.egg-info/entry_points.txt +5 -0
  64. mcdonald-0.2.4/src/mcdonald.egg-info/requires.txt +10 -0
  65. mcdonald-0.2.4/src/mcdonald.egg-info/top_level.txt +1 -0
  66. mcdonald-0.2.4/tests/test_cli.py +599 -0
  67. mcdonald-0.2.4/tests/test_golden.py +224 -0
  68. mcdonald-0.2.4/tests/test_gui.py +2054 -0
  69. mcdonald-0.2.4/tests/test_measurement.py +1471 -0
  70. mcdonald-0.2.4/tests/test_published.py +252 -0
  71. mcdonald-0.2.4/tests/test_reduction.py +809 -0
mcdonald-0.2.4/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Jacob Haqq Misra
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,3 @@
1
+ # What a source distribution needs beyond the package: the documents setup.py copies in.
2
+ include README-technical.md
3
+ include docs/method.md docs/agents.md docs/install.md
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcdonald
3
+ Version: 0.2.4
4
+ Summary: Measurement tools for single-sensor video of unidentified objects: background layers, clip integrity, symbology, angular scale, kinematics, co-motion
5
+ Author-email: Jacob Haqq Misra <jacob@bmsis.org>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/haqqmisra/mcdonald
8
+ Project-URL: Repository, https://github.com/haqqmisra/mcdonald
9
+ Project-URL: Documentation, https://github.com/haqqmisra/mcdonald/blob/main/README-technical.md
10
+ Project-URL: Installing, https://github.com/haqqmisra/mcdonald/blob/main/docs/install.md
11
+ Keywords: uap,video-forensics,photogrammetry,kinematics,remote-sensing
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Operating System :: Microsoft :: Windows
19
+ Classifier: Operating System :: POSIX :: Linux
20
+ Classifier: Environment :: X11 Applications :: Qt
21
+ Classifier: Environment :: Console
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: numpy>=1.24
26
+ Requires-Dist: scipy>=1.10
27
+ Requires-Dist: matplotlib>=3.7
28
+ Requires-Dist: pillow>=9.5
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7; extra == "dev"
31
+ Provides-Extra: gui
32
+ Requires-Dist: PySide6-Essentials>=6.6; extra == "gui"
33
+ Dynamic: license-file
34
+
35
+ <picture>
36
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/haqqmisra/mcdonald/main/docs/logo-dark.png">
37
+ <img alt="mcDonald UAP Toolkit" src="https://raw.githubusercontent.com/haqqmisra/mcdonald/main/docs/logo-light.png" width="420">
38
+ </picture>
39
+
40
+ **mcdonald** is a frame-by-frame analysis toolkit for measuring the kinematics of an unknown
41
+ object in a single-camera video.
42
+
43
+ > "Science is in default for having failed to mount any truly adequate studies of this problem."
44
+ >
45
+ > — James E. McDonald
46
+
47
+ **Status:** early test version (0.2.4), tried on Linux and macOS; Windows is next.
48
+
49
+ ## Install
50
+
51
+ 1. Prerequisite: make sure **ffmpeg** is installed:
52
+ `brew install ffmpeg` (Mac), `winget install Gyan.FFmpeg` (Windows),
53
+ `sudo apt install ffmpeg` or `sudo dnf install ffmpeg` (Linux).
54
+ 2. Install **mcdonald** (needs Python 3.10 or newer):
55
+
56
+ ```bash
57
+ python3 -m pip install "mcdonald[gui]"
58
+ ```
59
+
60
+ 3. Check your setup, which also provides instructions on what to do next:
61
+
62
+ ```bash
63
+ mcdonald setup
64
+ ```
65
+
66
+ When a newer version is out, `mcdonald-gui` offers to update itself when it starts, and the
67
+ command line prints the command to update with.
68
+
69
+ New to Python or the terminal? [docs/install.md](https://github.com/haqqmisra/mcdonald/blob/main/docs/install.md) walks through each step in
70
+ detail.
71
+
72
+ ## Use
73
+
74
+ **mcdonald** has a graphical interface and a command-line interface. The software is intended
75
+ to be driven by human users and/or AI agents.
76
+
77
+ The graphical interface is designed to prompt human users through the steps of analysis. To
78
+ start the graphical interface:
79
+
80
+ ```bash
81
+ mcdonald-gui
82
+ ```
83
+
84
+ The command-line interface is designed for developers, advanced users, and AI agents. To start
85
+ the command-line interface and see a summary of options:
86
+
87
+ ```bash
88
+ mcdonald
89
+ ```
90
+
91
+ ### Prompting an agent
92
+
93
+ You can invoke an AI agent to assist at any point in the analysis, whether before or after a
94
+ human has looked at the video. Here is a sample prompt that you can pass to your favorite
95
+ command-line AI model:
96
+
97
+ > Please use the mcdonald toolkit to analyze the PR144 video released under PURSUE. Run
98
+ > `mcdonald readme` to get started.
99
+
100
+ ## License
101
+
102
+ Copyright (c) 2026 Jacob Haqq Misra. Released under the [BSD 3-Clause License](https://github.com/haqqmisra/mcdonald/blob/main/LICENSE).
@@ -0,0 +1,441 @@
1
+ # McDonald UAP Toolkit: the technical README
2
+
3
+ **For AI agents and technical users**: every command, how each step works, what the tests hold it to,
4
+ and why. For installing it and using the window, [README.md](README.md) is shorter.
5
+
6
+ Measurement tools for single-sensor video of unidentified objects.
7
+
8
+ Point it at a clip and it will tell you how the background moves and in how many
9
+ layers, whether the clip behaves like the output of one sensor chain, whether
10
+ the object in it behaves like imagery or like something laid over imagery, and
11
+ whether your track is actually on the object in every frame.
12
+
13
+ It will also, frequently, tell you that a question cannot be answered from the
14
+ clip you have. That is the intended behaviour, not a shortfall. Most released
15
+ sensor video does not contain enough information to state a speed, and a tool
16
+ that always returns a number is worse than useless on this subject.
17
+
18
+ > Named for James E. McDonald, who argued that the subject deserved ordinary
19
+ > scientific instruments rather than either credulity or dismissal.
20
+
21
+ **Status: alpha (0.2.4).** The full pipeline: eight stages from a file to a
22
+ case report, plus each stage as its own command. See [Roadmap](#roadmap) for
23
+ what is still missing.
24
+
25
+ ---
26
+
27
+ ## Install
28
+
29
+ Three steps, the same on macOS, Windows and Linux ([docs/install.md](docs/install.md) goes
30
+ through each one slowly, for each system):
31
+
32
+ 1. **ffmpeg**, which mcdonald reads video with: `brew install ffmpeg` (macOS),
33
+ `winget install Gyan.FFmpeg` (Windows), `sudo dnf install ffmpeg` or `sudo apt install ffmpeg` (Linux).
34
+ 2. **mcdonald**, with its window (Python 3.10 or newer):
35
+
36
+ ```bash
37
+ python3 -m pip install "mcdonald[gui]"
38
+ ```
39
+
40
+ 3. **Check this computer**, which also says what to type next:
41
+
42
+ ```bash
43
+ mcdonald setup
44
+ ```
45
+
46
+ Then `mcdonald-gui` opens the window, and `mcdonald run PR149` does the whole job on the
47
+ command line (downloading PR149 the first time).
48
+
49
+ **Updates.** A copy installed this way asks PyPI, at most once a day, what the newest version
50
+ is (one small request; no account). If it is newer, the window asks whether to update: on a yes
51
+ it closes, pip installs the new version, and it opens again. The command line never stops to
52
+ ask; it prints one line on stderr (stdout stays JSON) with the pip command. A copy installed
53
+ from GitHub (`pip install "mcdonald[gui] @ git+https://github.com/haqqmisra/mcdonald"`, as
54
+ before 0.2.4) asks GitHub's main instead and updates from there; a working copy
55
+ (`pip install -e`) is never checked, and `MCDONALD_NO_UPDATE_CHECK=1` turns it off
56
+ (`mcdonald.update`).
57
+
58
+ ### What it needs, and how long it takes
59
+
60
+ - **Python 3.10 or newer, and ffmpeg**, on macOS, Windows or Linux. The window needs a desktop;
61
+ the command line does not.
62
+ - **Disk**: each frame worked on is saved once, losslessly: about 0.75 MB a frame at 1920×1080,
63
+ so a 3-second segment is ~70 MB and a whole 3-minute video ~4 GB. Downloaded PURSUE videos are
64
+ 2 MB to 3 GB (most under 100 MB). Both go in one folder you choose on the first screen.
65
+ - **Memory**: 8 GB is enough for a segment of a few hundred frames; more for whole videos.
66
+ - **Time.** mcdonald compares frames pixel by pixel, and some steps are slow. On the machine it
67
+ was built on (a 2012 six-core Xeon workstation, 2 GHz, 32 GB), for a 1080p segment:
68
+
69
+ | step | about |
70
+ |---|---|
71
+ | saving the frames (once) | a few seconds per 100 frames |
72
+ | Find the object | 25 s + 0.2 s a frame (a 3-second segment: under a minute) |
73
+ | Follow it | seconds to a minute |
74
+ | Measure, without the two slow checks | under a minute |
75
+ | Measure: the *layers* check | about 1.7 s per pair of frames (3 s: ~2 minutes) |
76
+ | Measure: the *integrity* check | about 2.6 s per pair of frames + 90 s (3 s: ~5 minutes) |
77
+
78
+ So **open only the part of the video with the object in it**, plus a second or two either side:
79
+ the window asks which part, and says what it will cost before it starts. A whole 3-minute video
80
+ measured with every check is hours. Each step shows its progress, and can be stopped.
81
+
82
+ ### Working on the code
83
+
84
+ ```bash
85
+ git clone https://github.com/haqqmisra/mcdonald.git
86
+ cd mcdonald
87
+ python3 -m pip install -e ".[gui]"
88
+ ```
89
+
90
+ Verify the install measures correctly before you trust a number from it:
91
+
92
+ ```bash
93
+ python3 tests/test_measurement.py # measurement: masks, registration, layers, detection
94
+ python3 tests/test_reduction.py # reduction: symbology, kinematics, scale, marks, report
95
+ python3 tests/test_published.py # every number in the Technical Note and the PR144 notes
96
+ ```
97
+
98
+ Together those are the checks against cases whose answers are known by
99
+ construction — a known rigid shift, two backgrounds moving at different rates,
100
+ planted repeated frames, an object on a known path, the published PR113
101
+ reduction, the PR149 scale-bar bound — confirming the library recovers each
102
+ one. They need no video data, take a few minutes, and each should end
103
+ `ALL PASS`.
104
+
105
+ `test_published.py` adds 32 more, walking the Technical Note's and the PR144
106
+ notes' quantitative claims one at a time, so a change that would move a number
107
+ in a manuscript fails here first. `tests/test_golden.py` goes further and
108
+ re-measures real clips, but needs the video files; it skips cleanly and says
109
+ so when they are absent.
110
+
111
+ `python3 tests/test_gui.py` presses every key and button of both `mcdonald mark`
112
+ windows — the Qt one, and the matplotlib one under each interactive backend the
113
+ machine can open — runs the same checks against each, and compares the files
114
+ they save. It skips what cannot open here and says why, which also makes it the
115
+ quickest way to find out whether `mcdonald mark` will open a window at all.
116
+
117
+ ## Use
118
+
119
+ Everything at once, into one report:
120
+
121
+ ```bash
122
+ mcdonald run CLIP.mp4 --track track.csv
123
+ mcdonald run CLIP.mp4 --marks CLIP/clip_marks.json # or from two clicks: the track is linked first
124
+ ```
125
+
126
+ That walks the clip through ingest → survey → track → **verify** → layers →
127
+ scale → kinematics → integrity → report, and writes `<tag>_case.md` and
128
+ `.json`. A stage that has nothing to work with says so and the run continues;
129
+ nothing is silently skipped. Each stage is the function the stage's own command
130
+ calls (`mcdonald.stages`), so a number in the case report is the number
131
+ `mcdonald layers` or `mcdonald kinematics` would give. With a track, the layers
132
+ stage is the whole measurement — the object against each background layer, about
133
+ a second per frame pair — and `--skip layers,integrity` is the quick run.
134
+
135
+ Or one question at a time:
136
+
137
+ ```bash
138
+ # How does the background move -- and is it one background?
139
+ mcdonald layers CLIP.mp4 --auto-track --validate
140
+
141
+ # Has the clip been altered? Was the object added?
142
+ mcdonald integrity CLIP.mp4 --track track.csv
143
+
144
+ # Is my track on the object in every frame?
145
+ mcdonald tracksheet CLIP.mp4 --track track.csv
146
+
147
+ # Find the object and click it on a couple of frames (opens a window)
148
+ mcdonald mark CLIP.mp4 # the whole clip
149
+ mcdonald mark CLIP.mp4 --n0 400 --n1 420 # or a window of it
150
+
151
+ # ...and hand those clicks to the pipeline: the track is linked from them
152
+ mcdonald layers CLIP.mp4 --marks CLIP/clip_marks.json
153
+
154
+ # The same with no window, for something that cannot click: see the clip, then say where
155
+ mcdonald look CLIP.mp4 --n0 1 --n1 120 --propose # what moves against the background, best first, as strips
156
+ mcdonald look CLIP.mp4 # an overview sheet; --n0/--n1 narrow it
157
+ mcdonald look CLIP.mp4 --frame 408 --size 21 --dark # a frame's candidates, ringed, numbered, enlarged
158
+ mcdonald mark CLIP.mp4 --n0 400 --n1 420 --no-window --link \
159
+ --set object@408=1010.9,313.0 --set object@411=702.4,604.2 --why "candidate 1 of 6 at 21 px dark; the one that moves"
160
+
161
+ # Boresight, north pointer, corner brackets -- the overlay's own readings
162
+ mcdonald symbology CLIP.mp4
163
+
164
+ # v_px -> omega -> what the motion permits (with --ladder when k is unknown)
165
+ mcdonald kinematics CLIP.mp4 --track track.csv --ladder
166
+
167
+ # Does the object move WITH the texture around it, or THROUGH it?
168
+ mcdonald comotion CLIP.mp4 --track track.csv --diameter 72
169
+ ```
170
+
171
+ `CLIP` is a path to any video file. Results go to a **case directory** —
172
+ `--out DIR`, otherwise `./<name>/` under your working directory. Nothing is ever
173
+ written next to the installed code.
174
+
175
+ **Two ways in, and each does the whole job.** A person with no terminal starts
176
+ `mcdonald-gui` (below), marks and links the object, and measures it from the
177
+ window's Measure menu, which is `mcdonald run` with a form in place of the
178
+ options. An agent with no display uses `look` and `mark --set`: `docs/agents.md`
179
+ is PR113 done start to finish that way, to 141.4 px/frame against the published
180
+ 142. A mark placed with `--set` is recorded
181
+ as an agent's, with its `--why`, never counts as a hand mark, and a case report
182
+ built on it says so above its bottom line — which thing is the object is a
183
+ judgment, and the files say who made it.
184
+
185
+ Every command takes `--json`: one object alone on stdout — `command`, `inputs`,
186
+ `clip`, `files`, `results`, `no_power`, `needs`, `notes`, `exit`, `error` — and
187
+ everything meant for a person on stderr. Every command has its results as
188
+ fields — numbers with the unit in the name, never a sentence to parse — and what
189
+ it printed for a person beside them as `results.said`, written *from* those
190
+ fields. Each measuring command is a command line over one function, and `run`
191
+ calls the same ones (`mcdonald.stages`), so a number in a case report is the
192
+ number the stage's own command gives: `tests/test_cli.py` holds them equal to
193
+ the last digit, and `tests/test_gui.py` does the same for the window. Exit
194
+ codes tell an expected failure from a bug: 0 done, 1 a traceback (a bug), 2 the
195
+ command line was wrong, 3 the machine lacks ffmpeg or a window, 4 the input is
196
+ not there or is not a video, 5 nothing to work on (no marks; the link acquired
197
+ nothing). `mcdonald --help` lists them.
198
+
199
+ A track CSV needs a frame column (`frame`, `n` or `frame_n`) and an x/y column
200
+ pair in video pixels (`x_px,y_px` or `x,y`). `--auto-track` will attempt one for
201
+ you, but **look at the track sheet before building anything on it**: on the clip
202
+ this toolkit was developed against, the first automatic tracker spent seven
203
+ frames locked to a cloud feature 100 px from the object.
204
+
205
+ Long clips: `--n0/--n1` take a frame window and keep the absolute numbering.
206
+ Frames are extracted losslessly and are ~2 MB each, so point `--workdir`
207
+ somewhere with room if `/tmp` is small or a tmpfs.
208
+
209
+ ### Marking the object
210
+
211
+ Nothing here can decide which thing in the frame is the object, and it does
212
+ not pretend to. `mcdonald mark` opens a window on the extracted frames; two
213
+ clicks give the linker its seed and the velocity it cannot acquire on its own,
214
+ after which the automatic track covers the rest. It writes a marks JSON, a
215
+ track CSV the other commands read, and a magnified contact strip with the
216
+ marks drawn back onto the pixels — which is how a coordinate gets checked
217
+ rather than trusted.
218
+
219
+ On DOW-UAP-PR113 that is the entire human input: two clicks reproduce the
220
+ published 142 px/frame.
221
+
222
+ There are two windows over the same marks, and `--gui auto` (the default) takes
223
+ the first that will open.
224
+
225
+ - **The Qt window** (`pip install -e ".[gui]"`) is for the clip you have not
226
+ seen. A timeline over the whole clip, playback at true speed — it holds
227
+ 30 frames/s on lossless 1080p, skipping frames rather than stretching time if
228
+ it ever cannot, and saying how many — an overview of the clip as tiles (`o`),
229
+ the detector's candidates drawn on the frame (`c`), a loupe under the cursor,
230
+ and undo. The candidates are there to be looked at, not trusted: on PR148 the
231
+ detector ranks the reticle's corner marks alongside the ship.
232
+
233
+ Then **`l` links**: an automatic track through the marks, drawn over the clip
234
+ as it grows, with the linked frames shown on the timeline and the track strip
235
+ put in front of you at the end. The detector's scale and polarity are chosen
236
+ from the marks. Every mark is a seed: between two marks the track is linked
237
+ forward from one and backward from the other, and a frame where the two
238
+ disagree is drawn amber rather than settled quietly; before the first mark
239
+ and after the last it runs until the object is lost, and stops there rather
240
+ than starting again on the brightest thing in the frame. So after a loss,
241
+ mark the object where it reappears and press `l` again — the detector's work
242
+ is kept, and only new frames are computed. `object` and `object #2` are
243
+ linked together. `s` then also writes `<tag>_autotrack.csv`, which the other
244
+ commands take as `--track`. On PR113 the two documented clicks choose 21 px
245
+ dark, link frames 408–411 onto the vendored positions to 0.005 px, and give
246
+ back 141.4 px/frame against the published 142 — in the window, in about a
247
+ minute. The same step without a window is `mcdonald.autolink`, and
248
+ `layers`, `integrity` and `run` take a marks file directly as `--marks`:
249
+ from the command line that is the only way to track an object too fast for
250
+ `--auto-track`, which links 1 of PR113's frames where `--marks` links all 4.
251
+
252
+ For placing a mark finely: `ctrl`+arrows nudge it a pixel (`ctrl+shift`, a
253
+ tenth), and `shift`+click snaps it to the detector's nearest candidate. A
254
+ snapped mark says so, in the window, the JSON, the CSV and the contact strip:
255
+ it agrees with the detector because it *is* the detector's, so it must never
256
+ be mistaken for an independent hand mark.
257
+
258
+ A clip it has not seen is extracted first, behind a progress bar with a
259
+ Cancel on it. Where `--n0/--n1` do not say which part, it asks, and what it
260
+ asks with is a player: play the clip forward or backward at its true speed or
261
+ slower, step a frame or ten, drag the bar, press "Start here" and "End here"
262
+ at the frame on the screen, and read what that part will cost before anything
263
+ is extracted. Nothing is extracted to watch it: the frames come from an ffmpeg
264
+ pipe (`mcdonald/reel.py`) under the numbers extraction will give them. A long
265
+ clip is asked about even when it is all on disk already, because opening the
266
+ whole of it costs nothing and measuring the whole of it costs hours.
267
+
268
+ **It can look first.** Track → Find the object (`f`; `mcdonald look --propose`
269
+ from a command line) looks for what moves against the background — a double
270
+ difference on the registered background, compact residual peaks, chains at
271
+ constant velocity, marked down where several things go the same way at once,
272
+ which is a layer or a scale that scrolls, and where the frame itself shows an
273
+ edge or a stroke and not a spot with background all round it — and lists what
274
+ it finds, best first, each as a strip of the clip's own pixels. It shows the
275
+ best few and says how many more it kept (`--more`). "This is it" places marks
276
+ along one and links from them; a click is then the correction rather than the
277
+ first step. It proposes and does not decide: no detector can say which thing
278
+ is the object, so a mark taken from a proposal is recorded as `proposed`,
279
+ never counts as a hand mark, and a report built on it says the suggestion was
280
+ the detector's and the yes a person's. Against every clip with a recorded
281
+ track: on PR149 the contact is the one strong proposal (0.4 px from the hand
282
+ workup; taken and linked, 20.1 px/frame for the published 20.2); on PR144,
283
+ where the sensor follows the object and only the background moves, on PR142
284
+ and on PR148, likewise, the second row scoring a fiftieth of the first; on
285
+ PR113, a four-frame transit past a scrolling heading tape under a pan, it is
286
+ first by a narrow margin, and weak — it was sixth to eleventh until the frame
287
+ was asked whether each thing is a spot — and taken, links to the vendored
288
+ track exactly; on PR055 at its true size, a black disc 24 px across that
289
+ moves 1.5 px a frame, it is first and strong, and its marks are on the disc's
290
+ centre and not on its rim, because a proposal's positions are the thing's
291
+ own (`propose.thing_at`) and not the residual's — the ×3 copy of the same
292
+ scene, a 72 px disc, still cancels in the double difference and is not on the
293
+ list. A proposal's marks are for the linker: `tools/find_rank.py --link`
294
+ holds Find and the link from its marks against every clip with a recorded
295
+ track, and is what to run after any change. About 0.2 s a frame, shown as
296
+ it goes.
297
+
298
+ **With no terminal.** `mcdonald-gui` starts the same window the other way
299
+ round: it asks for the clip (a file, or a catalog id: a PURSUE video not on
300
+ the computer yet is downloaded, after asking), asks which part, keeps its
301
+ cases in the storage folder (`Documents/mcdonald/<tag>` until changed on the
302
+ first screen) and shows where, and says what goes
303
+ wrong in a dialog instead of printing it. Everything a flag does is in the
304
+ File menu — Open a video, Open by catalog name, Open marks (`--load`), Save to
305
+ a different folder (`--out`) — and every key is in the menus and under Help →
306
+ Keys and mouse, which are made from one table (`mcdonald/actions.py`) together
307
+ with `mcdonald mark --help`. What the window says is written in plain words,
308
+ for someone outside the field: a video, not a clip; a spot the computer found,
309
+ not a detector's candidate; "frame", "mark", "track" and "link" are the four
310
+ words of the trade it keeps, and Help → Getting started says what each means
311
+ first. (The case report and the lines each stage prints are the measurement's
312
+ own words, and are not part of that yet.) **Measure → Measure this video** is the rest of the
313
+ job: it saves the marks and the link, asks what you know that the pixels cannot
314
+ say (the form is made from the rows `mcdonald run`'s options are made from),
315
+ says what the slow stages will cost, and makes the case on a thread of its own.
316
+ While it runs there is a bar that counts where the step can count (frame
317
+ pairs, tiles) and runs to and fro where it cannot, the stage it is on, the time
318
+ gone and the time left in the step, and a Stop that ends the step under way;
319
+ and it measures the frames round the track unless asked for everything that is
320
+ open, because someone who opened a whole clip to find a four-frame transit has
321
+ hours of frames open. (The command line says the same on stderr.)
322
+ The track sheet is put on the screen first, with the question every number
323
+ after it depends on — is the circle on the object in every frame? — and
324
+ closing it unanswered is a no, which the report records as provisional. The
325
+ case report opens beside the window when it is done, and Measure → Open the
326
+ case folder finds the files. `mcdonald-gui --desktop-entry`, or Help in the
327
+ window, adds it to the applications menu on Linux. It needs the `gui` extra
328
+ installed once. On macOS every suite passes on GitHub's runner
329
+ (`.github/workflows/platforms.yml`, run by hand or by `git push -f origin HEAD:macos-ci`);
330
+ it has not yet been run on Windows.
331
+ - **The matplotlib window** (`--gui mpl`) needs nothing beyond what the package
332
+ already depends on. It steps at about 11 frames/s on 1080p, which is ample
333
+ when you already know which frames to look at.
334
+
335
+ Both keep every mark in the same `MarkSet`, save through the same function, and
336
+ are held to the same checks by `tests/test_gui.py`. In both, a frame is a
337
+ lossless PNG named by its frame number; there is no video element to disagree
338
+ with ffmpeg about which frame is on screen, playback included.
339
+
340
+ ### The gate
341
+
342
+ `run` makes a track sheet — every frame tiled with the tracked object circled —
343
+ and treats looking at it as a prerequisite, not an option. Without
344
+ `--i-looked`, every object measurement in the report is stamped
345
+ **provisional**. There is no flag that skips making the sheet.
346
+
347
+ This is not ceremony. On the clip this toolkit was developed against, the first
348
+ automatic tracker spent seven frames locked to a cloud feature 100 px from the
349
+ object and produced a clean, plausible, wrong rate. The sheet is how that is
350
+ caught, and it takes about ten seconds to look at.
351
+
352
+ ### Reading the results
353
+
354
+ `docs/method.md` is the method note: what each measurement means, how each one
355
+ fails, and the traps the library is built around. Read it before quoting a
356
+ number. The short version of the three that matter most:
357
+
358
+ 1. **Never quote a rate "against the background" without naming the layer.** Sea
359
+ and cloud tops moved 98 px/s apart on the clip this came from; a single
360
+ consensus returns a blend and names neither.
361
+ 2. **Pixels per second become metres per second only through an angular scale
362
+ *k* and a range *R*.** Neither is usually recoverable from a clip. A speed
363
+ quoted without both sourced is not a measurement.
364
+ 3. **NO POWER is a verdict.** A clip that cannot decide a test has not passed
365
+ it, and those tests are reported, never dropped.
366
+
367
+ ### Provenance, and the PURSUE videos
368
+
369
+ The tools run on any file. The package also carries the list of the U.S. DoW
370
+ PURSUE release's videos (war.gov/UFO), so a record id works with nothing set up:
371
+ the first time a video is named, it is downloaded from DVIDS into the storage
372
+ folder (below), and the integrity report quotes the releasing body's own words,
373
+ including any alteration statement, alongside the pixel tests:
374
+
375
+ ```bash
376
+ mcdonald integrity PR144 # downloads PR144 the first time (31 MB)
377
+ ```
378
+
379
+ A mirror of your own, or a catalog of another release, is named instead with
380
+ `MCDONALD_CATALOG` (`none` for no catalog at all):
381
+
382
+ ```bash
383
+ export MCDONALD_CATALOG=/path/to/pursue_index/records.csv
384
+ ```
385
+
386
+ Writing another backend is a subclass with one method; see
387
+ `src/mcdonald/catalog.py`. A clip that is not in the catalog gets "no record
388
+ for this file": nothing is borrowed from someone else's release.
389
+
390
+ ### Where the large files go
391
+
392
+ Downloaded videos and each video's frames (a lossless 1080p frame is most of a
393
+ megabyte, so a whole clip is gigabytes) go in one storage folder:
394
+ `Documents/mcdonald` unless `MCDONALD_HOME` says otherwise, with `videos/` and
395
+ `frames/` inside it. In the window it is shown on the first screen, with a
396
+ button to change it. `--workdir DIR` puts one command's frames somewhere else.
397
+
398
+ This matters more than it looks. On a disclosed digital recreation, every pixel
399
+ test returned NO POWER or INCONCLUSIVE — **the record identified the clip, not
400
+ the pixels.** Custody and disclosure are not things pixel forensics can replace.
401
+
402
+ ## What it cannot do
403
+
404
+ `integrity` shows whether a clip and the object in it behave like the output of
405
+ one sensor chain. It catches an object pasted, AI-inserted or animated onto
406
+ footage that already existed. **It cannot exclude a composite made upstream of
407
+ the symbology by someone who modelled exposure, shake, gain and parallax.**
408
+ Nothing in the pixels can; that is a custody question — the original recording
409
+ with its metadata, and the mission report. Say so whenever you quote a result.
410
+
411
+ The synthetic-insert self-test is what gives a PASS its meaning: the tool
412
+ animates a fake object over the same frames and runs the same tests on it. If
413
+ the fake passes too, that test has no discriminating power on your clip,
414
+ whatever its verdict says. Note the honest limit: no clip with a *known real*
415
+ insert was available to validate against, so the synthetic stands in for one,
416
+ and it models a naive overlay rather than a match-moved physical composite.
417
+
418
+ ## Roadmap
419
+
420
+ Working (0.2): the `run` driver and the case report; background layers; clip
421
+ integrity; track verification; symbology (boresight, north pointer, corner
422
+ brackets); angular scale (graticule, in-frame reference, zoom chain, the FOV
423
+ ladder); the kinematic reduction; object-versus-texture co-motion; one figure
424
+ style; a catalog, with the PURSUE videos' list shipped and downloaded on demand.
425
+
426
+ Next: automatic tracking good enough to trust without hand marks (the current
427
+ `--auto-track` needs its sheet checked every time); the mode annunciator and
428
+ the data bar; batch mode over a whole catalog; the dimensionless-number
429
+ reduction.
430
+
431
+ ## License
432
+
433
+ **BSD 3-Clause** ([LICENSE](LICENSE)), copyright Jacob Haqq Misra, chosen
434
+ 2026-09-25 when the repository went public: the scientific-Python norm, and
435
+ its third clause keeps the author's name off a derived version's promotion
436
+ without permission, which matters on this subject.
437
+
438
+ A dependency's licence could have constrained that choice: anything GPL linked
439
+ into the package would have forced the package GPL. That is why the marking GUI
440
+ uses **PySide6 (LGPL)** rather than PyQt (GPL or commercial), and only as an
441
+ optional extra — see `docs/handoff-gui.md`. Keep it that way.
@@ -0,0 +1,68 @@
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/haqqmisra/mcdonald/main/docs/logo-dark.png">
3
+ <img alt="mcDonald UAP Toolkit" src="https://raw.githubusercontent.com/haqqmisra/mcdonald/main/docs/logo-light.png" width="420">
4
+ </picture>
5
+
6
+ **mcdonald** is a frame-by-frame analysis toolkit for measuring the kinematics of an unknown
7
+ object in a single-camera video.
8
+
9
+ > "Science is in default for having failed to mount any truly adequate studies of this problem."
10
+ >
11
+ > — James E. McDonald
12
+
13
+ **Status:** early test version (0.2.4), tried on Linux and macOS; Windows is next.
14
+
15
+ ## Install
16
+
17
+ 1. Prerequisite: make sure **ffmpeg** is installed:
18
+ `brew install ffmpeg` (Mac), `winget install Gyan.FFmpeg` (Windows),
19
+ `sudo apt install ffmpeg` or `sudo dnf install ffmpeg` (Linux).
20
+ 2. Install **mcdonald** (needs Python 3.10 or newer):
21
+
22
+ ```bash
23
+ python3 -m pip install "mcdonald[gui]"
24
+ ```
25
+
26
+ 3. Check your setup, which also provides instructions on what to do next:
27
+
28
+ ```bash
29
+ mcdonald setup
30
+ ```
31
+
32
+ When a newer version is out, `mcdonald-gui` offers to update itself when it starts, and the
33
+ command line prints the command to update with.
34
+
35
+ New to Python or the terminal? [docs/install.md](https://github.com/haqqmisra/mcdonald/blob/main/docs/install.md) walks through each step in
36
+ detail.
37
+
38
+ ## Use
39
+
40
+ **mcdonald** has a graphical interface and a command-line interface. The software is intended
41
+ to be driven by human users and/or AI agents.
42
+
43
+ The graphical interface is designed to prompt human users through the steps of analysis. To
44
+ start the graphical interface:
45
+
46
+ ```bash
47
+ mcdonald-gui
48
+ ```
49
+
50
+ The command-line interface is designed for developers, advanced users, and AI agents. To start
51
+ the command-line interface and see a summary of options:
52
+
53
+ ```bash
54
+ mcdonald
55
+ ```
56
+
57
+ ### Prompting an agent
58
+
59
+ You can invoke an AI agent to assist at any point in the analysis, whether before or after a
60
+ human has looked at the video. Here is a sample prompt that you can pass to your favorite
61
+ command-line AI model:
62
+
63
+ > Please use the mcdonald toolkit to analyze the PR144 video released under PURSUE. Run
64
+ > `mcdonald readme` to get started.
65
+
66
+ ## License
67
+
68
+ Copyright (c) 2026 Jacob Haqq Misra. Released under the [BSD 3-Clause License](https://github.com/haqqmisra/mcdonald/blob/main/LICENSE).