Where It Actually Started: A Semester in the SoC Design Lab

Every DHM and ARMS paper I’ve written since started, in a very literal sense, in a small lab room on the fifth floor of the engineering building at Dong-A University — S06-RS834, home of the SoC Design Lab, Department of Electronics Engineering. I spent a semester there as an undergraduate under Korea’s research credit program (연구학점제) — coursework that earns academic credit for hands-on lab research instead of sitting in a lecture hall. Below are the actual weekly notes, kept close to their original structure rather than summarized down.

Week 1 — Digital Image Fundamentals

1. Screen Coordinate System

The coordinate system used in computer graphics and image processing differs structurally from the standard mathematical Cartesian system.

  • Origin: the top-left corner of the screen is $(0, 0)$.
  • x-axis: increases left → right.
  • y-axis: increases top → bottom.
(0,0) ───────────────────────> X (increases left to right)
  │
  │
  ▼
  Y (increases top to bottom)

When representing an image as a matrix, rows correspond to y and columns correspond to x — so image coordinates are indexed as $I(y, x)$, not $I(x, y)$.

5. Why this mattered later

This basic image math and representation later became the foundation for numerical reconstruction in Digital Holographic Microscopy and scattering-medium removal (dehazing) algorithms. Knowing the quantization characteristics of an image precisely — e.g. when designing Angular Spectrum Method propagation on a grid that satisfies the sampling theorem, or when controlling Poisson-Gaussian noise — turns out to be decisive for reducing modeling error.

Week 2 — C Programming Fundamentals

2. Basic Data Types

Category Type Size Range
Integer short 2 bytes $-2^{15} \sim 2^{15}-1$
  unsigned short 2 bytes $0 \sim 2^{16}-1$
  int 4 bytes $-2^{31} \sim 2^{31}-1$
  unsigned int 4 bytes $0 \sim 2^{32}-1$
  long int 4 bytes $-2^{31} \sim 2^{31}-1$ (typical 32/64-bit compiler)
  unsigned long 4 bytes $0 \sim 2^{32}-1$
Float float 4 bytes single precision, ≈ $\pm 3.4 \times 10^{38}$
  double 8 bytes double precision, ≈ $\pm 1.79 \times 10^{308}$
  long double 8 bytes extended precision, ≈ $\pm 1.79 \times 10^{308}$ (12–16 bytes on some platforms)

In C environments without a dedicated boolean type, logical/relational operations return integer 1 (true) or 0 (false) into the control flow.

4. Control Flow

for loop’s three components — each with a distinct execution timing:

for (initialization; condition; increment) {
    /* loop body */
}
  • Initialization: runs exactly once, at loop start.
  • Condition: evaluated before every iteration; loop exits the moment this is false.
  • Increment: runs after each iteration’s body completes.

switch-case — a jump-table alternative to nested if-else:

graph TD
    classDef dec fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px;
    classDef proc fill:#e8f5e9,stroke:#4caf50,stroke-width:1px;
    classDef startEnd fill:#ececff,stroke:#9370db,stroke-width:2px;

    Start(["Switch Expression"]):::startEnd --> Case1{"case 1?"}:::dec
    Case1 -- "Yes" --> Action1["Code 1"]:::proc
    Action1 --> Break1{"break present?"}:::dec
    Break1 -- "Yes" --> End(["Exit switch"]):::startEnd
    Break1 -- "No (fall-through)" --> Action2

    Case1 -- "No" --> Case2{"case 2?"}:::dec
    Case2 -- "Yes" --> Action2["Code 2"]:::proc
    Action2 --> Break2{"break present?"}:::dec
    Break2 -- "Yes" --> End
    Break2 -- "No" --> DefaultAction["default code"]:::proc

    Case2 -- "No" --> DefaultAction
    DefaultAction --> End
  • If break is omitted, execution falls through into the next case block unconditionally — a common bug source.
  • default plays the role of a trailing else.

while vs. do-while — differ in when the condition is checked:

  • while (check-first): if the condition is false on entry, the body never runs.
  • do-while (check-last): the body always runs at least once, condition is checked afterward. Requires a trailing ; after the closing while(...).

Week 3 — C Memory & Systems

