<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Hadasa E</title>
    <description>The latest articles on Forem by Hadasa E (@hadasa_73ea7c221359817306).</description>
    <link>https://forem.com/hadasa_73ea7c221359817306</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3650321%2F36de08fb-582c-4dbe-810d-33a4c2d5beda.png</url>
      <title>Forem: Hadasa E</title>
      <link>https://forem.com/hadasa_73ea7c221359817306</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hadasa_73ea7c221359817306"/>
    <language>en</language>
    <item>
      <title>From a Basic U-Net to a Robust SEM Denoiser</title>
      <dc:creator>Hadasa E</dc:creator>
      <pubDate>Mon, 08 Dec 2025 13:53:53 +0000</pubDate>
      <link>https://forem.com/hadasa_73ea7c221359817306/from-a-basic-u-net-to-a-robust-sem-denoiser-3ie9</link>
      <guid>https://forem.com/hadasa_73ea7c221359817306/from-a-basic-u-net-to-a-robust-sem-denoiser-3ie9</guid>
      <description>&lt;p&gt;When I first approached the problem of denoising Scanning Electron Microscope (SEM) images, I assumed the key would be choosing the &lt;em&gt;right architecture&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
I thought in terms of layers, depth, and parameters.&lt;/p&gt;

&lt;p&gt;After some time of research and experiments, I realized the real question is different:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do you translate an engineer’s intuition into a mathematical objective?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words: &lt;strong&gt;how do I tell the model what must be preserved (tiny defects) and what it’s allowed to remove (scan noise)?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you’ve ever been asked to “just do denoising for SEM images” or to improve an existing model and the data + noise feel like a black box, this post is for you.&lt;br&gt;&lt;br&gt;
It walks through the process I went through – from a naïve baseline to a more robust system built around custom loss functions, which is now integrated into a full application.&lt;/p&gt;

&lt;p&gt;This work was done as part of a joint &lt;strong&gt;Applied Materials &amp;amp; Extra-Tech&lt;/strong&gt; bootcamp project.&lt;br&gt;
I’d like to thank our mentors &lt;strong&gt;Roman Kris&lt;/strong&gt; and &lt;strong&gt;Mor Baram&lt;/strong&gt; from Applied Materials for their guidance, technical insights, and thoughtful feedback throughout the project, and our instructors &lt;strong&gt;Shmuel Fine&lt;/strong&gt; and &lt;strong&gt;Sara Shimon&lt;/strong&gt; from Extra-Tech for their support and teaching.&lt;/p&gt;

&lt;p&gt;Together with the team, we built an end-to-end SEM denoising system – from datasets and classical denoising baselines to deep-learning models and a production-style application. In this post I’ll focus on the deep-learning denoisers I worked on: both designing and training the models, and integrating them into the backend API and desktop client.&lt;/p&gt;

&lt;p&gt;SEM-based inspection sits in the critical path of semiconductor manufacturing. If your denoiser removes real defects or leaves too much noise, you’re directly affecting yield, false alarms, and engineers’ trust in the system. That’s why “just denoising” SEM images isn’t a cosmetic enhancement — it’s a core part of the inspection pipeline.&lt;/p&gt;


&lt;h2&gt;
  
  
  The challenge: when noise is part of the signal
&lt;/h2&gt;

&lt;p&gt;SEM (Scanning Electron Microscope) images create a very specific computer-vision challenge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low SNR&lt;/strong&gt; – the noise is not a “nice” Gaussian; it comes from the physics of the scan.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge sensitivity&lt;/strong&gt; – unlike natural images, in wafers every tiny line or texture change may indicate a critical defect in the manufacturing process.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The trade-off&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Too much smoothing → you lose defects (false negatives).
&lt;/li&gt;
&lt;li&gt;Too little denoising → the remaining noise makes it hard for algorithms and engineers to detect real issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was to build a model that &lt;strong&gt;removes noise aggressively enough&lt;/strong&gt; to make analysis easier, but &lt;strong&gt;protects fine structures and critical defects&lt;/strong&gt; as much as possible.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1 – Baseline U-Net and the limits of MSE
&lt;/h2&gt;

