<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="http://localhost:8080/feed.xml" rel="self" type="application/atom+xml"/><link href="http://localhost:8080/" rel="alternate" type="text/html" hreflang="en"/><updated>2026-04-14T12:32:11+00:00</updated><id>http://localhost:8080/feed.xml</id><title type="html">SMART Lab</title><subtitle>Associate Professor at Shanghai Ocean University. Researching Fisheries Acoustics, Marine Bioacoustics, and Resource Survey Techniques. </subtitle><entry><title type="html">Tutorial: Visualizing Porpoise Clicks (Waveform &amp;amp; Spectrogram)</title><link href="http://localhost:8080/blog/2026/porpoise-clicks-tutorial/" rel="alternate" type="text/html" title="Tutorial: Visualizing Porpoise Clicks (Waveform &amp;amp; Spectrogram)"/><published>2026-01-22T15:00:00+00:00</published><updated>2026-01-22T15:00:00+00:00</updated><id>http://localhost:8080/blog/2026/porpoise-clicks-tutorial</id><content type="html" xml:base="http://localhost:8080/blog/2026/porpoise-clicks-tutorial/"><![CDATA[<p>This tutorial demonstrates how to visualize the echolocation clicks of the <strong>Yangtze Finless Porpoise</strong>. We will use Python to load a <code class="language-plaintext highlighter-rouge">.wav</code> file, plot its time-domain waveform, and generate a spectrogram to analyze its frequency content.</p> <h3 id="1-the-audio-data">1. The Audio Data</h3> <p>First, let’s listen to the echolocation clicks we are going to analyze.</p> <p><strong>Note:</strong> Yangtze Finless Porpoise clicks are high-frequency signals (peak energy &gt; 100 kHz), which are well above the human hearing range (typically max 20 kHz).</p> <ul> <li><strong>Original Audio:</strong> You will likely hear <em>nothing</em> or very faint clicks because most of the energy is ultrasonic.</li> <li><strong>Downsampled Audio (Pitch Shifted):</strong> We have resampled the audio to 44.1 kHz (slowing it down) to shift the ultrasonic clicks into the audible range for demonstration purposes.</li> </ul> <div class="row mt-3"> <div class="col-sm-6 mt-3 mt-md-0"> <label>Original (Ultrasonic, 2s)</label> <figure> <audio src="/assets/audio/porpoise_clicks_3s.wav" controls=""/> </figure> </div> <div class="col-sm-6 mt-3 mt-md-0"> <label>Audible Version (Downsampled, 6.7s)</label> <figure> <audio src="/assets/audio/porpoise_clicks_3s_downsampling.wav" controls=""/> </figure> </div> </div> <h3 id="2-python-implementation">2. Python Implementation</h3> <p>We will use <code class="language-plaintext highlighter-rouge">librosa</code> for audio processing and <code class="language-plaintext highlighter-rouge">matplotlib</code> for visualization.</p> <h4 id="requirements">Requirements</h4> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>librosa matplotlib numpy
</code></pre></div></div> <h4 id="the-code">The Code</h4> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">librosa</span>
<span class="kn">import</span> <span class="n">librosa.display</span>
<span class="kn">import</span> <span class="n">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="n">matplotlib.ticker</span> <span class="k">as</span> <span class="n">ticker</span>
<span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span>

<span class="c1"># 1. Load the audio file
# sr=None preserves original high sampling rate
</span><span class="n">audio_path</span> <span class="o">=</span> <span class="sh">'</span><span class="s">porpoise_clicks_3s.wav</span><span class="sh">'</span>
<span class="n">y</span><span class="p">,</span> <span class="n">sr</span> <span class="o">=</span> <span class="n">librosa</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="n">audio_path</span><span class="p">,</span> <span class="n">sr</span><span class="o">=</span><span class="bp">None</span><span class="p">)</span>

<span class="c1"># 2. Create the plot with sharex=True to lock the time axis alignment
</span><span class="n">fig</span><span class="p">,</span> <span class="n">ax</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplots</span><span class="p">(</span><span class="n">nrows</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">ncols</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">8</span><span class="p">),</span> <span class="n">sharex</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="c1"># --- Top Plot: Waveform (Time Domain) ---
</span><span class="n">librosa</span><span class="p">.</span><span class="n">display</span><span class="p">.</span><span class="nf">waveshow</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">sr</span><span class="o">=</span><span class="n">sr</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">ax</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">color</span><span class="o">=</span><span class="sh">'</span><span class="s">blue</span><span class="sh">'</span><span class="p">)</span>
<span class="n">ax</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">set_title</span><span class="p">(</span><span class="sh">'</span><span class="s">Time Domain Waveform (Aligned)</span><span class="sh">'</span><span class="p">)</span>
<span class="n">ax</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">set_ylabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Amplitude</span><span class="sh">'</span><span class="p">)</span>
<span class="n">ax</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">grid</span><span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.3</span><span class="p">)</span>

