visualdynamics 0.1.0a71__tar.gz → 0.1.0a72__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 (256) hide show
  1. {visualdynamics-0.1.0a71/src/visualdynamics.egg-info → visualdynamics-0.1.0a72}/PKG-INFO +1 -1
  2. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/__init__.py +1 -1
  3. visualdynamics-0.1.0a72/src/visualdynamics/core/amplitude_spectrum.py +455 -0
  4. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/author.py +231 -6
  5. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/averaging.py +135 -0
  6. visualdynamics-0.1.0a72/src/visualdynamics/core/breakpoints.py +316 -0
  7. visualdynamics-0.1.0a72/src/visualdynamics/core/chance.py +516 -0
  8. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/channel_table.py +38 -11
  9. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/compliance.py +11 -1
  10. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/data.py +449 -36
  11. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/filters.py +13 -4
  12. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/merge.py +6 -0
  13. visualdynamics-0.1.0a72/src/visualdynamics/core/resample.py +692 -0
  14. visualdynamics-0.1.0a72/src/visualdynamics/core/rms_check.py +661 -0
  15. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/shocks.py +2 -1
  16. visualdynamics-0.1.0a72/src/visualdynamics/core/signals.py +925 -0
  17. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/sine.py +27 -11
  18. visualdynamics-0.1.0a72/src/visualdynamics/core/sine_distortion.py +765 -0
  19. visualdynamics-0.1.0a72/src/visualdynamics/core/sine_frequency.py +411 -0
  20. visualdynamics-0.1.0a72/src/visualdynamics/core/sine_profile.py +964 -0
  21. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/stroke.py +17 -14
  22. visualdynamics-0.1.0a72/src/visualdynamics/core/transmissibility.py +441 -0
  23. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/decimate.py +6 -0
  24. visualdynamics-0.1.0a72/src/visualdynamics/gui/amplitude_panel.py +205 -0
  25. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/author_panel.py +255 -28
  26. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/averaging_panel.py +12 -3
  27. visualdynamics-0.1.0a72/src/visualdynamics/gui/chance_panel.py +132 -0
  28. visualdynamics-0.1.0a72/src/visualdynamics/gui/confidence_panel.py +162 -0
  29. visualdynamics-0.1.0a72/src/visualdynamics/gui/distortion_panel.py +112 -0
  30. visualdynamics-0.1.0a72/src/visualdynamics/gui/frequency_panel.py +125 -0
  31. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/icons.py +193 -1
  32. visualdynamics-0.1.0a72/src/visualdynamics/gui/levels_readout.py +255 -0
  33. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/main_window.py +1084 -10
  34. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/panes.py +244 -23
  35. visualdynamics-0.1.0a72/src/visualdynamics/gui/profile_panel.py +145 -0
  36. visualdynamics-0.1.0a72/src/visualdynamics/gui/resample_panel.py +211 -0
  37. visualdynamics-0.1.0a72/src/visualdynamics/gui/rms_check_panel.py +159 -0
  38. visualdynamics-0.1.0a72/src/visualdynamics/gui/signal_panel.py +356 -0
  39. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/sine_panel.py +23 -2
  40. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/statistics_panel.py +9 -3
  41. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/stroke_panel.py +2 -1
  42. visualdynamics-0.1.0a72/src/visualdynamics/gui/transmissibility_panel.py +107 -0
  43. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/native.py +7 -0
  44. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/rattlesnake.py +23 -4
  45. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/__init__.py +1138 -63
  46. visualdynamics-0.1.0a72/src/visualdynamics/plot/amplitude_spectrum.py +123 -0
  47. visualdynamics-0.1.0a72/src/visualdynamics/plot/breakpoints.py +284 -0
  48. visualdynamics-0.1.0a72/src/visualdynamics/plot/chance.py +223 -0
  49. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/replication_fit.py +4 -2
  50. visualdynamics-0.1.0a72/src/visualdynamics/plot/resample.py +218 -0
  51. visualdynamics-0.1.0a72/src/visualdynamics/plot/rms_check.py +161 -0
  52. visualdynamics-0.1.0a72/src/visualdynamics/plot/sine_distortion.py +198 -0
  53. visualdynamics-0.1.0a72/src/visualdynamics/plot/sine_frequency.py +203 -0
  54. visualdynamics-0.1.0a72/src/visualdynamics/plot/sine_profile.py +208 -0
  55. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/stroke.py +1 -1
  56. visualdynamics-0.1.0a72/src/visualdynamics/plot/transmissibility.py +297 -0
  57. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/project.py +264 -2
  58. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/report/__init__.py +51 -3
  59. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72/src/visualdynamics.egg-info}/PKG-INFO +1 -1
  60. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics.egg-info/SOURCES.txt +30 -0
  61. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/LICENSE +0 -0
  62. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/README.md +0 -0
  63. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/pyproject.toml +0 -0
  64. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/setup.cfg +0 -0
  65. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/__main__.py +0 -0
  66. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/compatibility.py +0 -0
  67. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/__init__.py +0 -0
  68. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/classical_pulse.py +0 -0
  69. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/correlate.py +0 -0
  70. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/detect.py +0 -0
  71. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/dwell.py +0 -0
  72. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/entities.py +0 -0
  73. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/fem.py +0 -0
  74. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/geometry.py +0 -0
  75. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/half_power.py +0 -0
  76. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/hexmesh.py +0 -0
  77. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/instrumentation.py +0 -0
  78. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/kurtosis.py +0 -0
  79. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/matches.py +0 -0
  80. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/mates.py +0 -0
  81. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/mesh.py +0 -0
  82. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/modal_fit.py +0 -0
  83. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/octave.py +0 -0
  84. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/photos.py +0 -0
  85. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/progress.py +0 -0
  86. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/replication.py +0 -0
  87. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/replication_fit.py +0 -0
  88. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/replication_segments.py +0 -0
  89. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/report.py +0 -0
  90. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/rigid.py +0 -0
  91. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/shaker.py +0 -0
  92. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/shapes.py +0 -0
  93. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/shock_check.py +0 -0
  94. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/signature.py +0 -0
  95. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/sine_tracking.py +0 -0
  96. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/snr.py +0 -0
  97. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/solids.py +0 -0
  98. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/sparse_solve.py +0 -0
  99. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/srs.py +0 -0
  100. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/sweep_rate.py +0 -0
  101. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/tables.py +0 -0
  102. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/tetmesh.py +0 -0
  103. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/time_statistics.py +0 -0
  104. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/tone_readout.py +0 -0
  105. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/transform.py +0 -0
  106. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/truncate.py +0 -0
  107. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/unit_choices.py +0 -0
  108. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/validate.py +0 -0
  109. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/core/wavelet.py +0 -0
  110. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/deform.py +0 -0
  111. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/demo/__init__.py +0 -0
  112. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/demo/barc.py +0 -0
  113. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/demo/drone.py +0 -0
  114. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/demo/frame_wing.py +0 -0
  115. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/demo/plate.py +0 -0
  116. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/demo/shapes.py +0 -0
  117. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/__init__.py +0 -0
  118. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/app_icon.py +0 -0
  119. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/ask.py +0 -0
  120. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/console.py +0 -0
  121. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/disclaimer.py +0 -0
  122. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/drawer_tab.py +0 -0
  123. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/dwell_panel.py +0 -0
  124. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/editors.py +0 -0
  125. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/filter_panel.py +0 -0
  126. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/glyphs.py +0 -0
  127. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/icons/drawer-tab-shape.svg +0 -0
  128. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/icons/drawer-tab.svg +0 -0
  129. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/instrumentation_panel.py +0 -0
  130. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/mate_panel.py +0 -0
  131. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/merge_panel.py +0 -0
  132. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/mesh_panel.py +0 -0
  133. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/mesh_solids_panel.py +0 -0
  134. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/message_bar.py +0 -0
  135. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/modes_panel.py +0 -0
  136. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/movie.py +0 -0
  137. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/object_tables.py +0 -0
  138. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/octave_panel.py +0 -0
  139. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/opengl.py +0 -0
  140. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/pattern_panel.py +0 -0
  141. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/preferences.py +0 -0
  142. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/project_tree.py +0 -0
  143. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/pulse_panel.py +0 -0
  144. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/record_grid.py +0 -0
  145. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/replication_fit_panel.py +0 -0
  146. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/report_editor.py +0 -0
  147. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/run_reports.py +0 -0
  148. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/segments_panel.py +0 -0
  149. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/settings_panel.py +0 -0
  150. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/shock_panel.py +0 -0
  151. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/signature_panel.py +0 -0
  152. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/srs_tolerance_panel.py +0 -0
  153. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/stage_drag.py +0 -0
  154. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/stream_window.py +0 -0
  155. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/sweep_rate_panel.py +0 -0
  156. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/tables.py +0 -0
  157. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/tone_panel.py +0 -0
  158. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/toolbars.py +0 -0
  159. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/tooltips.py +0 -0
  160. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/truncate_panel.py +0 -0
  161. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/updater.py +0 -0
  162. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/wavelet_panel.py +0 -0
  163. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/gui/windows.py +0 -0
  164. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/__init__.py +0 -0
  165. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/adf.py +0 -0
  166. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf.py +0 -0
  167. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_objects.py +0 -0
  168. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/LICENSE +0 -0
  169. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/activity_result.txt +0 -0
  170. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/channel_table.txt +0 -0
  171. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/data.txt +0 -0
  172. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/data_physics_random_control_parameters.txt +0 -0
  173. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/data_physics_shock_response_synthesis_control_parameters.txt +0 -0
  174. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/data_physics_sine_control_parameters.txt +0 -0
  175. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/geometry.txt +0 -0
  176. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/global_analysis_attributes.txt +0 -0
  177. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/global_test_attributes.txt +0 -0
  178. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/matrix.txt +0 -0
  179. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/mechanical_control_parameters.txt +0 -0
  180. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/modal_test_parameters.txt +0 -0
  181. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/mode.txt +0 -0
  182. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/parameter_set.txt +0 -0
  183. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/point_cloud.txt +0 -0
  184. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/rattlesnake_random_control_parameters.txt +0 -0
  185. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/rattlesnake_sysid_parameters.txt +0 -0
  186. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/rattlesnake_transient_control_parameters.txt +0 -0
  187. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/response_spectrum.txt +0 -0
  188. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/scalar.txt +0 -0
  189. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/unknown.txt +0 -0
  190. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/vector.txt +0 -0
  191. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/escdf_specifications/vibration_channel_table.txt +0 -0
  192. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/excel.py +0 -0
  193. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/exodus.py +0 -0
  194. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/exporters.py +0 -0
  195. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/femap.py +0 -0
  196. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/matlab.py +0 -0
  197. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/nastran.py +0 -0
  198. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/notes.py +0 -0
  199. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/photo_files.py +0 -0
  200. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/punch.py +0 -0
  201. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/rattlesnake_sine.py +0 -0
  202. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/rattlesnake_specification.py +0 -0
  203. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/report_template.py +0 -0
  204. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/sdynpy_data.py +0 -0
  205. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/sdynpy_npz.py +0 -0
  206. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/sdynpy_shapes.py +0 -0
  207. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/sep005.py +0 -0
  208. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/sniffing.py +0 -0
  209. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/step.py +0 -0
  210. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/stl.py +0 -0
  211. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/threemf.py +0 -0
  212. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/io/unv.py +0 -0
  213. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/names.py +0 -0
  214. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/averaging.py +0 -0
  215. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/bands.py +0 -0
  216. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/bars.py +0 -0
  217. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/classical_pulse.py +0 -0
  218. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/dwell.py +0 -0
  219. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/filtering.py +0 -0
  220. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/half_power.py +0 -0
  221. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/replication_segments.py +0 -0
  222. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/scalogram.py +0 -0
  223. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/shock_check.py +0 -0
  224. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/shocks.py +0 -0
  225. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/signature.py +0 -0
  226. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/sine_tracking.py +0 -0
  227. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/srs_tolerance.py +0 -0
  228. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/sweep_rate.py +0 -0
  229. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/time_statistics.py +0 -0
  230. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/tone_readout.py +0 -0
  231. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/tracking_filter.py +0 -0
  232. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/plot/truncation.py +0 -0
  233. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/py.typed +0 -0
  234. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/report/markdown.py +0 -0
  235. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/report/page.py +0 -0
  236. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/rotate.py +0 -0
  237. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/theme.py +0 -0
  238. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/units.py +0 -0
  239. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/update.py +0 -0
  240. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/__init__.py +0 -0
  241. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/_quiet.py +0 -0
  242. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/animate.py +0 -0
  243. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/banded.py +0 -0
  244. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/geometry.py +0 -0
  245. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/mac_bars.py +0 -0
  246. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/marks.py +0 -0
  247. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/paired.py +0 -0
  248. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/pick.py +0 -0
  249. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/rigid.py +0 -0
  250. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/scalogram.py +0 -0
  251. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/sinespec.py +0 -0
  252. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics/viz/waterfall.py +0 -0
  253. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics.egg-info/dependency_links.txt +0 -0
  254. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics.egg-info/entry_points.txt +0 -0
  255. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/src/visualdynamics.egg-info/requires.txt +0 -0
  256. {visualdynamics-0.1.0a71 → visualdynamics-0.1.0a72}/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.0a71