1. Pointers & Memory Access

  • Pointer declaration (*): int *p; declares “a variable that holds an address.”
  • Address-of (&): obtains a variable’s physical address.
  • Array name decay: a bare array name is a constant pointer to element 0 — int *p = arr;, never &arr for this purpose.
  • Dereference (*): *p reads/writes the value at the address p holds; printing p bare prints the address itself.
graph TD
    classDef ptr fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px;
    classDef val fill:#ffe6cc,stroke:#d79b00,stroke-width:2px;
    classDef addr fill:#f5f5f5,stroke:#9e9e9e,stroke-width:1px;

    subgraph Pointer ["Pointer Variable"]
        P["int *p = arr;"]:::ptr
    end
    subgraph MemoryArray ["arr[5] Layout"]
        A0["arr[0]=10"]:::val
        A1["arr[1]=20"]:::val
        A2["arr[2]=30"]:::val
        A3["arr[3]=40"]:::val
        A4["arr[4]=50"]:::val
    end
    subgraph PhysicalAddr ["Physical Addresses"]
        Addr0["0x1000"]:::addr
        Addr1["0x1004"]:::addr
        Addr2["0x1008"]:::addr
        Addr3["0x100C"]:::addr
        Addr4["0x1010"]:::addr
    end
    P -- "Points to array start" --> Addr0
    Addr0 --- A0
    Addr1 --- A1
    Addr2 --- A2
    Addr3 --- A3
    Addr4 --- A4
    P -. "dereference *p" .-> A0
    P -. "dereference *(p+1)" .-> A1

3. Dynamic Memory Allocation

[program start] ──> malloc/calloc ──> heap space allocated ──> free(p) (prevents dangling)
  • malloc(size) — grabs a contiguous heap block; contents are garbage, uninitialized.
  • calloc(num, size) — same, but zero-clears every byte before returning.
  • realloc(ptr, size) — resizes an existing block; existing data is preserved (copied to a new location if it can’t grow in place).
#include <stdio.h>
#include <stdlib.h>

int main() {
    int i, hap = 0;
    int *p = NULL;
    int count;

    printf("How many numbers to enter?: ");
    if (scanf_s("%d", &count) != 1 || count <= 0) {
        printf("Invalid input.\n");
        return 1;
    }

    p = (int*)malloc(sizeof(int) * count);
    if (p == NULL) {
        printf("Dynamic memory allocation was refused.\n");
        return 1;
    }

    for (i = 0; i < count; i++) {
        printf("Enter element %d: ", i + 1);
        scanf_s("%d", &(*(p + i)));
    }

    for (i = 0; i < count; i++) {
        hap = hap + *(p + i);
    }

    printf("Sum of all entered elements: %d\n", hap);

    free(p);
    p = NULL; // prevent dangling pointer reuse

    return 0;
}

5. Debugging Troubleshooting: Visual Studio PDB Loading Errors

A PDB (Program Database) file is the symbol database that 1:1-maps compiled machine binary back to human-readable source. When Visual Studio’s debugger (F5) launches, it tries to resolve Windows system DLL symbols (kernel32.dll, etc.) against Microsoft’s remote symbol server — and if that server is slow or unreachable, debug entry stalls or hangs entirely.

Fix: Debug → Options → Debugging → Symbols, and uncheck Microsoft Symbol Servers. This eliminates the network round-trip entirely and forces the debugger to rely only on local build symbols.

Faster daily-use tip: for pure logic checks that don’t need kernel-level stack tracing, Ctrl+F5 (Start Without Debugging) skips the symbol-loading path entirely and launches the built binary directly.

Week 4 — OpenCV Image Matrix Basics

1. cv::imread Loading Mode Analysis

cv::Mat imread(const cv::String& filename, int flags = cv::IMREAD_COLOR);
  • cv::IMREAD_COLOR (default, flag 1) — force-loads as 3-channel BGR (24-bit), even if the source file is grayscale (duplicated across channels).
  • cv::IMREAD_GRAYSCALE (flag 0) — loads as 1-channel grayscale (8-bit); color sources are compressed via the internal conversion formula.
  • cv::IMREAD_UNCHANGED (flag -1) — loads the file’s native format with no conversion; required for 4-channel BGRA sources with an alpha/transparency channel.
