visualdynamics 0.1.0a2__tar.gz → 0.1.0a4__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 (133) hide show
  1. {visualdynamics-0.1.0a2/src/visualdynamics.egg-info → visualdynamics-0.1.0a4}/PKG-INFO +16 -13
  2. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/README.md +14 -12
  3. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/pyproject.toml +8 -1
  4. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/__init__.py +4 -4
  5. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/author.py +3 -3
  6. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/averaging.py +9 -9
  7. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/channel_table.py +1 -1
  8. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/compliance.py +6 -6
  9. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/data.py +19 -19
  10. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/detect.py +7 -7
  11. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/fem.py +22 -22
  12. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/filters.py +3 -3
  13. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/geometry.py +12 -12
  14. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/kurtosis.py +7 -7
  15. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/merge.py +1 -1
  16. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/modal_fit.py +17 -17
  17. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/octave.py +19 -19
  18. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/replication.py +11 -11
  19. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/report.py +28 -28
  20. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/rigid.py +16 -16
  21. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/shapes.py +3 -3
  22. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/shocks.py +13 -13
  23. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/sine.py +10 -10
  24. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/tables.py +1 -1
  25. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/transform.py +3 -3
  26. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/unit_choices.py +3 -3
  27. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/wavelet.py +174 -39
  28. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/deform.py +9 -9
  29. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/demo/drone.py +48 -48
  30. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/demo/plate.py +1 -1
  31. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/__init__.py +2 -2
  32. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/app_icon.py +6 -6
  33. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/author_panel.py +2 -2
  34. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/averaging_panel.py +8 -8
  35. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/console.py +3 -3
  36. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/disclaimer.py +3 -3
  37. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/drawer_tab.py +6 -6
  38. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/filter_panel.py +3 -3
  39. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/glyphs.py +2 -2
  40. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/icons.py +19 -19
  41. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/main_window.py +86 -84
  42. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/object_tables.py +3 -3
  43. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/panes.py +14 -14
  44. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/preferences.py +2 -2
  45. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/project_tree.py +9 -9
  46. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/record_grid.py +11 -11
  47. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/report_editor.py +4 -4
  48. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/rigid_panel.py +6 -6
  49. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/settings_panel.py +1 -1
  50. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/shock_panel.py +7 -7
  51. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/stage_drag.py +1 -1
  52. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/tables.py +10 -10
  53. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/truncate_panel.py +2 -2
  54. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/wavelet_panel.py +3 -3
  55. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/windows.py +2 -2
  56. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/__init__.py +2 -2
  57. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/excel.py +1 -1
  58. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/exodus.py +16 -10
  59. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/native.py +3 -3
  60. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/rattlesnake.py +8 -4
  61. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/rattlesnake_specification.py +1 -1
  62. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/sep005.py +1 -1
  63. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/step.py +4 -4
  64. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/stl.py +1 -1
  65. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/threemf.py +3 -3
  66. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/names.py +2 -2
  67. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/plot/__init__.py +77 -69
  68. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/plot/averaging.py +8 -8
  69. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/plot/bands.py +5 -5
  70. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/plot/bars.py +15 -15
  71. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/plot/filtering.py +6 -6
  72. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/plot/scalogram.py +10 -16
  73. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/plot/shocks.py +2 -2
  74. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/plot/truncation.py +3 -3
  75. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/project.py +13 -13
  76. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/report/__init__.py +41 -52
  77. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/report/page.py +45 -45
  78. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/theme.py +29 -20
  79. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/units.py +79 -4
  80. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/update.py +27 -1
  81. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/__init__.py +1 -1
  82. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/animate.py +9 -9
  83. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/banded.py +4 -4
  84. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/geometry.py +22 -22
  85. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/mac_bars.py +11 -11
  86. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/marks.py +18 -18
  87. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/paired.py +2 -2
  88. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/pick.py +5 -5
  89. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/rigid.py +4 -4
  90. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/scalogram.py +9 -15
  91. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/sinespec.py +9 -9
  92. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/waterfall.py +5 -5
  93. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4/src/visualdynamics.egg-info}/PKG-INFO +16 -13
  94. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics.egg-info/requires.txt +1 -0
  95. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/LICENSE +0 -0
  96. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/setup.cfg +0 -0
  97. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/__main__.py +0 -0
  98. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/compatibility.py +0 -0
  99. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/__init__.py +0 -0
  100. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/correlate.py +0 -0
  101. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/entities.py +0 -0
  102. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/matches.py +0 -0
  103. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/photos.py +0 -0
  104. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/srs.py +0 -0
  105. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/truncate.py +0 -0
  106. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/core/validate.py +0 -0
  107. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/decimate.py +0 -0
  108. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/demo/__init__.py +0 -0
  109. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/editors.py +0 -0
  110. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/icons/drawer-tab-shape.svg +0 -0
  111. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/icons/drawer-tab.svg +0 -0
  112. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/octave_panel.py +0 -0
  113. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/gui/toolbars.py +0 -0
  114. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/adf.py +0 -0
  115. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/exporters.py +0 -0
  116. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/femap.py +0 -0
  117. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/nastran.py +0 -0
  118. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/photo_files.py +0 -0
  119. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/punch.py +0 -0
  120. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/report_template.py +0 -0
  121. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/sdynpy_data.py +0 -0
  122. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/sdynpy_npz.py +0 -0
  123. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/sdynpy_shapes.py +0 -0
  124. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/sniffing.py +0 -0
  125. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/io/unv.py +0 -0
  126. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/py.typed +0 -0
  127. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/report/markdown.py +0 -0
  128. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/rotate.py +0 -0
  129. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics/viz/_quiet.py +0 -0
  130. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics.egg-info/SOURCES.txt +0 -0
  131. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics.egg-info/dependency_links.txt +0 -0
  132. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics.egg-info/entry_points.txt +0 -0
  133. {visualdynamics-0.1.0a2 → visualdynamics-0.1.0a4}/src/visualdynamics.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: visualdynamics