<span class="c1"># --- Bottom Plot: Spectrogram (Frequency Domain) ---
# Use smaller n_fft and hop_length to capture sharp transient clicks
</span><span class="n">n_fft</span> <span class="o">=</span> <span class="mi">1024</span>
<span class="n">hop_length</span> <span class="o">=</span> <span class="mi">256</span>
<span class="n">D</span> <span class="o">=</span> <span class="n">librosa</span><span class="p">.</span><span class="nf">amplitude_to_db</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nf">abs</span><span class="p">(</span><span class="n">librosa</span><span class="p">.</span><span class="nf">stft</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">n_fft</span><span class="o">=</span><span class="n">n_fft</span><span class="p">,</span> <span class="n">hop_length</span><span class="o">=</span><span class="n">hop_length</span><span class="p">)),</span> <span class="n">ref</span><span class="o">=</span><span class="n">np</span><span class="p">.</span><span class="nb">max</span><span class="p">)</span>

<span class="c1"># Draw spectrogram without colorbar
</span><span class="n">img</span> <span class="o">=</span> <span class="n">librosa</span><span class="p">.</span><span class="n">display</span><span class="p">.</span><span class="nf">specshow</span><span class="p">(</span><span class="n">D</span><span class="p">,</span> 
                               <span class="n">y_axis</span><span class="o">=</span><span class="sh">'</span><span class="s">linear</span><span class="sh">'</span><span class="p">,</span> 
                               <span class="n">x_axis</span><span class="o">=</span><span class="sh">'</span><span class="s">time</span><span class="sh">'</span><span class="p">,</span> 
                               <span class="n">sr</span><span class="o">=</span><span class="n">sr</span><span class="p">,</span> 
                               <span class="n">hop_length</span><span class="o">=</span><span class="n">hop_length</span><span class="p">,</span> 
                               <span class="n">ax</span><span class="o">=</span><span class="n">ax</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> 
                               <span class="n">cmap</span><span class="o">=</span><span class="sh">'</span><span class="s">inferno</span><span class="sh">'</span><span class="p">)</span>

<span class="c1"># Set frequency display range from 50,000Hz to 192,000Hz
</span><span class="n">ax</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nf">set_ylim</span><span class="p">([</span><span class="mi">50000</span><span class="p">,</span> <span class="mi">192000</span><span class="p">])</span>

<span class="c1"># Change Y-axis units from Hz to kHz using a Formatter
</span><span class="n">ax</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">yaxis</span><span class="p">.</span><span class="nf">set_major_formatter</span><span class="p">(</span><span class="n">ticker</span><span class="p">.</span><span class="nc">FuncFormatter</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">,</span> <span class="n">pos</span><span class="p">:</span> <span class="sa">f</span><span class="sh">'</span><span class="si">{</span><span class="n">x</span><span class="o">/</span><span class="mi">1000</span><span class="si">:</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="si">}</span><span class="sh">'</span><span class="p">))</span>

<span class="n">ax</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nf">set_title</span><span class="p">(</span><span class="sh">'</span><span class="s">Spectrogram (50 - 192 kHz)</span><span class="sh">'</span><span class="p">)</span>
<span class="n">ax</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nf">set_ylabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Frequency (kHz)</span><span class="sh">'</span><span class="p">)</span>
<span class="n">ax</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nf">set_xlabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Time (s)</span><span class="sh">'</span><span class="p">)</span>

<span class="c1"># 3. Final layout adjustment
</span><span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>
</code></pre></div></div> <h4 id="visualization-result">Visualization Result</h4> <p>The code above generates the following visualization, showing the precise alignment between the click trains in the time domain and their high-frequency signatures in the spectrogram:</p> <div class="row mt-3"> <div class="col-sm mt-3 mt-md-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/porpoise_3s_plot-480.webp 480w,/assets/img/porpoise_3s_plot-800.webp 800w,/assets/img/porpoise_3s_plot-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/porpoise_3s_plot.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" title="Porpoise Clicks Visualization" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <h3 id="3-understanding-the-results">3. Understanding the Results</h3> <ul> <li><strong>Waveform (Top)</strong>: Shows the click trains as distinct spikes in amplitude over time. You can clearly see the rhythmic nature of the echolocation behavior.</li> <li><strong>Spectrogram (Bottom)</strong>: Reveals the spectral energy distribution. Porpoise clicks are broadband but typically have peak energy in very high frequencies (often ultrasonic, &gt;100 kHz), though this recording might be downsampled depending on the hydrophone used.</li> </ul> <hr/> <p><em>This tutorial serves as a basic template for acoustic data analysis in our lab.</em></p>]]></content><author><name>Jianfeng Tong</name></author><category term="education"/><category term="tutorial"/><category term="acoustics"/><category term="python"/><summary type="html"><![CDATA[A step-by-step guide to plotting acoustic data using Python.]]></summary></entry></feed>