graph TD
    classDef input fill:#ffffff,stroke:#333333,stroke-width:2px;
    classDef op fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px;
    classDef mem fill:#ffe6cc,stroke:#d79b00,stroke-width:2px;
    classDef gray fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px;

    InputFile["Input file (e.g. RGBA 4-channel source)"]:::input

    subgraph ReadingOptions ["cv::imread Loading Flags"]
        FlagColor["cv::IMREAD_COLOR (1)"]:::op
        FlagGray["cv::IMREAD_GRAYSCALE (0)"]:::op
        FlagUnchanged["cv::IMREAD_UNCHANGED (-1)"]:::op
    end

    subgraph MemoryLayout ["Memory Allocation (cv::Mat)"]
        MatColor["cv::Mat (3-ch BGR) / [B][G][R] forced replication"]:::mem
        MatGray["cv::Mat (1-ch Gray) / Y=0.299R+0.587G+0.114B"]:::gray
        MatUnchanged["cv::Mat (4-ch BGRA) / original layout preserved"]:::mem
    end

    InputFile --> FlagColor --> MatColor
    InputFile --> FlagGray --> MatGray
    InputFile --> FlagUnchanged --> MatUnchanged

Week 5 — Neighbor-Pixel Convolution Design

1. 2D Spatial Convolution — Math

Spatial filtering multiplies a pixel and its neighbors by a filter kernel’s weights and sums the result. For a $(2M+1) \times (2M+1)$ kernel $K$ applied to image $I$:

\[O(y, x) = (I * K)(y, x) = \sum_{j=-M}^{M} \sum_{i=-M}^{M} I(y - j, x - i) \cdot K(j + M, i + M)\]

This single operation underlies most low-level CV: edge detection (Sobel/Laplacian), Gaussian blur/denoising, sharpening.

graph TD
    classDef edge fill:#ffebee,stroke:#c62828,stroke-width:2px;
    classDef valid fill:#e8f5e9,stroke:#4caf50,stroke-width:2px;
    classDef mask fill:#dae8fc,stroke:#6c8ebf,stroke-width:2px;

    subgraph OutOfBounds ["Out of Bounds"]
        O11["?"]:::edge
        O12["?"]:::edge
        O13["?"]:::edge
        O21["?"]:::edge
        O31["?"]:::edge
    end
    subgraph InBounds ["Valid Image Pixels"]
        P22["I(0,0)"]:::valid
        P23["I(0,1)"]:::valid
        P32["I(1,0)"]:::valid
        P33["I(1,1)"]:::valid
    end
    subgraph Mask3x3 ["3x3 Mask"]
        M11["K(-1,-1)"]:::mask
        M12["K(-1,0)"]:::mask
        M13["K(-1,1)"]:::mask
        M21["K(0,-1)"]:::mask
        M22["K(0,0) Anchor"]:::mask
        M23["K(0,1)"]:::mask
        M31["K(1,-1)"]:::mask
        M32["K(1,0)"]:::mask
        M33["K(1,1)"]:::mask
    end
    M22 --> |center align| P22
    M11 -.-> |out-of-bound error| O11
    M12 -.-> O12
    M13 -.-> O13
    M21 -.-> O21
    M31 -.-> O31
    M23 --> P23
    M32 --> P32
    M33 --> P33

2. Zero Padding for Boundary Handling

Border pixels can’t have a full neighborhood — the kernel would read past the image edge. Zero padding surrounds the image with a margin of zero-valued pixels so every pixel, including original border pixels, gets a complete neighborhood.

For a $W \times H$ image padded by margin $M$, the padded matrix is $(W+2M) \times (H+2M)$, and original coordinate $(y,x)$ maps to padded coordinate $(y+M, x+M)$:

  Padding Margin = 1
  (0,0) -> (1,1)
  ┌───┬───┬───┬───┬───┐
  │ 0 │ 0 │ 0 │ 0 │ 0 │  <- padding margin (y=0)
  ├───┼───┼───┼───┼───┤
  │ 0 │ P │ P │ P │ 0 │  <- valid data region (y=1)
  ├───┼───┼───┼───┼───┤
  │ 0 │ P │ P │ P │ 0 │  (P = original pixels)
  └───┴───┴───┴───┴───┘