3
+ Version: 0.1.0a72
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: MIT
@@ -97,7 +97,7 @@ from .units import (
97
97
  #: alpha, said in the version itself so the dialog, the About text, the
98
98
  #: installers and latest.json all say the same thing, and the first
99
99
  #: non-alpha release is offered as an upgrade (Brandon, 2026-09-11)
100
- __version__ = '0.1.0a71'
100
+ __version__ = '0.1.0a72'
101
101
 
102
102
 
103
103
  def launch_gui(*paths: str, in_process: bool | None = None) -> Any:
@@ -0,0 +1,455 @@
1
+ """A record's amplitude spectrum: each line scaled so a sine reads its
2
+ amplitude, in peak units, under a window chosen for it.
3
+
4
+ `TimeHistory.compute_spectra` is the raw transform: a rectangular
5
+ window, no scaling (a sine of amplitude A on a line reads A·N/2) and a
6
+ complex mean across the averages, so content whose phase moves from one
7
+ frame to the next averages toward zero. Other code depends on it as it
8
+ is. This module is the scaled spectrum beside it, the one a tone is read
9
+ from in g peak.
10
+
11
+ The scaling, for a frame of N samples x_n windowed by w_n:
12
+
13
+ A(f_k) = 2·|X(k)| / Σw, X(k) = Σ w_n x_n e^(-i2πkn/N)
14
+
15
+ with the 2 left off at the DC line and, for an even frame, the Nyquist
16
+ line, which have no mirror twin at negative frequency to fold in. A
17
+ sine of amplitude A that sits on line k transforms to A·Σw/2 there, so
18
+ the line reads A whatever the window. Σw is the window's amplitude
19
+ correction: Σw/N, the window's mean, is its coherent gain (0.5 for
20
+ Hann), and dividing by Σw rather than N undoes it (a correction of
21
+ ×2.000 for Hann). It is not the correction a density takes. A density is
22
+ mean square per hertz and is corrected by the sum of the window's
23
+ squares (`TimeHistory.compute_psds`); the two differ by the window's
24
+ noise bandwidth, and applying the amplitude correction to a density
25
+ reads every ASD 1.5 times too high under Hann.
26
+
27
+ The frames' magnitudes are averaged, not their complex values. A tone
28
+ reads its amplitude in every frame whatever its phase there, so the
29
+ average reads it too: a tone that is not locked to the frames, or a
30
+ record saved as captures with a fresh phase each, keeps its amplitude
31
+ where a complex mean would shrink it. A broadband floor reads its mean
32
+ magnitude per line, which is not a level: it moves with the line spacing
33
+ and the window, and a random record's level is read from its ASD. An
34
+ amplitude spectrum is for tones. The same averaging makes a tone that is
35
+ not well clear of the floor read high: the floor's magnitude adds to the
36
+ tone's in every frame and does not average away.
37
+
38
+ The window is chosen here, not taken from the record's averaging. The
39
+ frames are the averaging's (its frame length, overlap, start, frame
40
+ count and detrend), because those are what was set for the record; but
41
+ the window right for a density of random data and the window right for a
42
+ tone's amplitude are not the same window. Hann is the default. It reads
43
+ a tone on a line exactly and one midway between lines up to 1.42 dB low,
44
+ because the tallest line then sits half a line off the tone's frequency
45
+ (scalloping). The flat top's crown is flat across a line, so a tone
46
+ reads within about 0.01 dB of its amplitude wherever it falls, at the
47
+ cost of a main lobe several lines wide; it is the window for amplitude
48
+ accuracy. Its correction depends on the variant a program implements and
49
+ how it is normalized; the one here is scipy's `flattop`, periodic, whose
50
+ coherent gain is 0.2156 (a correction of ×4.639). Every reading states
51
+ its window and its correction, so a number read here is never carried to
52
+ another program without them.
53
+
54
+ References
55
+ ----------
56
+ The amplitude correction is the window's coherent gain and the loss of
57
+ a tone between lines its scalloping loss, both Harris's; the one-sided
58
+ scaling and the doubling left off at DC and Nyquist are how the
59
+ autospectrum beside it is scaled.
60
+
61
+ 1. Harris, F. J. (1978). "On the Use of Windows for Harmonic Analysis
62
+ with the Discrete Fourier Transform." *Proceedings of the IEEE*,
63
+ 66(1), 51-83.
64
+ [doi:10.1109/PROC.1978.10837](https://doi.org/10.1109/PROC.1978.10837)
65
+ Coherent gain, equivalent noise bandwidth and scalloping loss,
66
+ window by window, and the flat-top window.
67
+ 2. Bendat, J. S., & Piersol, A. G. (2010). *Random Data: Analysis and
68
+ Measurement Procedures*, 4th ed. Wiley. The finite Fourier transform
69
+ of a record and its one-sided spectra.
70
+ """
71
+
72
+ from __future__ import annotations
73
+
74
+ from dataclasses import dataclass, field, replace
75
+ from typing import Any
76
+
77
+ import numpy as np
78
+
79
+ #: a channel whose lines above DC, its frames' means taken out, are no
80
+ #: more than this share of its largest line, DC included, is constant:
81
+ #: what is left is rounding, not a tone
82
+ CONSTANT_SHARE = 1e-9
83
+
84
+ #: the frame the window's arithmetic is worked out on when no frame
85
+ #: length is given. A periodic cosine-sum window's mean is its first
86
+ #: coefficient at any length that holds it, so the coherent gain does not
87
+ #: depend on this; it only has to be long enough for every window offered
88
+ GAIN_LENGTH = 4096
89
+
90
+
91
+ def coherent_gain(window: Any = 'hann', length: int | None = None,
92
+ parameter: float | None = None) -> float:
93
+ """A window's coherent gain: its mean, Σw/N.
94
+
95
+ The share of a sine's amplitude a line on the sine keeps through the
96
+ window: 1 for a rectangle (scipy's 'boxcar'), 0.5 for Hann, 0.2156
97
+ for scipy's flat top. Its reciprocal is the amplitude correction
98
+ (`amplitude_correction`).
99
+
100
+ Parameters
101
+ ----------
102
+ window : str or Averaging, default 'hann'
103
+ A window by any name `core.averaging.normalize_window` takes, or
104
+ an `Averaging`, whose window, parameter and frame length are
105
+ used.
106
+ length : int, optional
107
+ The frame length in samples; `GAIN_LENGTH` when omitted. The
108
+ window is periodic, as a spectral analysis uses it.
109
+ parameter : float, optional
110
+ The Tukey window's alpha or the Kaiser window's beta.
111
+
112
+ Returns
113
+ -------
114
+ float
115
+ """
116
+ from .averaging import Averaging, window_shape
117
+
118
+ if isinstance(window, Averaging):
119
+ parameter = window.window_parameter
120
+ length = window.frame_length if length is None else length
121
+ window = window.window
122
+ n = GAIN_LENGTH if length is None else int(length)
123
+ shape = np.asarray(window_shape(window, n, parameter), dtype=np.float64)
124
+ return float(np.mean(shape))
125
+
126
+
127
+ def amplitude_correction(window: Any = 'hann', length: int | None = None,
128
+ parameter: float | None = None) -> float:
129
+ """The factor a windowed transform's lines are multiplied by so a
130
+ sine reads its amplitude: N/Σw, one over the coherent gain.
131
+
132
+ 2.000 for Hann, 4.639 for scipy's flat top, 1 for a rectangle. The
133
+ arguments are as for `coherent_gain`.
134
+ """
135
+ return 1.0 / coherent_gain(window, length, parameter)
136
+
137
+
138
+ def scalloping(window: Any = 'hann', length: int | None = None,
139
+ parameter: float | None = None) -> float:
140
+ """The share of its amplitude a sine midway between two lines reads
141
+ at the taller of them: 0.849 for Hann (1.42 dB low), 0.637 for a
142
+ rectangle (3.92 dB low), within a part in a thousand of 1 for the
143
+ flat top.
144
+
145
+ Harris's scalloping loss, worked out from the window's own shape
146
+ (`core.tone_readout.leakage`), so it holds for every window offered.
147
+ The arguments are as for `coherent_gain`.
148
+ """
149
+ from .tone_readout import leakage
150
+
151
+ # the leakage kernel is in power, the share of a tone's mean square
152
+ # a line receives; its root is the share of the amplitude
153
+ return float(np.sqrt(leakage(window, length, parameter).scallop(0.5)))
154
+
155
+
156
+ def amplitude_lines(frames: Any, shape: Any) -> np.ndarray:
157
+ """The amplitude spectrum of frames already windowed, their
158
+ magnitudes averaged.
159
+
160
+ Parameters
161
+ ----------
162
+ frames : array_like
163
+ One windowed frame per row, every row the same length N.
164
+ shape : array_like
165
+ The window they were multiplied by, N samples: its sum is the
166
+ amplitude correction.
167
+
168
+ Returns
169
+ -------
170
+ numpy.ndarray
171
+ N//2 + 1 lines, from DC up: 2·mean|X|/Σw, and mean|X|/Σw at DC
172
+ and at the Nyquist line of an even frame.
173
+ """
174
+ import scipy.fft as scipy_fft
175
+
176
+ frames = np.atleast_2d(np.asarray(frames, dtype=np.float64))
177
+ total = float(np.sum(np.asarray(shape, dtype=np.float64)))
178
+ if not total > 0.0:
179
+ raise ValueError('the window sums to nothing, so no line can be '
180
+ 'scaled to an amplitude')
181
+ n = frames.shape[1]
182
+ lines = np.mean(np.abs(scipy_fft.rfft(frames, axis=1, workers=-1)),
183
+ axis=0)
184
+ scale = np.full(lines.shape, 2.0 / total)
185
+ # no mirror twin at DC, nor at the Nyquist line of an even frame:
186
+ # doubled, a record's mean would read twice itself
187
+ scale[0] = 1.0 / total
188
+ if n % 2 == 0:
189
+ scale[-1] = 1.0 / total
190
+ return lines * scale
191
+
192
+
193
+ @dataclass(frozen=True, eq=False)
194
+ class AmplitudeSpectrum:
195
+ """What `amplitude_spectrum` returns: a record's amplitude spectrum,
196
+ one row per channel, with the window and its correction stated.
197
+
198
+ `amplitude` is in peak units, SI as the record is stored (m/s² for an
199
+ acceleration). `frames` is how many frames each channel's
200
+ magnitudes were averaged over. `coherent_gain` is the window's mean
201
+ and `correction` its reciprocal, the factor its lines were
202
+ multiplied by; `noise_bandwidth` is in lines and `scalloping` is the
203
+ share of its amplitude a sine midway between lines reads.
204
+ `leveled` is the same spectrum of the same frames with each frame's
205
+ mean taken out before its window, which `tallest` reads: a record's
206
+ mean leaks through the window's main lobe into the lines just above
207
+ DC (under Hann a 5 g mean reads 5 g on line 1 as well as at DC), and
208
+ a tone on those lines is read without it. It is `amplitude` itself
209
+ when the averaging levels its frames already.
210
+ `from_averaging` says whether the frames are the record's averaging
211
+ (False: each record whole as one frame, the record having none).
212
+ `lowest_line` is the lowest line a tone is read on exactly under this
213
+ window: below it the tone's mirror at negative frequency is inside
214
+ the main lobe (1 for Hann, 3 for the flat top).
215
+ """
216
+
217
+ frequencies: np.ndarray
218
+ amplitude: np.ndarray
219
+ response_dof: list[str]
220
+ ordinate_dim: list[str]
221
+ dimension_hint: list[str | None]
222
+ window: str
223
+ window_parameter: float | None
224
+ frame_length: int
225
+ sample_rate: float
226
+ frames: tuple[int, ...]
227
+ coherent_gain: float
228
+ noise_bandwidth: float
229
+ scalloping: float
230
+ leveled: np.ndarray | None = None
231
+ from_averaging: bool = True
232
+ labels: list[str] = field(default_factory=list)
233
+ lowest_line: int = 1
234
+
235
+ @property
236
+ def correction(self) -> float:
237
+ """The amplitude correction applied: N/Σw (2.000 for Hann)."""
238
+ return 1.0 / self.coherent_gain
239
+
240
+ @property
241
+ def spacing(self) -> float:
242
+ """The line spacing in Hz: the sample rate over the frame
243
+ length."""
244
+ return self.sample_rate / self.frame_length
245
+
246
+ @property
247
+ def scalloping_db(self) -> float:
248
+ """How far low, in dB, a sine midway between lines reads: 1.42
249
+ for Hann, as a positive number."""
250
+ return float(-20.0 * np.log10(self.scalloping))
251
+
252
+ def label(self, record: int) -> str:
253
+ """The channel's name, as the record labels it."""
254
+ if record < len(self.labels):
255
+ return self.labels[record]
256
+ return str(self.response_dof[record])
257
+
258
+ def tallest(self, record: int = 0,
259
+ band: tuple[float, float] | None = None
260
+ ) -> tuple[float, float]:
261
+ """(frequency, amplitude) of the tallest line of channel
262
+ `record` above DC, read off the frames with their means taken
263
+ out (`leveled`). A record's mean is not a tone, and it leaks:
264
+ under Hann a mean of 5 g reads 5 g at DC and 5 g again on the
265
+ line above, and under the flat top on four lines above. Taken
266
+ out before the window, it leaks into none, so a tone on line 1
267
+ reads its own line and amplitude under Hann. A tone's own
268
+ mirror at negative frequency lies 2k lines from it, and while
269
+ that is inside the main lobe the two add: the flat top's lobe
270
+ reaches five lines, so its lowest line read exactly is line 3
271
+ (`lowest_line`, and `mirror_note` says so).
272
+
273
+ `band` is (low, high) in Hz to look within; the whole spectrum
274
+ above DC when omitted. A line that is not a number is passed
275
+ over.
276
+
277
+ Raises
278
+ ------
279
+ ValueError
280
+ When no line of the channel is a number (it holds samples
281
+ that are not finite), when every line is zero (the channel
282
+ is zero throughout, or in the band), when the channel is
283
+ constant (its lines above DC are rounding beside its mean),
284
+ or when the band holds no line above DC.
285
+ """
286
+ f = self.frequencies
287
+ read = self.amplitude if self.leveled is None else self.leveled
288
+ values = np.asarray(read[record], dtype=np.float64)
289
+ name = self.label(record)
290
+ finite = np.isfinite(values)
291
+ if not finite.any():
292
+ raise ValueError(f'{name} holds samples that are not finite, so '
293
+ 'no line of its spectrum is a number')
294
+ above = (np.arange(len(f)) >= 1) & finite
295
+ drawn = np.asarray(self.amplitude[record], dtype=np.float64)
296
+ largest = float(np.max(np.abs(drawn[np.isfinite(drawn)]), initial=0.0))
297
+ # a constant channel's mean, taken out, leaves lines of 1e-17 of
298
+ # it, which would be read as the tallest line
299
+ if (largest > 0.0 and above.any()
300
+ and np.max(values[above]) <= CONSTANT_SHARE * largest):
301
+ raise ValueError(f'{name} is constant throughout: there is no '
302
+ 'tone to read')
303
+ keep = above
304
+ if band is not None:
305
+ low, high = band
306
+ keep &= (f >= low) & (f <= high)
307
+ if not keep.any():
308
+ raise ValueError('no line to read in that band')
309
+ if not np.max(values[keep]) > 0.0:
310
+ where = ('zero throughout' if band is None
311
+ else f'zero from {band[0]:g} to {band[1]:g} Hz')
312
+ raise ValueError(f'{name} is {where}: there is no tone to read')
313
+ at = int(np.flatnonzero(keep)[np.argmax(values[keep])])
314
+ return float(f[at]), float(values[at])
315
+
316
+ def mirror_note(self, frequency: float) -> str | None:
317
+ """What to say of a tallest line read at `frequency` Hz when it
318
+ is below `lowest_line`, where the window mixes a tone with its
319
+ own mirror and neither the line nor the amplitude is the tone's;
320
+ None at or above it."""
321
+ below = self.lowest_line * self.spacing
322
+ if frequency >= below:
323
+ return None
324
+ return (f'below {below:g} Hz the {self.window} window mixes a tone '
325
+ 'with its mirror at negative frequency; read it under hann')
326
+
327
+ def correction_text(self) -> str:
328
+ """The window and its correction, in words, as the plot and the
329
+ pane state them: 'hann window, amplitude correction ×2.000'."""
330
+ name = self.window
331
+ if self.window_parameter is not None:
332
+ name = f'{name} ({self.window_parameter:g})'
333
+ return f'{name} window, amplitude correction ×{self.correction:.3f}'
334
+
335
+ def describe(self, record: int = 0, unit: str = '',
336
+ scale: float = 1.0) -> str:
337
+ """One line for a status bar: the tallest line of channel
338
+ `record` in peak units, then the window and its correction.
339
+ `scale` converts the amplitude into `unit`."""
340
+ frequency, amplitude = self.tallest(record)
341
+ value = f'{amplitude * scale:.4g} {unit} peak'.replace(' ', ' ')
342
+ note = self.mirror_note(frequency)
343
+ return (f'{self.label(record)}: tallest line {value} at '
344
+ f'{frequency:.4g} Hz; {self.correction_text()}, '
345
+ f'{_frames_text(self.frames[record])} averaged as '
346
+ 'magnitudes' + ('' if note is None else f'; {note}'))
347
+
348
+
349
+ def _frames_text(count: int) -> str:
350
+ return '1 frame' if count == 1 else f'{count} frames'
351
+
352
+
353
+ def channel_row(history: Any, record: int) -> int:
354
+ """The row of an amplitude spectrum of `history` that record
355
+ `record` is averaged into.
356
+
357
+ A row is a channel (`TimeHistory.channel_key`), in the order its
358
+ first record comes, so a history with one record per channel has
359
+ its rows in record order, and every capture of a channel saved as
360
+ captures maps to that channel's one row.
361
+ """
362
+ keys: list = []
363
+ for i in range(len(history.response_dof)):
364
+ key = history.channel_key(i)
365
+ if key not in keys:
366
+ keys.append(key)
367
+ return keys.index(history.channel_key(record))
368
+
369
+
370
+ def amplitude_spectrum(history: Any, averaging: Any = None, *,
371
+ window: Any = 'hann',
372
+ parameter: float | None = None) -> AmplitudeSpectrum:
373
+ """The amplitude spectrum of every channel of a time history, each
374
+ line scaled so a sine reads its amplitude, the frames' magnitudes
375
+ averaged.
376
+
377
+ The frames are the averaging's: `averaging` when given, else the
378
+ record's own, else each record whole as one frame, as
379
+ `TimeHistory.compute_psds` cuts them. Only the window is replaced:
380
+ `window` and `parameter` are this spectrum's own (the module says
381
+ why). Channels are grouped as a PSD groups them
382
+ (`TimeHistory.channel_key`), so a record saved as captures averages
383
+ every capture's frames together.
384
+
385
+ Parameters
386
+ ----------
387
+ history : TimeHistory
388
+ The record, evenly sampled.
389
+ averaging : Averaging, optional
390
+ How to cut it into frames.
391
+ window : str, default 'hann'
392
+ Any window `core.averaging.WINDOWS` names; 'flattop' for a
393
+ tone's amplitude wherever it falls between lines.
394
+ parameter : float, optional
395
+ The Tukey window's alpha or the Kaiser window's beta.
396
+
397
+ Returns
398
+ -------
399
+ AmplitudeSpectrum
400
+
401
+ Raises
402
+ ------
403
+ ValueError
404
+ If the samples are not evenly spaced, the frames do not fit in
405
+ the record, or the window is not one offered.
406
+ """
407
+ from .averaging import normalize_window
408
+ from .tone_readout import leakage, noise_bandwidth
409
+
410
+ key = normalize_window(window)
411
+ given = averaging if averaging is not None else history.averaging
412
+ used = history._resolved_averaging(averaging)
413
+ used = replace(used, window=key, window_parameter=(
414
+ None if parameter is None else float(parameter)))
415
+ # the framing every spectrum of a record shares, so these frames are
416
+ # the PSD's frames exactly; its density scale is not this one
417
+ frequencies, _density, groups = history._spectral_frame(used)
418
+ # and again with each frame's mean taken out before its window, the
419
+ # lines a tone is read from (`AmplitudeSpectrum.tallest`); frames the
420
+ # averaging levels already are those
421
+ leveled = (groups if used.detrend != 'none' else
422
+ history._spectral_frame(replace(used, detrend='mean'))[2])
423
+ shape = used.shape()
424
+ rows, flat, dofs, dims, hints, counts, labels = [], [], [], [], [], [], []
425
+ first = {}
426
+ for i in range(len(history.response_dof)):
427
+ first.setdefault(history.channel_key(i), i)
428
+ for (dof, dim, hint), windowed in groups.items():
429
+ rows.append(amplitude_lines(windowed, shape))
430
+ flat.append(rows[-1] if leveled is groups else
431
+ amplitude_lines(leveled[(dof, dim, hint)], shape))
432
+ dofs.append(dof)
433
+ dims.append(dim)
434
+ hints.append(hint)
435
+ counts.append(len(windowed))
436
+ labels.append(history.record_label(first[(dof, dim, hint)]))
437
+ amplitude = np.asarray(rows, dtype=np.float64)
438
+ return AmplitudeSpectrum(
439
+ frequencies=np.asarray(frequencies, dtype=np.float64),
440
+ amplitude=amplitude,
441
+ response_dof=dofs, ordinate_dim=dims, dimension_hint=hints,
442
+ window=key, window_parameter=used.window_parameter,
443
+ frame_length=int(used.frame_length),
444
+ sample_rate=float(history.sample_rate),
445
+ frames=tuple(counts),
446
+ coherent_gain=coherent_gain(used),
447
+ noise_bandwidth=float(noise_bandwidth(used)),
448
+ scalloping=scalloping(used),
449
+ leveled=(amplitude if leveled is groups
450
+ else np.asarray(flat, dtype=np.float64)),
451
+ from_averaging=given is not None,
452
+ labels=labels,
453
+ # a tone on line k has its mirror 2k lines off, and the two part
454
+ # once that reaches the main lobe's edge, its half-width
455
+ lowest_line=int(np.ceil(leakage(used).half_width / 2)))