&lt;p&gt;I started with a fairly standard setup to get a benchmark:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Architecture:&lt;/strong&gt; vanilla U-Net.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset:&lt;/strong&gt; &lt;em&gt;Tin-balls&lt;/em&gt; – ~50 pairs of noisy + clean images, a small and relatively clean dataset.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loss function:&lt;/strong&gt; MSE + SSIM:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;Lbaseline=MSE(x,y)+λ⋅(1−SSIM(x,y))
\mathcal{L}_{\text{baseline}} = \mathrm{MSE}(x, y) + \lambda \cdot \bigl(1 - \mathrm{SSIM}(x, y)\bigr)
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathcal"&gt;L&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord text mtight"&gt;&lt;span class="mord mtight"&gt;baseline&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathrm"&gt;MSE&lt;/span&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;λ&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;&lt;span class="delimsizing size1"&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathrm"&gt;SSIM&lt;/span&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="delimsizing size1"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;The metrics looked “okay”, but visually something was off:&lt;br&gt;&lt;br&gt;
the model tended to &lt;strong&gt;smear sharp edges&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;MSE (Mean Squared Error) heavily punishes large errors (outliers), which encourages the model to “average out” local extremes.&lt;br&gt;&lt;br&gt;
Instead of reconstructing the true texture, it prefers a slightly blurred version that is safer from the loss perspective.&lt;/p&gt;

&lt;p&gt;This baseline was still very important: it provided a &lt;strong&gt;stable reference point&lt;/strong&gt; to measure every later improvement against.&lt;/p&gt;


  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5dfox609ieyfpxe1p7ur.png" alt="Tin-balls SEM: noisy vs ground truth vs denoised"&gt;
    &lt;small&gt;&lt;em&gt;
      Source:
      &lt;a href="https://iopscience.iop.org/article/10.1088/1361-6501/ad7e41" rel="noopener noreferrer"&gt;
        https://iopscience.iop.org/article/10.1088/1361-6501/ad7e41
      &lt;/a&gt;
    &lt;/em&gt;&lt;/small&gt;
  
  


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F773an5lqcemyi1rl5a5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F773an5lqcemyi1rl5a5z.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All experiments were implemented in PyTorch, with training and evaluation scripts sharing the same config-driven codebase.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2 – Rethinking the problem: moving to Charbonnier loss
&lt;/h2&gt;

&lt;p&gt;Once the baseline worked “fine on paper” but clearly blurred edges and defects, I stopped trying to make the model bigger and asked a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Where exactly is the model wrong?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Looking at examples, difference maps and metrics, I noticed a recurring pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model reacted &lt;strong&gt;too aggressively to single noisy pixels&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It was willing to oversmooth entire regions just to reduce a few large local errors
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the problem was not only the model – it was &lt;strong&gt;how we defined “error”&lt;/strong&gt; in the loss.&lt;/p&gt;

&lt;p&gt;At this point I went looking for a loss function that would better match SEM noise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less sensitive to outliers
&lt;/li&gt;
&lt;li&gt;Better at preserving textures and edges
&lt;/li&gt;
&lt;li&gt;Still smooth and differentiable for training deep networks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After comparing several options, &lt;strong&gt;Charbonnier loss&lt;/strong&gt; stood out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It behaves similarly to ( L_1 ) (more robust than MSE),
&lt;/li&gt;
&lt;li&gt;but is smooth and differentiable everywhere:&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;Lchar(x,y)=(x−y)2+ϵ2
\mathcal{L}_{\text{char}}(x, y) = \sqrt{(x - y)^2 + \epsilon^2}
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathcal"&gt;L&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord text mtight"&gt;&lt;span class="mord mtight"&gt;char&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord sqrt"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span class="svg-align"&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;ϵ&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="hide-tail"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;I re-trained the &lt;em&gt;same&lt;/em&gt; U-Net, on the &lt;em&gt;same&lt;/em&gt; Tin-balls dataset, with the &lt;em&gt;same&lt;/em&gt; training setup –&lt;br&gt;&lt;br&gt;
the only change was replacing the MSE term with Charbonnier.&lt;/p&gt;

&lt;p&gt;After this change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PSNR and SSIM improved consistently 

&lt;ul&gt;
&lt;li&gt;PSNR improved from 27.4 dB to 30.7 dB &lt;/li&gt;
&lt;li&gt;SSIM increased from 0.83 to 0.88&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Visually, edges looked more natural, with much less over-smoothing around defects
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frn1mnywm74zpfubdy9n7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frn1mnywm74zpfubdy9n7.png" alt="Bar chart comparing baseline vs fine-tuned model (PSNR, SSIM, FSIM, CNR, UIQ)"&gt;&lt;/a&gt;&lt;br&gt;
You can see that the fine-tuned model consistently improves all metrics over the baseline, not just PSNR/SSIM.&lt;/p&gt;