Week 6 — Spatial Filtering, Kernel Design & the ROI Bug

The bug that actually taught me something

Implementing a 5×5 filter, I wrote this:

cv::Mat dst(SubM.rows, SubM.cols, CV_8UC1);

for (int y = 0; y < SubM.rows; ++y) {
    const uchar* prevRow2 = SubM.ptr<uchar>(y - 2); // segfaults here
    const uchar* prevRow1 = SubM.ptr<uchar>(y - 1);
    const uchar* currRow  = SubM.ptr<uchar>(y);
    // ... 5x5 filter math
}

The reasoning felt airtight at the time: SubM is an ROI sub-matrix sharing its physical heap buffer with the parent cv::Mat — no copy, just a view. So walking the pointer backward past the ROI’s own boundary with a negative row index should land on the parent’s actual neighboring pixels.

It wasn’t airtight, for two reasons I didn’t know yet:

  1. Mat::ptr<T>(y) runs an internal CV_DbgAssert(y >= 0 && y < rows) bounds check — fires immediately in debug builds.
  2. In release builds where the assert compiles out, the negative offset does walk into the parent’s physical memory — but the sub-matrix’s row stride doesn’t match the parent’s stride once the ROI’s column offset is accounted for. Naive pointer subtraction across that mismatch lands on a diagonally-wrong address, not “the row above.” Result: streak artifacts at frame boundaries, or a walk straight past datastart into OS-protected memory → segfault.

Fix — extend the ROI’s officially addressable region first, via adjustROI, instead of hand-rolling pointer arithmetic:

cv::Mat tempSub = SubM.clone();       // preserve the original view
tempSub.adjustROI(2, 2, 2, 2);        // expand the header 2px each direction

for (int y = 0; y < SubM.rows; ++y) {
    const uchar* srcRow_m2 = tempSub.ptr<uchar>(y);
    const uchar* srcRow_m1 = tempSub.ptr<uchar>(y + 1);
    const uchar* srcRow_0  = tempSub.ptr<uchar>(y + 2);
    // now safe and correct
}

adjustROI doesn’t move any data — it renegotiates the matrix header’s addressable window against the parent buffer’s real bounds. The gap between my first attempt and this one is the gap between “I know this is a shallow-copy view” and “I understand exactly what geometry that view’s header is and isn’t allowed to promise me.”

3. High-Performance Pixel-Pointer 2D Sliding Window (C++)

Projecting the SoC Design Lab’s cache-efficiency and HW-SW co-design lens onto this: skip Mat::at()’s repeated 2D address math and cache the row pointer once per kernel row instead.

cv::Mat apply5x5FilterFast(const cv::Mat& src, const float kernel[5][5], float denom) {
    CV_Assert(src.type() == CV_8UC1);
    int H = src.rows, W = src.cols;
    cv::Mat dst(H, W, CV_8UC1, cv::Scalar(0));
    const int radius = 2;

    for (int y = radius; y < H - radius; ++y) {
        uchar* dstRow = dst.ptr<uchar>(y);
        for (int x = radius; x < W - radius; ++x) {
            float sum = 0.0f;
            for (int ky = -radius; ky <= radius; ++ky) {
                const uchar* srcRow = src.ptr<uchar>(y + ky); // fetched once per kernel row
                sum += srcRow[x - 2] * kernel[ky + 2][0]
                     + srcRow[x - 1] * kernel[ky + 2][1]
                     + srcRow[x    ] * kernel[ky + 2][2]
                     + srcRow[x + 1] * kernel[ky + 2][3]
                     + srcRow[x + 2] * kernel[ky + 2][4];
            }
            dstRow[x] = cv::saturate_cast<uchar>(sum / denom);
        }
    }
    return dst;
}

int main() {
    float gaussian5x5[5][5] = {
        {2,  4,  5,  4, 2},
        {4,  9, 12,  9, 4},
        {5, 12, 15, 12, 5},
        {4,  9, 12,  9, 4},
        {2,  4,  5,  4, 2}
    };
    float denom = 159.0f;
    cv::Mat dummyImg(100, 100, CV_8UC1, cv::Scalar(100));
    cv::Mat result = apply5x5FilterFast(dummyImg, gaussian5x5, denom);
    std::cout << "Low-level pointer-based 5x5 Gaussian spatial filter complete." << std::endl;
    return 0;
}

