pyshindo 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. pyshindo-0.3.0/CHANGELOG.md +77 -0
  2. pyshindo-0.3.0/LICENSE +21 -0
  3. pyshindo-0.3.0/MANIFEST.in +5 -0
  4. pyshindo-0.3.0/PATENTS.md +7 -0
  5. pyshindo-0.3.0/PKG-INFO +342 -0
  6. pyshindo-0.3.0/README.md +303 -0
  7. pyshindo-0.3.0/docs/algorithm.md +80 -0
  8. pyshindo-0.3.0/docs/api.md +370 -0
  9. pyshindo-0.3.0/docs/data.md +89 -0
  10. pyshindo-0.3.0/docs/images/hero.png +0 -0
  11. pyshindo-0.3.0/docs/long-period.md +238 -0
  12. pyshindo-0.3.0/docs/migration.md +189 -0
  13. pyshindo-0.3.0/docs/validation.md +126 -0
  14. pyshindo-0.3.0/examples/00_quickstart.py +84 -0
  15. pyshindo-0.3.0/examples/01_measured_intensity.py +86 -0
  16. pyshindo-0.3.0/examples/02_realtime_intensity.py +76 -0
  17. pyshindo-0.3.0/examples/03_official_jma_record.py +51 -0
  18. pyshindo-0.3.0/examples/04_filter_designs.py +74 -0
  19. pyshindo-0.3.0/examples/05_streaming_sample_api.py +32 -0
  20. pyshindo-0.3.0/examples/06_peak_velocity.py +149 -0
  21. pyshindo-0.3.0/examples/07_obspy_interop.py +108 -0
  22. pyshindo-0.3.0/examples/08_long_period.py +129 -0
  23. pyshindo-0.3.0/examples/09_spectrum_intensity.py +90 -0
  24. pyshindo-0.3.0/examples/10_station_map.py +79 -0
  25. pyshindo-0.3.0/examples/11_response_spectrum.py +83 -0
  26. pyshindo-0.3.0/pyproject.toml +113 -0
  27. pyshindo-0.3.0/scripts/validate_official.py +454 -0
  28. pyshindo-0.3.0/setup.cfg +4 -0
  29. pyshindo-0.3.0/src/pyshindo/__init__.py +213 -0
  30. pyshindo-0.3.0/src/pyshindo/_order.py +203 -0
  31. pyshindo-0.3.0/src/pyshindo/_spectral_response.py +331 -0
  32. pyshindo-0.3.0/src/pyshindo/_version.py +3 -0
  33. pyshindo-0.3.0/src/pyshindo/comparison.py +72 -0
  34. pyshindo-0.3.0/src/pyshindo/duration.py +152 -0
  35. pyshindo-0.3.0/src/pyshindo/exceptions.py +43 -0
  36. pyshindo-0.3.0/src/pyshindo/filters/__init__.py +43 -0
  37. pyshindo-0.3.0/src/pyshindo/filters/jma.py +146 -0
  38. pyshindo-0.3.0/src/pyshindo/filters/realtime.py +971 -0
  39. pyshindo-0.3.0/src/pyshindo/io/__init__.py +5 -0
  40. pyshindo-0.3.0/src/pyshindo/io/jma.py +273 -0
  41. pyshindo-0.3.0/src/pyshindo/long_period/__init__.py +54 -0
  42. pyshindo-0.3.0/src/pyshindo/long_period/_core.py +385 -0
  43. pyshindo-0.3.0/src/pyshindo/long_period/calculation.py +338 -0
  44. pyshindo-0.3.0/src/pyshindo/long_period/models.py +89 -0
  45. pyshindo-0.3.0/src/pyshindo/long_period/realtime.py +187 -0
  46. pyshindo-0.3.0/src/pyshindo/long_period/scale.py +129 -0
  47. pyshindo-0.3.0/src/pyshindo/measured.py +159 -0
  48. pyshindo-0.3.0/src/pyshindo/models.py +386 -0
  49. pyshindo-0.3.0/src/pyshindo/obspy_interop.py +304 -0
  50. pyshindo-0.3.0/src/pyshindo/plotting/__init__.py +49 -0
  51. pyshindo-0.3.0/src/pyshindo/plotting/figures.py +951 -0
  52. pyshindo-0.3.0/src/pyshindo/plotting/maps.py +323 -0
  53. pyshindo-0.3.0/src/pyshindo/plotting/theme.py +249 -0
  54. pyshindo-0.3.0/src/pyshindo/py.typed +0 -0
  55. pyshindo-0.3.0/src/pyshindo/realtime.py +579 -0
  56. pyshindo-0.3.0/src/pyshindo/response_spectrum.py +202 -0
  57. pyshindo-0.3.0/src/pyshindo/scale.py +194 -0
  58. pyshindo-0.3.0/src/pyshindo/signal.py +416 -0
  59. pyshindo-0.3.0/src/pyshindo/spectrum_intensity.py +441 -0
  60. pyshindo-0.3.0/src/pyshindo/strong_motion.py +133 -0
  61. pyshindo-0.3.0/src/pyshindo/synthetic.py +126 -0
  62. pyshindo-0.3.0/src/pyshindo/units.py +93 -0
  63. pyshindo-0.3.0/src/pyshindo/validation.py +148 -0
  64. pyshindo-0.3.0/src/pyshindo/velocity.py +208 -0
  65. pyshindo-0.3.0/src/pyshindo.egg-info/PKG-INFO +342 -0
  66. pyshindo-0.3.0/src/pyshindo.egg-info/SOURCES.txt +85 -0
  67. pyshindo-0.3.0/src/pyshindo.egg-info/dependency_links.txt +1 -0
  68. pyshindo-0.3.0/src/pyshindo.egg-info/requires.txt +18 -0
  69. pyshindo-0.3.0/src/pyshindo.egg-info/top_level.txt +1 -0
  70. pyshindo-0.3.0/tests/test_comparison.py +45 -0
  71. pyshindo-0.3.0/tests/test_duration.py +75 -0
  72. pyshindo-0.3.0/tests/test_filters.py +159 -0
  73. pyshindo-0.3.0/tests/test_io.py +138 -0
  74. pyshindo-0.3.0/tests/test_long_period.py +554 -0
  75. pyshindo-0.3.0/tests/test_maps.py +185 -0
  76. pyshindo-0.3.0/tests/test_measured.py +60 -0
  77. pyshindo-0.3.0/tests/test_obspy_interop.py +245 -0
  78. pyshindo-0.3.0/tests/test_order.py +42 -0
  79. pyshindo-0.3.0/tests/test_plotting.py +169 -0
  80. pyshindo-0.3.0/tests/test_realtime.py +205 -0
  81. pyshindo-0.3.0/tests/test_response_spectrum.py +239 -0
  82. pyshindo-0.3.0/tests/test_scale.py +89 -0
  83. pyshindo-0.3.0/tests/test_signal.py +240 -0
  84. pyshindo-0.3.0/tests/test_spectrum_intensity.py +345 -0
  85. pyshindo-0.3.0/tests/test_strong_motion.py +104 -0
  86. pyshindo-0.3.0/tests/test_synthetic.py +69 -0
  87. pyshindo-0.3.0/tests/test_velocity.py +212 -0