&lt;p&gt;This was the &lt;strong&gt;first mindset shift&lt;/strong&gt; in the project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Instead of “make the model bigger”, start with &lt;strong&gt;designing the right objective&lt;/strong&gt; for the model to optimize.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Step 3 – Cracking wafers: adding structure and edge awareness
&lt;/h2&gt;

&lt;p&gt;When I moved to the more complex &lt;strong&gt;Wafers&lt;/strong&gt; dataset — roughly 1,000 wafer SEM image pairs with high-intensity, physically-inspired synthetic noise added on top of clean references — the requirements changed again.  &lt;/p&gt;

&lt;p&gt;Here, periodic patterns and tiny defects matter even more.&lt;/p&gt;

&lt;p&gt;To help the model “understand” that, I upgraded the loss with two additional components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MS-SSIM (Multi-Scale SSIM)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Instead of measuring similarity at a single resolution, MS-SSIM looks at multiple scales.&lt;br&gt;&lt;br&gt;
This helps the model preserve both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;global structure (macro), and
&lt;/li&gt;
&lt;li&gt;fine details (micro).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edge-aware loss&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I added a term based on image gradients (using a Sobel operator) that penalizes the model when it &lt;strong&gt;breaks or smears edges&lt;/strong&gt; that exist in the input image.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full loss became:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;Ltotal=Lchar+α⋅LMS-SSIM+β⋅Ledge
\mathcal{L}{\text{total}} =
\mathcal{L}{\text{char}} +
\alpha \cdot \mathcal{L}{\text{MS-SSIM}} +
\beta \cdot \mathcal{L}{\text{edge}}
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathcal"&gt;L&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;total&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathcal"&gt;L&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;char&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;α&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathcal"&gt;L&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;MS-SSIM&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;β&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathcal"&gt;L&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;edge&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;The result was a clear jump in quality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defects stayed &lt;strong&gt;sharp and visible&lt;/strong&gt; on a clean background
&lt;/li&gt;
&lt;li&gt;The model outperformed classical denoising methods such as BM3D or bilateral filters
&lt;/li&gt;
&lt;li&gt;Both metrics and visual inspection aligned much better with what domain experts expected to see&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftejtm8f16c6zdsgljbe6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftejtm8f16c6zdsgljbe6.png" alt="Wafer SEM: noisy vs denoised result"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What didn’t work (and what I learned from it)
&lt;/h2&gt;

&lt;p&gt;A big part of this project was &lt;strong&gt;testing ideas that &lt;em&gt;didn’t&lt;/em&gt; make it into production&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
They were still valuable – each one refined my understanding of the problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Residual prediction
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Idea:&lt;/strong&gt; predict the &lt;strong&gt;noise&lt;/strong&gt; (noisy − clean) instead of the clean image itself.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Motivation:&lt;/strong&gt; the model might find it easier to focus purely on the noise component and avoid touching edges and defects. This is common in many denoising architectures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In practice&lt;/strong&gt;, with SEM noise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Training became less stable
&lt;/li&gt;
&lt;li&gt;I saw visual artifacts, especially around edges and defect regions
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; residual learning is not automatically a win – especially when the noise structure is complex and ground truth is limited.&lt;/p&gt;




&lt;h3&gt;
  
  
  Deeper U-Net
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Idea:&lt;/strong&gt; increase the model capacity (more depth) to better capture complex patterns and spatially varying noise, especially on wafers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expectation:&lt;/strong&gt; better performance on the harder datasets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reality:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Training and inference time increased significantly
&lt;/li&gt;
&lt;li&gt;No clear, consistent improvement in metrics or visual quality
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson learned:&lt;/strong&gt; &lt;strong&gt;more capacity is not always more value&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
With a well-designed loss, the original architecture was already “good enough”, and adding depth didn’t justify the extra cost.&lt;/p&gt;




&lt;h3&gt;
  
  
  Edge map as an extra input channel
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Idea:&lt;/strong&gt; explicitly provide an edge map (e.g., Sobel) as an additional input channel, hoping the model would pay more attention to boundaries and defects from the first layer.&lt;/p&gt;