Why this is fast:

  1. src.ptr<uchar>(y + ky) is hoisted out of the innermost loop — loaded once per kernel row instead of recomputed as a 2D address on every tap, maximizing spatial/temporal cache locality.
  2. srcRow[x-2]srcRow[x+2] are contiguous memory references that compile down to register-offset addressing, giving the compiler room for instruction-level parallelism.

Week 7 — cv::Mat Physical Structure

1. Dual Physical Structure (Header vs. Data Pointer)

cv::Mat splits into a small, fixed-size header (dims, type/step, refcount pointer, data pointer) and a separately-allocated heap data buffer.

graph TD
    classDef header fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px;
    classDef buffer fill:#ffe6cc,stroke:#d79b00,stroke-width:2px;
    classDef counter fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
    classDef main fill:#f3e5f5,stroke:#8e24aa,stroke-width:2px;

    subgraph MatObject ["Stack Header Area"]
        M["cv::Mat object"]:::main
        H_Dims["rows / cols / dims"]:::header
        H_Type["type / step"]:::header
        H_Ref["refcount pointer"]:::header
        H_Data["data pointer"]:::header
        M --> H_Dims
        M --> H_Type
        M --> H_Ref
        M --> H_Data
    end
    subgraph HeapArea ["Heap Memory Area"]
        U["UMatData shared refcount"]:::counter
        D["Physical data buffer B0 G0 R0 B1 G1 R1 ..."]:::buffer
    end
    H_Ref -- "manages memory lifetime" --> U
    H_Data -- "points to actual data" --> D
  • Header: fixed byte size; copying it is $O(1)$.
  • Data buffer: the large heap region pixels actually live in.
  • This split is why cropping/ROI is instant: only a new header is built, with no pixel data copy.

2. Reference Counting — Smart Memory Lifetime

cv::Mat embeds a shared_ptr-like reference-counting mechanism to prevent leaks and sync issues.

  • Assignment / copy constructor (Mat A = B; or Mat A(B);) — shallow copy, $O(1)$: copies only the header, shares the data pointer, increments refcount.
  • clone() / copyTo() — deep copy, $O(N)$: allocates new heap memory and memcpys the pixel data; fully independent lifecycle from that point.
cv::Mat A = cv::imread("image.png");
cv::Mat B = A;          // shallow copy: shared data, refcount = 2
cv::Mat C = A.clone();  // deep copy: new buffer, refcount = 1 (independent)
graph TD
    classDef original fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px;
    classDef shallow fill:#f3e5f5,stroke:#8e24aa,stroke-width:2px;
    classDef deep fill:#ffe6cc,stroke:#d79b00,stroke-width:2px;
    classDef shared fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;

    subgraph Stack ["Stack Area"]
        A["Mat A (original)"]:::original
        B["Mat B (shallow copy)"]:::shallow
        C["Mat C (deep copy)"]:::deep
    end
    subgraph Heap1 ["Heap Area A"]
        Ref1["UMatData refcount=2"]:::shared
        Data1["shared pixel buffer"]:::shared
    end
    subgraph Heap2 ["Heap Area B"]
        Ref2["UMatData refcount=1"]:::deep
        Data2["independent pixel buffer"]:::deep
    end
    A -- "header copy" --> B
    A -- "data pointer" --> Data1
    B -- "data pointer" --> Data1
    A -- "refcount address" --> Ref1
    B -- "refcount address" --> Ref1
    C -. "deep copy" .-> Heap2
    C -- "data pointer" --> Data2
    C -- "refcount address" --> Ref2
    Data1 -- "memcpy" --> Data2

When a Mat goes out of scope, its destructor decrements refcount; only when that count hits zero does MatAllocator actually deallocate the heap buffer.

4. Physical step Offset Formula

\[\text{addr}(M_{i,j}) = M.\text{data} + M.\text{step}[0] \cdot i + M.\text{step}[1] \cdot j\]
  • M.data — physical start address.
  • M.step[0] (row step) — bytes to skip to move down one row ($W \times \text{channels} \times \text{bytes/channel}$, plus alignment padding).
  • M.step[1] (column/element step) — bytes to skip to move one column over.

