Skip to content

Process a noisy sensor signal

Sample a fictional noisy signal, expose aliasing, compare two filters, inspect a DFT and apply hysteresis.

Signal Sage: I want you to preserve the raw trace before judging any filtered result., your practice guide

Signal Sage: I want you to preserve the raw trace before judging any filtered result.
Predict the reading, then compare it with the measurement.

Python 3 in your browser (JupyterLite)

Python · no install

Tier 2 · Web · paste-in setup · No account

Version tested: Python 3.12.7 / Pyodide 0.27.6 in JupyterLite 0.6.4; Chromium 148.0.7778.96; captureSource playwright:jupyterlite. Date: 2026-09-10.

Open the notebook in your browser and run each Python cell; no install or account is needed.

Three ways to run: use JupyterLite here with no install; run main.py locally from the downloadable lab folder; or open the same notebook in Google Colab.

Open in your browser (new tab)

Steps

Screens captured against Python 3 in your browser (JupyterLite) Python 3.12.7 / Pyodide 0.27.6 in JupyterLite 0.6.4; Chromium 148.0.7778.96; captureSource playwright:jupyterlite on 2026-09-10; the tool may have moved on — the text steps are the contract.

  1. 1 Step 1

    Do
    In the JupyterLite notebook, run the Step 1 cell to define the synthetic one-second signal and noise seed.
    You will see
    Input: execute notebook step 1 with its stated deterministic fixture. Observed: duration=1.0 s; sample rate=20 Hz; samples=20 Observed: components: baseline=20, wanted=1 Hz at amplitude 2 Observed: interference=9 Hz at amplitude 0.8; seeded noise=+/-0.35
    Why it matters
    Expected: the seed-42 fixture defines a one-second trace with 20 samples. The output gives the wanted 1 Hz and interference 9 Hz components. Stated units and bounded noise make later processing reproducible. Limit: units are arbitrary; this is not a calibrated sensor model.
    JupyterLite step 1 showing the signal contract.
    Step 1 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  2. 2 Step 2

    Do
    In the JupyterLite notebook, run the Step 2 cell to sample at 20 Hz and 8 Hz and compare the strongest DFT bin.
    You will see
    Input: execute notebook step 2 with its stated deterministic fixture. Observed: 20 20 1 9 Hz visible Observed: 8 8 1 9 Hz aliases to 1 Hz Observed: Nyquist requires sample rate greater than twice the highest component.
    Why it matters
    Expected: 8 Hz sampling aliases the 9 Hz interference into the 1 Hz bin. The run shows the 9 Hz component as visible at 20 Hz and aliased at 8 Hz. This demonstrates why sample rate must exceed twice the highest component. Limit: the analysis uses this finite synthetic signal.
    JupyterLite step 2 showing the aliasing comparison.
    Step 2 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  3. 3 Step 3

    Do
    In the JupyterLite notebook, run the Step 3 cell to print the beginning of the seeded raw trace.
    You will see
    Input: execute notebook step 3 with its stated deterministic fixture. Observed: 0 0.00 20.10 Observed: 1 0.05 20.53 Observed: 2 0.10 20.55
    Why it matters
    Expected: the unfiltered record keeps the original seeded samples. The first three local values are 20.10, 20.53, and 20.55 at the shown times. The stored raw trace remains available for comparisons. Limit: arbitrary units and synthetic noise are not sensor readings.
    JupyterLite step 3 showing raw timestamped samples.
    Step 3 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  4. 4 Step 4

    Do
    In the JupyterLite notebook, run the Step 4 cell to compare a moving average with an exponential filter.
    You will see
    Input: execute notebook step 4 with its stated deterministic fixture. Observed: 0 20.10 20.10 20.10 Observed: 3 22.07 21.05 20.96 Observed: 5 22.92 22.10 21.72
    Why it matters
    Expected: the filters smooth the same raw samples using different histories. At index 5, moving output is 22.10 and exponential output is 21.72. The finite moving window and decaying exponential history respond differently. Limit: this is a six-sample comparison on one fixture.
    JupyterLite step 4 comparing two filter outputs.
    Step 4 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  5. 5 Step 5

    Do
    In the JupyterLite notebook, run the Step 5 cell to calculate the strongest bins with a standard-library DFT.
    You will see
    Input: execute notebook step 5 with its stated deterministic fixture. Observed: 1 1.009 Observed: 9 0.365 Observed: 10 0.070
    Why it matters
    Expected: the one-second DFT has 1 Hz bins and exposes strong components. The run ranks bins 1, 9, and 10 with magnitudes 1.009, 0.365, and 0.070. The 1 Hz wanted tone and 9 Hz interference are the two largest peaks. Limit: rectangular-window leakage is not corrected.
    JupyterLite step 5 showing DFT peak bins.
    Step 5 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  6. 6 Step 6

    Do
    In the JupyterLite notebook, run the Step 6 cell to apply high and low hysteresis thresholds.
    You will see
    Input: execute notebook step 6 with its stated deterministic fixture. Observed: 0 20.10 NORMAL Observed: 4 21.08 HIGH Observed: 11 20.11 NORMAL
    Why it matters
    Expected: hysteresis enters HIGH above 21.0 and leaves below 20.5. The run transitions to HIGH at index 4 and back to NORMAL at 11. Separate entry and exit thresholds reduce chatter near the boundary. Limit: these thresholds are for the synthetic fixture only.
    JupyterLite step 6 showing hysteresis transitions.
    Step 6 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  7. 7 Step 7

    Do
    In the JupyterLite notebook, run the Step 7 cell to validate reproducibility and state the model limits.
    You will see
    Input: execute notebook step 7 with its stated deterministic fixture. Observed: same seed reproduces raw trace: PASS Observed: raw trace preserved before filtering: PASS Observed: Decision: 20 Hz resolves this fixture; measure real bandwidth before deployment.
    Why it matters
    Expected: repeatability and raw-data preservation checks pass for this code. Both checks passed in the local run; the DFT caveat remains explicit. The fixture supports the 20 Hz sampling choice for its 9 Hz component. Limit: measure real sensor bandwidth before deployment.
    JupyterLite step 7 showing validation and scope.
    Step 7 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)

Chapter checks

These questions refer to the chapter’s examples. Use the return links to review their answers.

  1. What does one LSB, the least significant bit, represent for an ADC?

    Return to the chapter’s knowledge check
  2. Readings are mostly steady but occasionally show single large spikes from electrical interference. Which filter best removes the spikes while preserving the true level?

    Return to the chapter’s knowledge check

Caution

The values are fictional and the DFT uses a rectangular window. Measure the real sensor bandwidth, sample clock and ADC behavior before choosing deployment settings.

Return to Signal Processing: Quantization and Filtering · Browse Labs