&lt;p&gt;Once I switched to a loss that already included an edge-aware term, this turned out to be unnecessary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model learned to emphasize edges directly from the raw image
&lt;/li&gt;
&lt;li&gt;There was no real gain in metrics or visual quality
&lt;/li&gt;
&lt;li&gt;The input pipeline became more complex for no benefit
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This reinforced the idea that it’s often better to &lt;strong&gt;encode our priorities in the loss&lt;/strong&gt;, not just in the inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarking against classical methods
&lt;/h2&gt;

&lt;p&gt;To validate the approach, I compared the U-Net model against&lt;br&gt;
established classical denoisers on the same test set:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvau2c8aftbmdfyev8w24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvau2c8aftbmdfyev8w24.png" alt="Benchmark table: classical methods vs U-Net"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The deep learning model clearly outperforms the classical methods in terms of&lt;br&gt;
quality metrics.&lt;/p&gt;

&lt;p&gt;However, the story is more nuanced when you factor in &lt;strong&gt;speed and deployment&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Classical denoisers&lt;/strong&gt; are simple to deploy, run on CPU, and require no training data or GPUs, which makes them attractive for quick experiments or low-resource environments.&lt;/li&gt;
&lt;li&gt;At the same time, they are typically tuned to relatively simple noise models and often struggle with the complex, structured noise in SEM images, forcing a compromise between over-smoothing and under-denoising.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Our U-Net&lt;/strong&gt; offers the best quality, but requires:

&lt;ul&gt;
&lt;li&gt;Initial training time and labeled noisy/clean pairs&lt;/li&gt;
&lt;li&gt;A GPU for real-time processing (or runs ~2–3× slower on CPU)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The trade-off:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
For high-throughput production lines where quality is critical and &lt;br&gt;
infrastructure exists → deep learning wins.&lt;br&gt;&lt;br&gt;
For prototyping or lower-volume scenarios → classical methods remain practical.&lt;/p&gt;




&lt;h2&gt;
  
  
  Engineering it for production
&lt;/h2&gt;

&lt;p&gt;A good model is not enough if it only lives in a notebook.&lt;br&gt;&lt;br&gt;
From the start, the project was built with production in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modularity&lt;/strong&gt; – clear separation between data loaders, model definitions, metrics, and experiment code.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Config-driven experiments&lt;/strong&gt; – every run is defined by a config file, which makes experiments reproducible and makes it easy to tweak hyper-parameters and loss components.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation framework&lt;/strong&gt; – built a Python-based evaluation pipeline using PSNR, SSIM, and several custom metrics, with automated reporting and visual side-by-side comparisons for all methods (baseline, classical denoisers, and U-Net variants).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend &amp;amp; storage&lt;/strong&gt; – developed a FastAPI backend, containerized with Docker, using PostgreSQL for metadata/metrics and MinIO as an object store for all noisy/clean/denoised images.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desktop client&lt;/strong&gt; – created a PyQt desktop application that talks to the API and lets users interactively compare methods (classical vs. deep learning) both visually and via metrics, image by image or over entire runs.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03syup5k755154lo12xz.png" alt="PyQt SEM denoising client: noisy vs denoised images with metrics and run list"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, the desktop client lets engineers scroll through wafers, switch between classical and deep-learning models, and inspect both the images and their metrics side by side.&lt;/p&gt;

&lt;p&gt;This turned the project from “just a research notebook” into a reproducible, debuggable system that other engineers can run, extend, and plug into existing SEM analysis workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;This project taught me that in deep learning, &lt;strong&gt;problem definition is just as important as model design&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Shifting the focus from “which architecture should I use?” to&lt;br&gt;&lt;br&gt;
“&lt;strong&gt;what exactly do I want the model to optimize?&lt;/strong&gt;” – via careful loss engineering –&lt;br&gt;&lt;br&gt;
is what allowed us to reach strong performance while preserving the tiny, critical defects that other methods tended to erase.&lt;/p&gt;

&lt;p&gt;In the end, a powerful model is not just about using the latest tools.&lt;br&gt;&lt;br&gt;
It’s about combining them with a &lt;strong&gt;deep understanding of the data and the domain&lt;/strong&gt; – and turning that understanding into the right objective for the model to learn.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>deeplearning</category>
      <category>computervision</category>
      <category>python</category>
    </item>
  </channel>
</rss>