Directly manipulating step bytes is what lets discontinuous sub-matrices (ROIs) still get pure-pointer-arithmetic, zero-overhead access to any pixel.

Week 13 — Color Space Pointer Manipulation

1. BGR Interleaved Memory Layout

OpenCV stores color images as BGR, not RGB — a historical artifact of Windows DIB compatibility — and channels are interleaved, not stored as separate planes: B,G,R,B,G,R,... in one contiguous run.

graph TD
    classDef blueChannel fill:#e3f2fd,stroke:#1e88e5,stroke-width:2px;
    classDef greenChannel fill:#e8f5e9,stroke:#43a047,stroke-width:2px;
    classDef redChannel fill:#ffebee,stroke:#e53935,stroke-width:2px;
    classDef pointer fill:#fff3e0,stroke:#fb8c00,stroke-width:2px;

    subgraph PixelArray ["BGR Interleaved Memory Layout (1D Byte Array)"]
        B0["B(0)"]:::blueChannel
        G0["G(1)"]:::greenChannel
        R0["R(2)"]:::redChannel
        B1["B(3)"]:::blueChannel
        G1["G(4)"]:::greenChannel
        R1["R(5)"]:::redChannel
        B2["B(6)"]:::blueChannel
        G2["G(7)"]:::greenChannel
        R2["R(8)"]:::redChannel
        B0 --- G0 --- R0 --- B1 --- G1 --- R1 --- B2 --- G2 --- R2
    end
    subgraph Offsets ["Pixel Index & Byte Offsets"]
        Ptr0["Pixel 0: (y*W+0)*3"]:::pointer
        Ptr1["Pixel 1: (y*W+1)*3"]:::pointer
        Ptr2["Pixel 2: (y*W+2)*3"]:::pointer
    end
    Ptr0 -.-> B0
    Ptr1 -.-> B1
    Ptr2 -.-> B2
  • Grayscale offset (1-channel): $\text{Offset} = y \times W + x$
  • BGR offset (3-channel): $\text{Offset} = (y \times W + x) \times 3$ — lands on Blue; Green is +1, Red is +2.

2. Grayscale Intensity — Biological & Mathematical Basis

Naive averaging ($\frac{R+G+B}{3}$) doesn’t match human perceived brightness. Retinal cone cells are most sensitive to green, then red, then blue — weighted accordingly:

\[Y = 0.299R + 0.587G + 0.114B\]

This is the actual formula cv::cvtColor(src, dst, cv::COLOR_BGR2GRAY) runs internally, as an optimized fixed-point computation.

4. BGR Pointer Address Arithmetic

A 2D Mat is physically a flattened 1D buffer. The blue-channel address of $(y,x)$ — &src.data[y*step[0] + x*step[1] + 0] — can have its row-jump term (y*step[0]) hoisted out of the inner loop via ptr<uchar>(y), leaving only $O(1)$ pointer increments (3*x) inside the loop, maximizing memory bandwidth utilization.

Why this ended up mattering for optics

None of this is computational optics. But every DHM paper I’ve written since involves windowing sidebands in a Fourier-transformed matrix, and every ARMS-line paper involves per-block frequency-domain masks over overlapping image regions — every single one of them is, underneath the physics, a program that has to slice sub-regions out of a large 2D buffer correctly and fast. The SoC Design Lab semester is where “correctly and fast” stopped being something I trusted a library to handle for me and became something I could reason about at the memory-layout level.

It’s also, not coincidentally, the first stage of what our lab now half-jokingly calls the growth path: SoC Design Lab (systems mastery) → KAIST microdegree tracks (mathematical depth) → Master’s in the 3D Optical Imaging Systems Lab (DHM, ARMS) → PhD-track computational imaging. Each stage’s “why” only became obvious from the next one.

Affiliation: SoC Design Lab, Department of Electronics Engineering, Dong-A University, under the undergraduate research credit program (연구학점제).




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • Counting Photons to See Through Fog: Peplography and COLaNoPS
  • ARMS: What Wireless Fading Models Have to Do With Seeing Through Fog
  • Chasing the DC Spectrum: Three Papers on Denoising Digital Holographic Microscopy
  • Review of Fourier ptychographic microscopy using wavelength multiplexing
  • Ptychography