3
- Version: 0.1.0a2
3
+ Version: 0.1.0a4
4
4
  Summary: Structural Dynamics Visualization: a units-aware structural dynamics analysis toolset
5
5
  Author-email: Brandon Zwink <bzwink@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -22,6 +22,7 @@ Requires-Python: >=3.12
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: numpy
25
+ Requires-Dist: certifi
25
26
  Requires-Dist: scipy
26
27
  Requires-Dist: h5py
27
28
  Requires-Dist: netCDF4
@@ -55,7 +56,7 @@ Dynamic: license-file
55
56
 
56
57
  [![tests](https://github.com/visualdynamics/visualdynamics/actions/workflows/ci.yml/badge.svg)](https://github.com/visualdynamics/visualdynamics/actions/workflows/ci.yml)
57
58
  [![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
58
- [![licence: GPL-3.0-or-later](https://img.shields.io/badge/licence-GPL--3.0--or--later-blue.svg)](LICENSE)
59
+ [![license: GPL-3.0-or-later](https://img.shields.io/badge/license-GPL--3.0--or--later-blue.svg)](LICENSE)
59
60
  [![python: 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](pyproject.toml)
60
61
 
61
62
  Units-aware structural dynamics analysis toolset. Geometry, test data (time
@@ -84,8 +85,8 @@ latest versions rather than pinned.
84
85
 
85
86
  **[Documentation](https://visualdynamics.org/documentation/)** —
86
87
  getting started, guides, and a generated API reference, a page of
87
- [visualdynamics.org](https://visualdynamics.org) from
88
- the first release; the [source](docs/README.md) is in the tree and GitHub
88
+ [visualdynamics.org](https://visualdynamics.org) rebuilt with every
89
+ release; the [source](docs/README.md) is in the tree and GitHub
89
90
  renders it well enough to read. The application itself, and its
90
91
  downloads, are at [visualdynamics.org](https://visualdynamics.org).
91
92
  To build the site locally:
@@ -115,7 +116,7 @@ What it does today: import sdynpy/exodus/UNV/Rattlesnake files (declaring
115
116
  units where the source is silent), write a specification back out as
116
117
  the target Rattlesnake loads, view everything as grids, plots, CMIF
117
118
  and coherence maps, animate shapes and time data on geometry, mark
118
- measurement DOFs with labelled axis-colored arrows, **fit modal models to
119
+ measurement DOFs with labeled axis-colored arrows, **fit modal models to
119
120
  FRFs interactively** (residual CMIF, one mode at a time, MAC beside the
120
121
  table), compare shape sets by cross-MAC with phase-aligned overlay
121
122
  animation, compute averaged spectra and PSDs from multi-average time
@@ -146,9 +147,11 @@ editable installs record it too, so a move wants those rewritten and
146
147
 
147
148
  ## Install
148
149
 
149
- Nothing is published — the repository is private, and the name on
150
- PyPI holds only an empty placeholder — so installation is from a
151
- clone:
150
+ The packaged application — one download per platform, nothing else
151
+ to install — is on the [downloads
152
+ page](https://visualdynamics.org/downloads). The library is on PyPI
153
+ as `visualdynamics` (`pip install visualdynamics`, Python 3.12 or
154
+ newer). To work on it, install from a clone:
152
155
 
153
156
  ```bash
154
157
  pip install -e .
@@ -177,8 +180,8 @@ application: it runs the checkout's own interpreter, so an edit to the
177
180
  source is live on the next launch, and it points at that checkout by
178
181
  absolute path. Move the repository and run it again.
179
182
 
180
- The distribution name and the import will be the same word whenever
181
- there is a release. The project was called `vibe` until August 2026,
183
+ The distribution name and the import are the same word. The project
184
+ was called `vibe` until August 2026,
182
185
  which could never have been published under that name — `vibe` belongs
183
186
  to an unrelated package on PyPI.
184
187
 
@@ -201,10 +204,10 @@ satisfy.
201
204
 
202
205
  Help that is not code is welcome too — advice, review, measurements, a
203
206
  bug report that turns out to be a real one. It is recorded in
204
- [ACKNOWLEDGEMENTS.md](ACKNOWLEDGEMENTS.md), which is thanks rather
207
+ [ACKNOWLEDGMENTS.md](ACKNOWLEDGMENTS.md), which is thanks rather
205
208
  than authorship.
206
209
 
207
- ## Licence
210
+ ## License
208
211
 
209
212
  Copyright (c) 2026 Brandon Zwink. Visual Dynamics is free software
210
213
  under the [GNU General Public License, version 3 or any later
@@ -217,5 +220,5 @@ or in what the software tells you about it.
217
220
  Other terms are available from the copyright holder
218
221
  (contact@visualdynamics.org). Contributions are accepted under
219
222
  [CLA.md](CLA.md), which is what keeps that possible; see
220
- [CONTRIBUTING.md](CONTRIBUTING.md). Third-party licences are listed in
223
+ [CONTRIBUTING.md](CONTRIBUTING.md). Third-party licenses are listed in
221
224
  [NOTICE.md](NOTICE.md).
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![tests](https://github.com/visualdynamics/visualdynamics/actions/workflows/ci.yml/badge.svg)](https://github.com/visualdynamics/visualdynamics/actions/workflows/ci.yml)
4
4
  [![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
5
- [![licence: GPL-3.0-or-later](https://img.shields.io/badge/licence-GPL--3.0--or--later-blue.svg)](LICENSE)
5
+ [![license: GPL-3.0-or-later](https://img.shields.io/badge/license-GPL--3.0--or--later-blue.svg)](LICENSE)
6
6
  [![python: 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](pyproject.toml)
7
7
 
8
8
  Units-aware structural dynamics analysis toolset. Geometry, test data (time
@@ -31,8 +31,8 @@ latest versions rather than pinned.
31
31
 
32
32
  **[Documentation](https://visualdynamics.org/documentation/)** —
33
33
  getting started, guides, and a generated API reference, a page of
34
- [visualdynamics.org](https://visualdynamics.org) from
35
- the first release; the [source](docs/README.md) is in the tree and GitHub
34
+ [visualdynamics.org](https://visualdynamics.org) rebuilt with every
35
+ release; the [source](docs/README.md) is in the tree and GitHub
36
36
  renders it well enough to read. The application itself, and its
37
37
  downloads, are at [visualdynamics.org](https://visualdynamics.org).
38
38
  To build the site locally:
@@ -62,7 +62,7 @@ What it does today: import sdynpy/exodus/UNV/Rattlesnake files (declaring
62
62
  units where the source is silent), write a specification back out as
63
63
  the target Rattlesnake loads, view everything as grids, plots, CMIF
64
64
  and coherence maps, animate shapes and time data on geometry, mark
65
- measurement DOFs with labelled axis-colored arrows, **fit modal models to
65
+ measurement DOFs with labeled axis-colored arrows, **fit modal models to
66
66
  FRFs interactively** (residual CMIF, one mode at a time, MAC beside the
67
67
  table), compare shape sets by cross-MAC with phase-aligned overlay
68
68
  animation, compute averaged spectra and PSDs from multi-average time
@@ -93,9 +93,11 @@ editable installs record it too, so a move wants those rewritten and
93
93
 
94
94
  ## Install
95
95
 
96
- Nothing is published — the repository is private, and the name on
97
- PyPI holds only an empty placeholder — so installation is from a
98
- clone:
96
+ The packaged application — one download per platform, nothing else
97
+ to install — is on the [downloads
98
+ page](https://visualdynamics.org/downloads). The library is on PyPI
99
+ as `visualdynamics` (`pip install visualdynamics`, Python 3.12 or
100
+ newer). To work on it, install from a clone:
99
101
 
100
102
  ```bash
101
103
  pip install -e .
@@ -124,8 +126,8 @@ application: it runs the checkout's own interpreter, so an edit to the
124
126
  source is live on the next launch, and it points at that checkout by
125
127
  absolute path. Move the repository and run it again.
126
128
 
127
- The distribution name and the import will be the same word whenever
128
- there is a release. The project was called `vibe` until August 2026,
129
+ The distribution name and the import are the same word. The project
130
+ was called `vibe` until August 2026,
129
131
  which could never have been published under that name — `vibe` belongs
130
132
  to an unrelated package on PyPI.
131
133
 
@@ -148,10 +150,10 @@ satisfy.
148
150
 
149
151
  Help that is not code is welcome too — advice, review, measurements, a
150
152
  bug report that turns out to be a real one. It is recorded in
151
- [ACKNOWLEDGEMENTS.md](ACKNOWLEDGEMENTS.md), which is thanks rather
153
+ [ACKNOWLEDGMENTS.md](ACKNOWLEDGMENTS.md), which is thanks rather
152
154
  than authorship.
153
155
 
154
- ## Licence
156
+ ## License
155
157
 
156
158
  Copyright (c) 2026 Brandon Zwink. Visual Dynamics is free software
157
159
  under the [GNU General Public License, version 3 or any later
@@ -164,5 +166,5 @@ or in what the software tells you about it.
164
166
  Other terms are available from the copyright holder
165
167
  (contact@visualdynamics.org). Contributions are accepted under
166
168
  [CLA.md](CLA.md), which is what keeps that possible; see
167
- [CONTRIBUTING.md](CONTRIBUTING.md). Third-party licences are listed in
169
+ [CONTRIBUTING.md](CONTRIBUTING.md). Third-party licenses are listed in
168
170
  [NOTICE.md](NOTICE.md).
@@ -38,6 +38,13 @@ classifiers = [
38
38
  ]
39
39
  dependencies = [
40
40
  "numpy",
41
+ # MPL-2.0, which is file-level copyleft on certifi's own files and
42
+ # nothing more for a program that ships them unmodified; already in
43
+ # every package as netCDF4's dependency. Declared because the update
44
+ # check imports it: the packaged OpenSSL's compiled-in certificate
45
+ # directory exists on nobody's machine, and this is the trust store
46
+ # it falls back to (2026-09-15).
47
+ "certifi",
41
48
  # BSD-3-Clause. Runtime, not test-only: the low-pass, the
42
49
  # integration, the filter view's response curve and the sine matched
43
50
  # filter all call it. It was declared a test dependency for a long
@@ -85,7 +92,7 @@ visualdynamics-gui = "visualdynamics.gui:main"
85
92
  [project.optional-dependencies]
86
93
  # scipy is also a runtime dependency (see `dependencies`); the suite
87
94
  # additionally uses it as an independent cross-check of the spectral
88
- # maths, which is a different job from calling it — where a method is
95
+ # math, which is a different job from calling it — where a method is
89
96
  # checked against scipy rather than delegated to it, the check is only
90
97
  # worth having because the implementation is our own.
91
98
  # sdypy-sep005 is the sdypy project's own SEP 005 validator (MIT),
@@ -7,7 +7,7 @@ from typing import Any
7
7
  # itself must pick the same one. Left alone in an environment that
8
8
  # also has PyQt5/PyQt6, pyqtgraph binds to one of those and every
9
9
  # brush and widget visualdynamics hands it is a foreign object — which is how
10
- # `data.plot()` from a plain script died on a colour it could not
10
+ # `data.plot()` from a plain script died on a color it could not
11
11
  # read. `QT_API` is the same question asked by qtpy, which is how
12
12
  # pyvistaqt picks. Both are set *only if unset*: a session that has
13
13
  # already chosen (sdynpy sets QT_API=pyqt5 at import) is left alone
@@ -16,7 +16,7 @@ from typing import Any
16
16
  _os.environ.setdefault('PYQTGRAPH_QT_LIB', 'PySide6')
17
17
  _os.environ.setdefault('QT_API', 'pyside6')
18
18
 
19
- # Qt Data Visualization is offered under GPLv3 or a commercial licence
19
+ # Qt Data Visualization is offered under GPLv3 or a commercial license
20
20
  # and nothing else — no LGPL (its own licensing page, read 2026-08-20).
21
21
  # Nothing here uses it, but qtpy imports it at startup for a
22
22
  # Windows-Qt5 compatibility alias, inside a suppress(ImportError) — so
@@ -76,7 +76,7 @@ from .units import (
76
76
  #: alpha, said in the version itself so the dialog, the About text, the
77
77
  #: installers and latest.json all say the same thing, and the first
78
78
  #: non-alpha release is offered as an upgrade (Brandon, 2026-09-11)
79
- __version__ = '0.1.0a2'
79
+ __version__ = '0.1.0a4'
80
80
 
81
81
 
82
82
  def launch_gui(*paths: str, in_process: bool | None = None) -> Any:
@@ -125,7 +125,7 @@ def _spawn_gui(paths=(), spawn=None):
125
125
  # the child is a fresh interpreter, but it inherits this one's
126
126
  # environment — including whatever chose a Qt here. Both are
127
127
  # overridden, not defaulted: sdynpy sets QT_API=pyqt5 at import,
128
- # and a child that honours it crashes mixing bindings.
128
+ # and a child that honors it crashes mixing bindings.
129
129
  environment = dict(_os.environ, PYQTGRAPH_QT_LIB='PySide6',
130
130
  QT_API='pyside6')
131
131
  launcher = ('import sys, visualdynamics; '
@@ -87,7 +87,7 @@ class SpecificationDraft:
87
87
  bands: One entry per channel: `{'warning': [(below, above)
88
88
  per segment], 'abort': [...], 'symmetric': bool, 'uniform':
89
89
  bool}`, the bands in decibels either side of the target,
90
- a segment being the span between two neighbouring
90
+ a segment being the span between two neighboring
91
91
  breakpoints. A channel's own constraints: `symmetric`
92
92
  holds above at minus below, `uniform` holds every segment
93
93
  at one band (Brandon, 2026-09-06: each channel its own
@@ -154,7 +154,7 @@ class SpecificationDraft:
154
154
  ) -> SpecificationDraft:
155
155
  """A starter at a shape set's modal coordinates `M1 … Mn`, each
156
156
  channel's quantity by the transform's unit rule — a unit rigid
157
- set's rotations per radian, a mass-normalised set's modal
157
+ set's rotations per radian, a mass-normalized set's modal
158
158
  quantity. A set with no mass unit gives its coordinates no
159
159
  unit, and a specification in nothing is refused.
160
160
 
@@ -945,7 +945,7 @@ class SpecificationDraft:
945
945
 
946
946
  def with_breakpoint(self, frequency: float) -> SpecificationDraft:
947
947
  """The same draft with a breakpoint at `frequency`, its levels
948
- read off the power law between its neighbours — or the end
948
+ read off the power law between its neighbors — or the end
949
949
  breakpoint's, past the ends.
950
950
 
951
951
  Parameters
@@ -43,7 +43,7 @@ _ALIASES = {'uniform': 'boxcar', 'rectangle': 'boxcar', 'none': 'boxcar',
43
43
  'blackman harris': 'blackmanharris'}
44
44
 
45
45
 
46
- #: how a frame is levelled before it is windowed. 'none' is the
46
+ #: how a frame is leveled before it is windowed. 'none' is the
47
47
  #: convention here and in sdynpy — the oracle pins it — while scipy's
48
48
  #: welch removes each segment's mean by default; offering the choice
49
49
  #: is scipy parity (Brandon, 2026-08-29). 'mean' subtracts the
@@ -54,7 +54,7 @@ DETRENDS = ('none', 'mean', 'linear')
54
54
  _DETREND_ALIASES = {'constant': 'mean', 'off': 'none'}
55
55
 
56
56
 
57
- def normalise_detrend(name: str | None) -> str:
57
+ def normalize_detrend(name: str | None) -> str:
58
58
  """The key for a detrend named any of the ways it might be."""
59
59
  if name is None:
60
60
  return 'none'
@@ -67,7 +67,7 @@ def normalise_detrend(name: str | None) -> str:
67
67
  return key
68
68
 
69
69
 
70
- def normalise_window(name: str | None) -> str:
70
+ def normalize_window(name: str | None) -> str:
71
71
  """The key for a window named any of the ways a file might name it."""
72
72
  if name is None:
73
73
  return 'boxcar'
@@ -114,14 +114,14 @@ def window_shape(name: str | None, length: int,
114
114
  trade reversed: the tables were verified identical to scipy's to
115
115
  machine epsilon at every length that can hold a frame before being
116
116
  deleted, and the sdynpy oracle now checks the windows through the
117
- whole spectral pipeline on every run. The one behavioural change is
117
+ whole spectral pipeline on every run. The one behavioral change is
118
118
  a one-sample window, which reads 1.0 where the old taper read its
119
119
  own first point — and a one-sample frame cannot exist
120
120
  (`Averaging` requires two).
121
121
  """
122
122
  from scipy.signal import get_window
123
123
 
124
- key = normalise_window(name)
124
+ key = normalize_window(name)
125
125
  if length < 1:
126
126
  raise ValueError('a window needs at least one sample')
127
127
  if key in WINDOW_PARAMETERS:
@@ -186,8 +186,8 @@ class Averaging:
186
186
  frames: int = 1
187
187
  start: float = 0.0
188
188
  detrend: str = 'none'
189
- #: the parameterised windows' own number — tukey's alpha, kaiser's
190
- #: beta — None for every window that takes none. A parameterised
189
+ #: the parameterized windows' own number — tukey's alpha, kaiser's
190
+ #: beta — None for every window that takes none. A parameterized
191
191
  #: window given None adopts its documented default, so a stored
192
192
  #: Averaging always carries the number it was computed with.
193
193
  window_parameter: float | None = None
@@ -206,9 +206,9 @@ class Averaging:
206
206
  object.__setattr__(self, 'overlap', float(self.overlap))
207
207
  object.__setattr__(self, 'frames', int(self.frames))
208
208
  object.__setattr__(self, 'start', float(self.start))
209
- object.__setattr__(self, 'window', normalise_window(self.window))
209
+ object.__setattr__(self, 'window', normalize_window(self.window))
210
210
  object.__setattr__(self, 'detrend',
211
- normalise_detrend(self.detrend))
211
+ normalize_detrend(self.detrend))
212
212
  settings = WINDOW_PARAMETERS.get(self.window)
213
213
  if settings is None:
214
214
  if self.window_parameter is not None:
@@ -483,7 +483,7 @@ class ChannelTable:
483
483
  The coordinate as the table has it, '101Z+'.
484
484
  new : str
485
485
  The coordinate to give it: a node number then a direction,
486
- normalised the way every DOF is, and refused when it is not
486
+ normalized the way every DOF is, and refused when it is not
487
487
  one — a table row may lack a node or a direction, but a
488
488
  correction typed by a person is whole.
489
489
  quantity : str, optional
@@ -129,7 +129,7 @@ def log_log_area(frequencies: ArrayLike, values: ArrayLike,
129
129
  # It matters because a specification is no longer always a
130
130
  # handful of breakpoints spanning decades. The PSD of a
131
131
  # transient target is a Specification with four thousand
132
- # lines a quarter-hertz apart, and neighbouring lines two
132
+ # lines a quarter-hertz apart, and neighboring lines two
133
133
  # decades apart over a frequency ratio of 1.00025 give an
134
134
  # exponent near twenty thousand: a**n overflowed, the
135
135
  # subtraction went inf - inf, and every channel's RMS error
@@ -215,7 +215,7 @@ def outside(lines: ArrayLike, values: ArrayLike,
215
215
 
216
216
  At the two ends it cannot be. A bin straddling the edge is covered
217
217
  by the specification for part of its width and by nothing for the
218
- rest, and reading the limit at the bin's centre either invents a
218
+ rest, and reading the limit at the bin's center either invents a
219
219
  requirement past the edge or drops a bin that is mostly inside. So
220
220
  a cut bin is judged on the part that is covered: the power the
221
221
  measurement holds over that stretch, `G` times the covered width,
@@ -275,7 +275,7 @@ def rms(frequencies: ArrayLike, values: ArrayLike) -> float:
275
275
 
276
276
  The widths come from the whole axis before any line is dropped, so
277
277
  a line that says nothing contributes nothing rather than having its
278
- bin quietly widened onto its neighbours. Bridging a gap would be
278
+ bin quietly widened onto its neighbors. Bridging a gap would be
279
279
  assuming what is in it.
280
280
  """
281
281
  frequencies = np.asarray(frequencies, dtype=float)
@@ -417,7 +417,7 @@ def detect_scale_db(specification: Specification, measured: DataArray,
417
417
  # not a statement about the commanded level — the broken-channel
418
418
  # test pins that such a channel cannot answer alone, and it must
419
419
  # not veto alone either. 2 dB of slack both ways lets a slightly
420
- # hot control sit under the commanded level without cancelling a
420
+ # hot control sit under the commanded level without canceling a
421
421
  # real run-up.
422
422
  plausible = [value for value in per_channel if value >= -2]
423
423
  if plausible and min(plausible) < candidate - 2:
@@ -546,7 +546,7 @@ def compare(specification: Specification, measured: DataArray,
546
546
  return {'lines': 0}
547
547
  # the bins the spectrum actually has. An octave-band one carries
548
548
  # its own — geometric, from a standard — and reading them off the
549
- # centres would be a hair out at every band and wrong at the ends.
549
+ # centers would be a hair out at every band and wrong at the ends.
550
550
  own = getattr(measured, 'bin_widths', None)
551
551
 
552
552
  # A measured line stands for its whole bin, half a width either
@@ -684,7 +684,7 @@ def compare_all(specification: Specification, measured: DataArray,
684
684
 
685
685
  #: The RMS dB deviation an SRS comparison is shaded past, either side.
686
686
  #: The same three decibels the level error either side of a
687
- #: specification uses, because it is the same judgement in the same
687
+ #: specification uses, because it is the same judgment in the same
688
688
  #: unit — how far a spectrum sits from what was asked for, and
689
689
  #: over-testing and under-testing are not the same fault.
690
690
  SRS_ERROR_DB = 3.0
@@ -426,7 +426,7 @@ class DataArray:
426
426
 
427
427
  A channel is a coordinate *and* a quantity — a drive point
428
428
  carries a load cell and an accelerometer at one DOF — and the
429
- rename is the channel's: a force labelled at the wrong node
429
+ rename is the channel's: a force labeled at the wrong node
430
430
  moves without taking the accelerometer at that node with it,
431
431
  and the other is changed explicitly if it should be (Brandon,
432
432
  2026-09-06). The channel moves wherever a record wears it, as
@@ -441,7 +441,7 @@ class DataArray:
441
441
  old : str
442
442
  The coordinate as it is, '101Z+'.
443
443
  new : str
444
- The coordinate to give it; normalised the way every DOF is
444
+ The coordinate to give it; normalized the way every DOF is
445
445
  ('101Z' is '101Z+'), and refused when it is not one.
446
446
  quantity : str, optional
447
447
  Which channel at `old`: 'acceleration', 'force' … as
@@ -764,7 +764,7 @@ class DataArray:
764
764
  Returns
765
765
  -------
766
766
  str
767
- The record's DOF, plus whatever tells it from its neighbours.
767
+ The record's DOF, plus whatever tells it from its neighbors.
768
768
  """
769
769
  if self.reference_dof is not None:
770
770
  return f'{self.response_dof[i]}/{self.reference_dof[i]}'
@@ -946,7 +946,7 @@ class DataArray:
946
946
 
947
947
  def plot_waterfall(self, records: Sequence[int] | None = None,
948
948
  **kwargs: Any) -> Any:
949
- """The records spread along a depth axis, coloured by level —
949
+ """The records spread along a depth axis, colored by level —
950
950
  the plot bar's 3-D reading, scripted. `screenshot=` renders
951
951
  headless to a file; without it a window of the app's own 3-D
952
952
  pane opens.
@@ -1166,7 +1166,7 @@ class TimeHistory(DataArray):
1166
1166
  for f in range(averaging.frames):
1167
1167
  at = first + f * averaging.hop
1168
1168
  frame = self.ordinate[i][at:at + n]
1169
- # levelled before it is windowed, exactly where
1169
+ # leveled before it is windowed, exactly where
1170
1170
  # scipy's welch does it; 'none' is the default and
1171
1171
  # the sdynpy convention, which the oracle pins
1172
1172
  if averaging.detrend == 'mean':
@@ -1267,7 +1267,7 @@ class TimeHistory(DataArray):
1267
1267
  above the content is the reach-for-first case; the filter view
1268
1268
  offers high- and band-pass beside it.
1269
1269
 
1270
- A judgement, not a detection — nothing in the record says where
1270
+ A judgment, not a detection — nothing in the record says where
1271
1271
  its content stops being signal. A tenth of the rate is where
1272
1272
  the integrate/differentiate round trip was measured at ~2% RMS
1273
1273
  (core.filters), and it sits below the mounted-resonance range a
@@ -2122,7 +2122,7 @@ class Frf(DataArray):
2122
2122
  Parameters
2123
2123
  ----------
2124
2124
  shapes : ShapeSet, optional
2125
- A modal fit, drawn as the synthesised CMIF over the measured one.
2125
+ A modal fit, drawn as the synthesized CMIF over the measured one.
2126
2126
  **kwargs
2127
2127
  Passed through to the plotting layer.
2128
2128
 
@@ -2281,10 +2281,10 @@ def density_ratio(signal: Any, floor: Any):
2281
2281
  class Psd(DataArray):
2282
2282
  #: the width of the band each line stands for, when the lines are
2283
2283
  #: not evenly spaced. `None` for a narrowband spectrum, where the
2284
- #: bins are the midpoints between neighbours and inferring them is
2284
+ #: bins are the midpoints between neighbors and inferring them is
2285
2285
  #: exact. An octave-band spectrum sets it: its bins are geometric,
2286
- #: its edges are a standard's and not its neighbours', and reading
2287
- #: them off the centres would be a hair out at every band and wrong
2286
+ #: its edges are a standard's and not its neighbors', and reading
2287
+ #: them off the centers would be a hair out at every band and wrong
2288
2288
  #: at the two ends.
2289
2289
  """Power spectral density: the declared unit is the engineering unit
2290
2290
  whose square-per-Hz the values are in (declare 'g' for g^2/Hz).
@@ -2405,7 +2405,7 @@ class Psd(DataArray):
2405
2405
  operating deflection shape: the dominant eigenvector of the
2406
2406
  cross-spectral matrix per line, each channel's phase relative
2407
2407
  to the others. An autospectrum set has no phase, so it shows
2408
- the envelope instead: two copies deflected ±sqrt(PSD), colour
2408
+ the envelope instead: two copies deflected ±sqrt(PSD), color
2409
2409
  reading dB below the loudest node at any line. Defaults to the
2410
2410
  strongest line; `frequency` picks another, `quantity` which
2411
2411
  measurement deflects.
@@ -2483,10 +2483,10 @@ class Psd(DataArray):
2483
2483
 
2484
2484
  #: the width of the band each line stands for, when the lines are
2485
2485
  #: not evenly spaced. None for a narrowband spectrum, whose bins
2486
- #: are the midpoints between neighbours and exactly inferable. An
2486
+ #: are the midpoints between neighbors and exactly inferable. An
2487
2487
  #: octave-band spectrum sets it: its bins are geometric, its edges
2488
- #: are a standard's rather than its neighbours', and reading them
2489
- #: off the centres is a hair out at every band and plainly wrong at
2488
+ #: are a standard's rather than its neighbors', and reading them
2489
+ #: off the centers is a hair out at every band and plainly wrong at
2490
2490
  #: the two ends.
2491
2491
  bandwidth: np.ndarray | None = None
2492
2492
 
@@ -2512,7 +2512,7 @@ class Psd(DataArray):
2512
2512
  mean-square content that falls in it, divided by its own width,
2513
2513
  so the area under the spectrum — and therefore the RMS it
2514
2514
  carries — is unchanged. Reading the narrowband curve at each
2515
- band centre would throw away everything between the centres.
2515
+ band center would throw away everything between the centers.
2516
2516
 
2517
2517
  The band grid is absolute (see `visualdynamics.core.octave`), so this
2518
2518
  needs no specification to be told about: the frequency range
@@ -2544,7 +2544,7 @@ class Psd(DataArray):
2544
2544
  'octave bands need a spectrum with frequency lines above '
2545
2545
  'zero; this one has ' + str(positive.size))
2546
2546
  # the range the bands have to span is the range the *bins*
2547
- # cover, not the range the centres do. A line at 0.5 Hz stands
2547
+ # cover, not the range the centers do. A line at 0.5 Hz stands
2548
2548
  # for a bin reaching down to 0.25, and a grid that started at
2549
2549
  # the line would leave that half outside every band — which is
2550
2550
  # a small loss of area and an unnecessary one.
@@ -2554,10 +2554,10 @@ class Psd(DataArray):
2554
2554
  if low is None else float(low)
2555
2555
  high = float(beyond.max()) if high is None else float(high)
2556
2556
  per_octave = PER_OCTAVE if per_octave is None else int(per_octave)
2557
- centres, widths, bounds = bands(low, high, per_octave)
2557
+ centers, widths, bounds = bands(low, high, per_octave)
2558
2558
  banded = resample(frequencies, self.ordinate, bounds, widths,
2559
2559
  source=self.bin_bounds())
2560
- out = Psd(centres, banded,
2560
+ out = Psd(centers, banded,
2561
2561
  response_dof=list(self.response_dof),
2562
2562
  reference_dof=(None if self.reference_dof is None
2563
2563
  else list(self.reference_dof)),
@@ -2577,7 +2577,7 @@ class Psd(DataArray):
2577
2577
  def bin_widths(self) -> np.ndarray:
2578
2578
  """The width of every line's own bin.
2579
2579
 
2580
- Its own when it has one, the midpoints between neighbours when
2580
+ Its own when it has one, the midpoints between neighbors when
2581
2581
  it does not — so a caller integrating a spectrum never has to
2582
2582
  ask which kind it is holding.
2583
2583
  """
@@ -6,7 +6,7 @@ environment stopped. Averaging the lot mixes them into one PSD that
6
6
  describes none of them. This works out which stretch to use.
7
7
 
8
8
  The record is compressed to one number per hop before anything else is
9
- decided — a level, in dB — and every judgement after that is made on
9
+ decided — a level, in dB — and every judgment after that is made on
10
10
  that short series. That is what keeps it quick on the sets where it
11
11
  matters: three hundred channels of a ten-minute run cost one pass, and
12
12
  the rest is arithmetic on a few thousand points.
@@ -15,11 +15,11 @@ Two things make the compression trustworthy rather than merely small:
15
15
 
16
16
  - the consensus is the median across channels, not the mean, so a
17
17
  channel that drops out or spikes or sits forty dB above its
18
- neighbours is one vote and not the answer;
18
+ neighbors is one vote and not the answer;
19
19
  - the mean is removed within each hop, so a DC offset or a slow drift
20
20
  is not read as level.
21
21
 
22
- Nothing normalises the levels against each other or centres the series.
22
+ Nothing normalizes the levels against each other or centers the series.
23
23
  Both were tried: the median across channels already does that work, and
24
24
  the threshold sweep walks every bin edge from the top, so scaling a
25
25
  whole record by a million moves the bins together and lands on the same
@@ -73,7 +73,7 @@ TOLERANCE = 2.0
73
73
  #: still sets it.
74
74
  TOP_QUANTILE = 95.0
75
75
 
76
- #: how many times a run is re-centred on itself and grown again. Three
76
+ #: how many times a run is re-centered on itself and grown again. Three
77
77
  #: is enough for anything seen: the first pass from a fragment's own
78
78
  #: median, the second from the median of what that reached, and the
79
79
  #: third to confirm it has stopped moving.
@@ -176,7 +176,7 @@ def hop_levels(ordinate: ArrayLike, hop: int,
176
176
  block = ordinate[:, first * hop:last * hop].reshape(
177
177
  channels, last - first, hop)
178
178
  mean = block.mean(axis=2)
179
- # einsum contracts without materialising the squares
179
+ # einsum contracts without materializing the squares
180
180
  power[:, first:last] = (
181
181
  np.einsum('cks,cks->ck', block, block) / hop - mean * mean)
182
182
 
@@ -218,7 +218,7 @@ def _grow(level, first, last, sigma, tolerance=TOLERANCE):
218
218
  Repeated until it settles, because the first median is the median of
219
219
  a fragment and a fragment of a wandering plateau sits wherever the
220
220
  threshold happened to cut it. Grown once from a median near the top
221
- of the wander it reaches only the upper half; re-centred on what it
221
+ of the wander it reaches only the upper half; re-centered on what it
222
222
  then holds, it reaches the rest.
223
223
  """
224
224
  for _ in range(GROW_PASSES):
@@ -241,7 +241,7 @@ def plateau(level: np.ndarray, sigma: float, want_hops: int,
241
241
  tolerance: float = TOLERANCE) -> tuple[int, int]:
242
242
  """The highest level the record holds for `want_hops` hops.
243
243
 
244
- Level alone is the wrong thing to maximise. A ten-second overload in
244
+ Level alone is the wrong thing to maximize. A ten-second overload in
245
245
  the middle of a two-minute test is the highest level in the record
246
246
  and the last thing anyone wants to average; sought that way it wins,
247
247
  and returns a fifth of the frames the test itself would have. So