@@ -0,0 +1,77 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 - 2026-09-14
4
+
5
+ First release published to PyPI, with the public API reviewed for consistency. See [`docs/migration.md`](docs/migration.md) for the upgrade path -- every breaking change is listed there with before/after code.
6
+
7
+ Renamed (breaking):
8
+
9
+ - `intensity_from_acceleration`, `acceleration_from_intensity`, `intensity_series_from_acceleration` -> `..._threshold_acceleration` forms. They take a 0.3-second threshold acceleration, not a waveform.
10
+ - `calculate_spectrum_intensity`'s `retain_spectrum` -> `retain_velocity_time_series`; it never controlled `sv_cm_s`, which is always returned.
11
+ - `published_lowrate_gamma_set`'s `policy` -> `lowrate_gamma_policy`, matching `design_realtime_filter`.
12
+ - `ObsPyRecordMetadata.station` -> `station_code`; `LongPeriodResult.absolute_velocity_cm_s` -> `absolute_velocity_time_series_cm_s`; `LongPeriodUpdate.class_so_far` -> `long_period_class_so_far`.
13
+
14
+ Changed (breaking):
15
+
16
+ - `realtime_intensity`'s `reported` now defaults to `True`, matching `measured_intensity`. The two returned different quantities under the same keyword.
17
+ - `peak_ground_acceleration` and `component_peak_acceleration` take `unit=` and always return gal, matching the PGV and PGD pairs.
18
+ - `apply_jma_filter_fft` returns `JMAFilterResult` instead of a bare tuple, takes `unit=`, and defaults `sampling_rate_hz` to 100.0.
19
+ - `scale_acceleration_to_intensity`'s `target_intensity_raw` is keyword-only, so the second positional argument is `sampling_rate_hz` as everywhere else.
20
+ - `SpectrumIntensityEstimator` no longer warns on a non-100 Hz rate, and its `warn_nonstandard_rate` argument is gone; the batch function never warned.
21
+ - `RealtimeIntensityEstimator`'s configuration attributes are read-only; assigning to them silently desynced the filter design from the rate.
22
+
23
+ Added:
24
+
25
+ - Every exception and warning class, `pyshindo.long_period`, `MeasuredIntensityTiming`, `RealtimeChunkTiming`, `report_intensity_array`, and `intensity_series_from_threshold_acceleration` are reachable from `pyshindo` directly.
26
+ - `RealtimeIntensityEstimator.approximate_scale`, the running intensity class.
27
+
28
+ Fixed:
29
+
30
+ - `NonstandardSamplingRateWarning`, `MissingComponentWarning`, and `FractionalDurationWarning` now point at the caller's line instead of pyshindo's own source.
31
+
32
+ Docs and packaging:
33
+
34
+ - `docs/migration.md`; documented why `RealtimeIntensityEstimator` has no `result()`.
35
+ - `Typing :: Typed` classifier; README links are absolute so they resolve on PyPI.
36
+
37
+ ## 0.2.2 - 2026-09-13
38
+
39
+ - Added: peak ground displacement -- `integrate_to_displacement`, `component_peak_displacement`, `peak_ground_displacement`, alongside the existing velocity trio.
40
+ - Added: `pyshindo.strong_motion` -- JMA's own published velocity/displacement waveform filters. `apply_strong_motion_displacement_filter` reproduces JMA's mechanical 1x strong-motion seismometer response directly from acceleration (no integration involved) and matches published peak displacement to ~0.15% median error, the recommended way to reproduce a long-period observation page's PGD.
41
+ - Added: `calculate_response_spectrum` also returns pseudo-velocity (PSV); its displacement and velocity time series are now independent `retain_*` flags.
42
+ - Added: `RealtimeChunkTiming.reporting_s` exposes the display-rounding cost.
43
+ - Added: `marker_size` on the map figures.
44
+ - Added: `ClippingReport.__str__` summarizes instead of dumping every interval.
45
+ - Fixed: removed two unreachable branches in `RollingKthLargest._rebalance`.
46
+
47
+ ## 0.2.1 - 2026-09-10
48
+
49
+ - Added: Housner's spectrum intensity (SI value) -- `calculate_spectrum_intensity`, plus a streaming estimator.
50
+ - Added: a general elastic response spectrum -- `calculate_response_spectrum` (Sd, Sv, PSA).
51
+ - Added: `detect_clipping`, a diagnostic-only check for saturated samples.
52
+ - Added: multi-station distribution maps -- `pyshindo.plotting.maps`.
53
+ - Added: `apply_obspy_calibration`, for readers (K-NET/KiK-net among them) that leave data in raw counts.
54
+ - Added: `classify_intensity_array` and `intensity_interval` are now exported.
55
+ - Fixed: `scale_acceleration_to_intensity`'s `allow_fewer_components` was ignored internally.
56
+ - Fixed: map figures didn't validate longitude range, and their legend toggle left a marker's halo behind.
57
+ - Docs: corrected `RealtimeIntensityEstimator.process_sample`'s docstring.
58
+ - Tests/CI: closed coverage gaps; CI now runs Python 3.12 and 3.13.
59
+
60
+ ## 0.2.0 - 2026-09-05
61
+
62
+ - Added: the JMA long-period ground motion class (`pyshindo.long_period`), plus a streaming estimator.
63
+ - Added: velocity by integration and peak ground velocity (`integrate_to_velocity`, `peak_ground_velocity`).
64
+ - Added: `apply_ground_motion_high_pass` reproduces JMA's own published peak velocity.
65
+ - Added: optional ObsPy interoperability (`pyshindo.obspy_interop.from_obspy_stream`).
66
+ - Added: `scripts/validate_official.py` compares results against JMA's published values for a whole event.
67
+ - Fixed: mutable shared state in period grids and in `RealtimeIntensityEstimator`'s filter design.
68
+ - Fixed: `acceleration_figure` silently dropped channels past the third.
69
+ - Packaging: PEP 639 license metadata, `MANIFEST.in`, `project.urls`.
70
+
71
+ ## 0.1.0 - 2026-09-02
72
+
73
+ - Frequency-domain reference calculation of instrumental seismic intensity.
74
+ - Causal real-time approximation filters (2008, 2012, and the generalized low-rate design).
75
+ - Stateful real-time estimation with an exact rolling order statistic.
76
+ - JMA strong-motion text parsing and single-file downloading.
77
+ - Optional Plotly figures.
pyshindo-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pyshindo contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include PATENTS.md
2
+ include CHANGELOG.md
3
+ recursive-include docs *.md *.png
4
+ recursive-include examples *.py
5
+ recursive-include scripts *.py
@@ -0,0 +1,7 @@
1
+ # Patent notice
2
+
3
+ Some real-time algorithms in this package (`RealtimeFilter.KUNUGI_2008`, `KUNUGI_2012`, `JP7681907_LOWRATE`) implement equations disclosed in Japanese patents JP4229337B2, JP5946067B2, and JP7681907B2. The MIT license covers the copyright in this source code; it is not a patent license. Do your own patent search before commercial or operational use of the real-time modules.
4
+
5
+ The rolling order statistic uses an exact two-heap selection over raw resultant acceleration rather than the patents' discretized-histogram examples -- a numerical implementation choice, not a claim about patent scope.
6
+
7
+ This is informational, written by an individual maintainer, not legal advice.
@@ -0,0 +1,342 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyshindo
3
+ Version: 0.3.0
4
+ Summary: Reference and real-time algorithms for Japanese instrumental seismic intensity
5
+ Author: pyshindo contributors
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/aldichollow/pyshindo
8
+ Project-URL: Documentation, https://github.com/aldichollow/pyshindo#readme
9
+ Project-URL: Issues, https://github.com/aldichollow/pyshindo/issues
10
+ Project-URL: Changelog, https://github.com/aldichollow/pyshindo/blob/main/CHANGELOG.md
11
+ Keywords: earthquake,instrumental-seismic-intensity,seismic-intensity,signal-processing,real-time,JMA
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.12
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ License-File: PATENTS.md
23
+ Requires-Dist: numpy<3,>=1.26
24
+ Requires-Dist: scipy<2,>=1.12
25
+ Provides-Extra: plot
26
+ Requires-Dist: plotly<7,>=5.24; extra == "plot"
27
+ Provides-Extra: obspy
28
+ Requires-Dist: obspy<2,>=1.4; extra == "obspy"
29
+ Provides-Extra: dev
30
+ Requires-Dist: build>=1.2; extra == "dev"
31
+ Requires-Dist: ipykernel>=7.3; extra == "dev"
32
+ Requires-Dist: mypy>=1.10; extra == "dev"
33
+ Requires-Dist: nbformat>=5.11; extra == "dev"
34
+ Requires-Dist: pytest>=8.3; extra == "dev"
35
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
36
+ Requires-Dist: ruff>=0.8; extra == "dev"
37
+ Requires-Dist: scipy-stubs>=1.12; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # pyshindo
41
+
42
+ [![CI](https://github.com/aldichollow/pyshindo/actions/workflows/ci.yml/badge.svg)](https://github.com/aldichollow/pyshindo/actions/workflows/ci.yml)
43
+
44
+ ## 概要
45
+
46
+ `pyshindo` は加速度から気象庁の計測震度を計算するPythonパッケージです。記録全体を使うFFT参照計算(計測震度)と、逐次入力向けの因果的リアルタイム近似を明確に分離しているのが特徴です。気象庁の公開計算式、Kunugi et al. (2008, 2013)、および関連特許(JP4229337B2 / JP5946067B2 / JP7681907B2)に基づき、係数は固定表を転記するのではなく式から都度導出しています。リアルタイム側は直近60秒の閾値をヒストグラム丸めなしの厳密な順序統計量で保持し、逐次入力(`process_sample`)と一括入力(`process`)のどちらでも同じ結果になるよう作られています。
47
+
48
+ 計測震度に加えて、長周期地震動階級、PGV・PGD(最大速度・最大変位。いずれも気象庁が定義・公表している量です)、SI値(Housnerのスペクトル強度)も算出できます。長周期地震動階級は気象庁が公開している絶対速度応答スペクトルと照合し、2地震・268観測点で全ての階級が一致、応答スペクトル自体も最大値で1e-05程度、検証した観測点のうち最も悪いところで1.7e-05の水準で一致することを確認しています。ObsPy連携を使えば、K-NET・KiK-net・miniSEED・SACなどObsPyが読める形式をそのまま入力にできます。
49
+
50
+ 詳細なアルゴリズム解説は日本語で [`docs/algorithm.md`](https://github.com/aldichollow/pyshindo/blob/main/docs/algorithm.md)(計測震度)と [`docs/long-period.md`](https://github.com/aldichollow/pyshindo/blob/main/docs/long-period.md)(長周期地震動階級)にあります。
51
+
52
+ 本パッケージは個人で開発しているものです。一次資料にあたって実装し、公開データとの照合結果も[`docs/validation.md`](https://github.com/aldichollow/pyshindo/blob/main/docs/validation.md)に記録していますが、計算結果の正確性・完全性を保証するものではありません。ご利用は自己判断・自己責任でお願いします。
53
+
54
+ ---
55
+
56
+ `pyshindo` is a small Python package for calculating Japanese instrumental seismic intensity from acceleration records. It keeps the complete-record FFT calculation separate from causal real-time approximations, so the meaning of both results remains explicit.
57
+
58
+ The package targets Python 3.12 or later. It is a research and engineering reference implementation, not a certified seismic intensity meter, earthquake early-warning service, or safety controller.
59
+
60
+ ## What is implemented
61
+
62
+ - The published JMA frequency-domain calculation: FFT per component, the three-factor intensity response, inverse FFT, three-component resultant, the 0.3-second cumulative-duration threshold, and the official decimal treatment.
63
+ - The original 2008 causal approximation filter.
64
+ - The improved 2012 causal approximation filter.
65
+ - The generalized low-sampling-rate filter disclosed in JP7681907B2.
66
+ - Exact rolling order statistics for a 60-second real-time window without discretizing intensity into fixed-width bins.
67
+ - Stateful chunk and single-sample APIs whose results are invariant to chunk boundaries.
68
+ - Unit conversion, sampling diagnostics, PGA, preprocessing helpers, JMA text-record parsing, and optional Plotly figures.
69
+ - Velocity and displacement by cumulative trapezoidal integration, and PGV/PGD -- with the baseline treatment left to the caller rather than applied silently. Displacement compounds the same drift a second time, so it is considerably more baseline-sensitive than velocity.
70
+ - The JMA long-period ground motion class (長周期地震動階級): the 20-second high-pass, a 32-oscillator bank over 1.6-7.8 s, the horizontal vector composite, the overall and per-band classes, and a streaming estimator. Every class matches JMA's own published values across 268 stations of two earthquakes; the response spectra themselves agree to about 1e-5, worst case, over the stations checked.
71
+ - Housner's spectrum intensity (SI value), per component: the relative-velocity response spectrum averaged over the 0.1-2.5 s period band, sharing the same linear-acceleration-method oscillator solver as the long-period class but without its absolute-velocity or component-combination steps, plus a streaming estimator with the same cumulative-maximum behavior as the long-period class's.
72
+ - A general elastic response spectrum (`calculate_response_spectrum`): relative displacement, relative velocity, pseudo-velocity, and pseudo-acceleration for any damping ratio and period grid, sharing the same oscillator solver as the long-period class and SI value without either one's own conventions baked in.
73
+ - `detect_clipping`: a diagnostic-only check for saturated samples, by a known digitizer range and/or a run of repeated values near a component's own peak. Never applied automatically.
74
+ - Optional ObsPy interoperability (`pyshindo[obspy]`): convert a stream that ObsPy already read -- K-NET, KiK-net, miniSEED, SAC -- into the arrays used here, without reimplementing any reader.
75
+ - Each causal filter's named analog factors (`RecursiveFilterDesign.stages`) can be inspected or plotted individually, not just as a combined response.
76
+ - Built-in wall-clock timing: every result carries a `timing` field (or, for `process_sample`, `elapsed_s`) measured with `time.perf_counter`, so callers can inspect calculation cost without wrapping their own timer.
77
+
78
+ Relevant real-time algorithms are associated with patent documents. Read [PATENTS.md](https://github.com/aldichollow/pyshindo/blob/main/PATENTS.md) before distribution or operational use. The MIT license covers copyright in this source code and is not a patent-clearance opinion.
79
+
80
+ Separately: Japan's forecasting-business licence (気象業務法 Article 17) covers _predicting_ ground motion before it happens and announcing that prediction, which is a different activity from what this package does -- computing intensity or long-period class _after the fact_ from an already-recorded waveform ([overview, in Japanese](https://www.jma.go.jp/jma/kishou/minkan/kyoka.html)). Where the line falls in a given use case is not something this note can settle, so it is not legal advice.
81
+
82
+ ## Installation
83
+
84
+ ```bash
85
+ python -m pip install pyshindo
86
+ ```
87
+
88
+ The optional extras are `plot` for the Plotly figures and `obspy` for reading
89
+ formats through ObsPy:
90
+
91
+ ```bash
92
+ python -m pip install "pyshindo[plot,obspy]"
93
+ ```
94
+
95
+ For the unreleased state of `main`, or from a local checkout:
96
+
97
+ ```bash
98
+ python -m pip install git+https://github.com/aldichollow/pyshindo.git
99
+ # from a checkout, editable, with the extras:
100
+ python -m pip install -e ".[plot,obspy]"
101
+ ```
102
+
103
+ ## Complete-record FFT calculation
104
+
105
+ ```python
106
+ from pyshindo import calculate_measured_intensity
107
+
108
+ result = calculate_measured_intensity(
109
+ acceleration, # shape: (samples, 3)
110
+ sampling_rate_hz=100.0,
111
+ unit="m/s^2",
112
+ )
113
+
114
+ print(result.intensity_raw) # Unrounded continuous value
115
+ print(result.intensity) # Official one-decimal treatment
116
+ print(result.scale.japanese) # Example: "5弱"
117
+ print(result.threshold_acceleration_gal) # 0.3-second threshold
118
+ print(result.filtered_pga_gal)
119
+ print(result.timing.total_s) # wall-clock time for this call
120
+ ```
121
+
122
+ `result.filtered_acceleration_gal`, `result.resultant_acceleration_gal`, the frequency vector, and the applied response are retained by default. Set `retain_intermediates=False` for lower memory use.
123
+
124
+ For a scalar-only call:
125
+
126
+ ```python
127
+ from pyshindo import measured_intensity
128
+
129
+ intensity = measured_intensity(acceleration, 100.0, unit="gal")
130
+ ```
131
+
132
+ ## Real-time calculation
133
+
134
+ ```python
135
+ from pyshindo import RealtimeIntensityEstimator
136
+
137
+ estimator = RealtimeIntensityEstimator(
138
+ sampling_rate_hz=100.0,
139
+ unit="gal",
140
+ )
141
+
142
+ for chunk in acceleration_chunks:
143
+ output = estimator.process(chunk)
144
+ latest = output.intensity_raw[-1]
145
+ print(output.timing.filter_s, output.timing.order_statistic_s)
146
+ ```
147
+
148
+ The estimator filters every sample, preserves recursive state, and maintains the exact 30th-largest value in the latest 60 seconds at 100 Hz. The first valid output appears with sample 30; preceding values are `NaN`. A zero threshold maps to negative infinity, as required by the logarithmic conversion.
149
+
150
+ For one complete-record replay:
151
+
152
+ ```python
153
+ from pyshindo import calculate_realtime_intensity
154
+
155
+ trace = calculate_realtime_intensity(acceleration, 100.0, unit="gal")
156
+ print(trace.approximate_intensity_raw)
157
+ print(trace.approximate_intensity)
158
+ ```
159
+
160
+ ## Velocity, displacement, PGV, and PGD
161
+
162
+ ```python
163
+ from pyshindo import peak_ground_displacement, peak_ground_velocity, remove_offset
164
+
165
+ pgv = peak_ground_velocity(remove_offset(acceleration), 100.0, unit="gal")
166
+ pgd = peak_ground_displacement(remove_offset(acceleration), 100.0, unit="gal")
167
+ ```
168
+
169
+ Velocity comes from cumulative trapezoidal integration and is always returned in
170
+ cm/s (kine); displacement integrates that same velocity a second time and is
171
+ always returned in cm. Nothing is baseline-corrected on your behalf:
172
+ integration cannot distinguish a baseline error from real long-period motion,
173
+ so a record with a nonzero mean integrates into a linearly drifting velocity
174
+ -- and, one integration further, a *quadratically* drifting displacement, so
175
+ PGD is considerably more sensitive to an uncorrected baseline than PGV is.
176
+ Apply `remove_offset`, `detrend_acceleration`, or a high-pass filter first,
177
+ and say which one you used. See
178
+ [`examples/06_peak_velocity.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/06_peak_velocity.py).
179
+
180
+ `peak_ground_velocity`/`peak_ground_displacement` take the resultant of
181
+ whichever components you pass, the same as `peak_ground_acceleration`: three
182
+ components give the three-component resultant, two horizontals give the
183
+ horizontal PGV/PGD.
184
+
185
+ JMA's own published peak velocity, in the `max.csv` of a long-period ground
186
+ motion observation page, does not match this default -- but does match, to
187
+ about 0.01 percent across 268 stations, once the same 20-second high-pass used
188
+ for the long-period class is applied to the acceleration first
189
+ (`pyshindo.long_period.apply_ground_motion_high_pass`). See
190
+ [`docs/validation.md`](https://github.com/aldichollow/pyshindo/blob/main/docs/validation.md) for the finding and
191
+ [`docs/api.md`](https://github.com/aldichollow/pyshindo/blob/main/docs/api.md) for the recipe.
192
+
193
+ The same `max.csv`'s published peak *displacement* is not a double integration
194
+ at all -- JMA derives it by filtering acceleration through a filter
195
+ reproducing the amplitude response of its mechanical 1x strong-motion
196
+ seismometer (natural period 6 s, damping 0.55), which is published in
197
+ [速度波形・変位波形の求め方](https://www.jma.go.jp/jma/kishou/know/jishin/kyoshin/kaisetsu/calc_wave.html).
198
+ `pyshindo.strong_motion.apply_strong_motion_displacement_filter` implements
199
+ this directly from acceleration (no separate integration step) and reproduces
200
+ the published displacement to a median relative error of about 0.15 percent
201
+ across the same 268 stations. See [`docs/validation.md`](https://github.com/aldichollow/pyshindo/blob/main/docs/validation.md).
202
+
203
+ ## Long-period ground motion class
204
+
205
+ ```python
206
+ from pyshindo.long_period import calculate_long_period_class
207
+
208
+ result = calculate_long_period_class(horizontal_acceleration, 100.0, unit="gal")
209
+ print(result.long_period_class) # "0" through "4"
210
+ print(result.max_sva_cm_s) # absolute velocity response maximum, cm/s
211
+ print(result.critical_period_s)
212
+ for band in result.bands: # the per-band classes JMA also reports
213
+ print(band.japanese_label, band.long_period_class)
214
+ ```
215
+
216
+ A different quantity from instrumental intensity and a different calculation:
217
+ horizontal components only, a bank of damped oscillators covering 1.6 to 7.8
218
+ seconds, and the largest absolute velocity response. `LongPeriodEstimator`
219
+ gives the same numbers incrementally for streaming input.
220
+
221
+ Checked against JMA's own published absolute velocity response spectra: across
222
+ 268 stations of two earthquakes, every long-period class matches, and the
223
+ spectra themselves agree to about 1e-5, worst case among the stations checked.
224
+ See [`docs/long-period.md`](https://github.com/aldichollow/pyshindo/blob/main/docs/long-period.md) for the algorithm and its
225
+ primary sources, [`docs/validation.md`](https://github.com/aldichollow/pyshindo/blob/main/docs/validation.md) for the full
226
+ comparison, and [`examples/08_long_period.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/08_long_period.py) to
227
+ reproduce it.
228
+
229
+ ## Spectrum intensity (SI value)
230
+
231
+ ```python
232
+ from pyshindo import calculate_spectrum_intensity
233
+
234
+ result = calculate_spectrum_intensity(acceleration, 100.0, unit="gal")
235
+ print(result.si_cm_s) # one value per component, not combined
236
+ ```
237
+
238
+ Housner's SI: `SI = (1/2.4) * integral[0.1, 2.5] Sv(T, h=0.20) dT`, where `Sv`
239
+ is the _relative_ velocity response spectrum -- not the absolute response the
240
+ long-period class uses, and not combined across horizontal components,
241
+ matching the same choice `peak_ground_velocity` leaves to the caller. The
242
+ oscillator response itself shares the long-period class's linear-acceleration
243
+ solver, without its ground-velocity or vector-combination steps.
244
+
245
+ Neither the damping ratio (0.20, specific to SI, not a general structural
246
+ value) nor the 0.1-2.5 s integration range has changed across the sources
247
+ checked, but no published discretization exists for evaluating that integral
248
+ numerically; the 121-point grid used here was chosen by checking convergence
249
+ directly. See [`examples/09_spectrum_intensity.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/09_spectrum_intensity.py).
250
+
251
+ ## Reading other formats through ObsPy
252
+
253
+ ```python
254
+ import obspy
255
+ from pyshindo.obspy_interop import from_obspy_stream
256
+
257
+ stream = obspy.read("...").select(station="...")
258
+ record = from_obspy_stream(stream, unit="gal")
259
+ ```
260
+
261
+ A thin adapter, not a reader: it converts a stream that is already in
262
+ acceleration units into the arrays used here and never resamples, trims,
263
+ merges, rotates, or rescales. `unit` is required rather than detected, because
264
+ SEED and the formats around it carry no dependable physical-unit field. See
265
+ [`docs/data.md`](https://github.com/aldichollow/pyshindo/blob/main/docs/data.md) and
266
+ [`examples/07_obspy_interop.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/07_obspy_interop.py).
267
+
268
+ ## Sampling rates other than 100 Hz
269
+
270
+ The FFT calculation accepts any positive sampling rate and evaluates the published response at the corresponding FFT frequencies. A warning is emitted because comparability still depends on the source bandwidth, anti-aliasing, record preparation, and validation data.
271
+
272
+ The default real-time selection is `RealtimeFilter.AUTO`:
273
+
274
+ - at 80 Hz or above, the improved 2012 filter is used;
275
+ - below 80 Hz, the generalized low-rate design is used;
276
+ - below 1 Hz, no published gamma table is available and an error is raised.
277
+
278
+ The selected design is recorded in `result.filter_name`. An explicit 2012 request is checked for pole stability and fails instead of returning a diverging sequence. Batch resampling is available through `resample_acceleration`, but is never performed implicitly.
279
+
280
+ ## Data and figures
281
+
282
+ `pyshindo.io` parses the seven-line JMA strong-motion text header and can download one explicitly selected URL. No observed waveform is bundled. See [`docs/data.md`](https://github.com/aldichollow/pyshindo/blob/main/docs/data.md).
283
+
284
+ Plotly figures use a restrained package theme. Intensity colors 1 through 7 follow the JMA web color guide; the guide does not assign intensity 0 a color, so the neutral intensity-0 background is identified as a package choice. The long-period class colors are the ones JMA uses on its own long-period observation pages. Multi-station distribution maps (`intensity_map_figure`, `long_period_class_map_figure`, `continuous_value_map_figure`) share the same colors, taking parallel latitude/longitude/value arrays from whichever source produced them.
285
+
286
+ ![pyshindo](https://raw.githubusercontent.com/aldichollow/pyshindo/main/docs/images/hero.png)
287
+
288
+ <sub>1つの実記録から計算した例。2026年8月23日 茨城県南部の地震 M5.9、気象庁 浦安市日の出観測点。
289
+ 上段は0.3秒継続の閾値がどこで選ばれるか、左下は同じ記録に対するリアルタイム近似とFFT参照計算がほぼ一致すること、
290
+ 右下は長周期地震動階級を示しています。データ出典: 気象庁「長周期地震動の観測結果」。</sub>
291
+
292
+ ## Documentation
293
+
294
+ - [Algorithm guide (Japanese)](https://github.com/aldichollow/pyshindo/blob/main/docs/algorithm.md)
295
+ - [API reference (Japanese)](https://github.com/aldichollow/pyshindo/blob/main/docs/api.md)
296
+ - [Long-period ground motion class (Japanese)](https://github.com/aldichollow/pyshindo/blob/main/docs/long-period.md)
297
+ - [Observed data I/O (Japanese)](https://github.com/aldichollow/pyshindo/blob/main/docs/data.md)
298
+ - [Validation against JMA's published values (Japanese)](https://github.com/aldichollow/pyshindo/blob/main/docs/validation.md)
299
+ - [Migration guide (Japanese)](https://github.com/aldichollow/pyshindo/blob/main/docs/migration.md)
300
+
301
+ ## Examples
302
+
303
+ Each file in [`examples/`](https://github.com/aldichollow/pyshindo/tree/main/examples) is a runnable script written with `# %%`
304
+ cell markers, so it can be executed top to bottom or stepped through in an
305
+ interactive window.
306
+
307
+ | | |
308
+ | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
309
+ | [`00_quickstart.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/00_quickstart.py) | Every headline result in one page: measured intensity, real-time intensity, PGV/PGD, long-period class, SI value |
310
+ | [`01_measured_intensity.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/01_measured_intensity.py) | The FFT reference calculation and its intermediate waveforms |
311
+ | [`02_realtime_intensity.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/02_realtime_intensity.py) | Real-time replay, and comparison against the FFT reference |
312
+ | [`03_official_jma_record.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/03_official_jma_record.py) | Reproducing JMA's own published intensity from a downloaded record |
313
+ | [`04_filter_designs.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/04_filter_designs.py) | The three causal filters and their named analog stages |
314
+ | [`05_streaming_sample_api.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/05_streaming_sample_api.py) | Feeding the estimator one sample at a time |
315
+ | [`06_peak_velocity.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/06_peak_velocity.py) | PGV, PGD, and why baseline treatment has to be your choice (PGD more so) |
316
+ | [`07_obspy_interop.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/07_obspy_interop.py) | Converting an ObsPy stream into this package's arrays |
317
+ | [`08_long_period.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/08_long_period.py) | Long-period class, per-band classes, and verification against JMA's published spectra |
318
+ | [`09_spectrum_intensity.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/09_spectrum_intensity.py) | SI value, per component, and why its period grid was chosen |
319
+ | [`10_station_map.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/10_station_map.py) | Distribution maps: long-period class and PGV across every station of one event |
320
+ | [`11_response_spectrum.py`](https://github.com/aldichollow/pyshindo/blob/main/examples/11_response_spectrum.py) | The general Sd/Sv/PSA spectrum, and reconstructing an absolute response spectrum from it |
321
+
322
+ ## Development
323
+
324
+ ```bash
325
+ python -m pip install -e ".[dev,plot,obspy]"
326
+ pytest
327
+ ruff check .
328
+ mypy src/pyshindo
329
+ ```
330
+
331
+ The ObsPy interoperability tests skip themselves when ObsPy is not installed.
332
+
333
+ ## Primary references
334
+
335
+ - Japan Meteorological Agency, "Calculation of instrumental seismic intensity."
336
+ - Kunugi, Aoi, and Nakamura (2008), _A real-time processing method of seismic intensity_, DOI: 10.4294/zisin.60.243.
337
+ - Kunugi, Aoi, and Nakamura (2013), _An improved approximation filter for the real-time calculation of seismic intensity_, DOI: 10.4294/zisin.65.223.
338
+ - JP4229337B2 / JP5946067B2 / JP7681907B2 -- see [PATENTS.md](https://github.com/aldichollow/pyshindo/blob/main/PATENTS.md).
339
+
340
+ ---
341
+
342
+ This is a personal, hobby-scale project maintained by one individual, not a company or research group. It comes with no warranty of accuracy, completeness, or fitness for any particular purpose -- use your own judgment, especially for anything safety-related.