@page "/gabor"
@rendermode InteractiveServer
@inject IJSRuntime JSRuntime
@using OpenCvSharp


<PageTitle>Gabor Filter Test</PageTitle>

<div>
    <h1 id="-gabor-filter-"><strong>Gabor Filter란?</strong></h1><br />
    <p>Gabor Filter는 영상처리에서 Bio-inspired라는 키워드가 있으면 빠지지않고 등장한다.</p>
    <p>외곽선을 검출하는 기능을 하는 필터로, 사람의 시각체계가 반응하는 것과 비슷하다는 이유로 널리 사용되고 있다.</p>
    <p>Gabor Fiter는 간단히 말해서 사인 함수로 모듈레이션 된 Gaussian Filter라고 생각할 수 있다.</p>
    <p>파라미터를 조절함에 따라 Edge의 크기나 방향성을 바꿀 수 있으므로 Bio-inspired 영상처리 알고리즘에서 특징점 추출 알고리즘으로 핵심적인 역할을 하고 있다.</p>
    <p>2D Gabor Filter의 수식은 아래와 같다.</p>
    <p>
        <img src="images/gaborfilter.png" alt="gaborfilter"><br />
        <img src="images/gaborfilter2.png" alt="gaborfilter"><br />
        <img src="images/gaborfilter3.png" alt="gaborfilter"><br />
    </p><br /><br />
    <h3 id="-parameters-"><strong>함수 원형</strong></h3>
    <pre style="background-color: #f8f8f8;">
    <code class="lang-cpp">
  cv::Mat cv::getGaborKernel(cv::Size ksize, <span class="hljs-keyword">double</span> sigma, <span class="hljs-keyword">double</span> theta, <span class="hljs-keyword">double</span> lambd, <span class="hljs-keyword">double</span> gamma, <span class="hljs-keyword">double</span> psi = CV_PI*<span class="hljs-number">0.5</span>, <span class="hljs-keyword">int</span> ktype = CV_64F)  
        </code>
    </pre><br />
    <p>cv::getGaborKernel 함수는 OpenCV에서 가버필터(Gabor filter)를 생성하는 데 사용된다.</p>
    <p>가버필터는 이미지 처리와 컴퓨터 비전에서 특정 방향성과 주파수의 특징을 강조하는 데 사용되는 선형 필터이다. </p><br /><br />
    <h3 id="-parameters-"><strong>Parameters</strong></h3><br />
    <ul>
        <li>
            <p>ksize: 커널의 크기로, cv::Size 타입. 커널의 너비와 높이를 지정.</p>
        </li>
        <li>
            <p>sigma: 가우시안 함수의 표준 편차. 값이 커질수록 커널의 크기가 커지고, 필터의 감도가 낮아진다.</p>
        </li>
        <li>
            <p>theta: 필터의 방향을 라디안 단위로 지정한다. 0은 수평 방향, CV_PI/2는 수직 방향을 의미한다.</p>
        </li>
        <li>
            <p>lambd: 가버필터의 파장. 이미지의 특정 패턴과 얼마나 잘 매치할지를 결정한다.</p>
        </li>
        <li>
            <p>gamma: 공간종횡비(Spatial aspect ratio)로, 타원형 가버 함수의 타원성을 결정한다. (gamma=1은 원형, gamma&lt;1은 타원형)</p>
        </li>
        <li>
            <p>psi: 위상변위(Phase offset)로, 가버 필터의 위상을 조절한다. (일반적으로 CV_PI*0.5가 기본값으로 사용)</p>
        </li>
        <li>
            <p>ktype: 커널 타입. (보통 CV_64F (64-비트 부동소수점)를 사용)</p><br />
        </li>
    </ul>
</div>
<br />
<div>
    <h3 id="-parameters-"><strong>Gabor Filter 실행 (속도 느림 주의)</strong></h3>
    @if (srcGabor != null)
    {
        <img src="data:image/bmp;base64,@Convert.ToBase64String(srcGabor.ToBytes())" />
    }
    @if (rstGabor != null)
    {
        <img src="data:image/bmp;base64,@Convert.ToBase64String(rstGabor.ToBytes())" />
    }
</div>
<div>
    <p>
        Kernel Size(0 이상 홀수)
        <input type="range" min="3" max="127" step="2" @bind="@valKernelSize" @bind:event="oninput" />
        <input type="text" @bind="@valKernelSize" />
    </p><br />
    <p>
        Sigma (0.0 이상)
        <input type="range" min="0" max="1000" step="0.01" @bind="@valSigma" @bind:event="oninput" />
        <input type="text" @bind="@valSigma" />
    </p><br />
    <p>
        Theta (0도 ~ 180도)
        <input type="range" min="0" max="180" step="0.01" @bind="@valTheta" @bind:event="oninput" />
        <input type="text" @bind="@valTheta" />
    </p><br />
    <p>
        Lambda (0.0 이상 이미지 사이즈 미만(256.0))
        <input type="range" min="0" max="255" step="0.01" @bind="@valLambd" @bind:event="oninput" />
        <input type="text" @bind="@valLambd" />
    </p><br />
    <p>
        Gamma (0.0 ~ 1.0)
        <input type="range" min="0.0" max="1.0" step="0.01" @bind="@valGamma" @bind:event="oninput" />
        <input type="text" @bind="@valGamma" />
    </p><br />
    <p>
        Psi (0도 ~ 360도)
        <input type="range" min="0.0" max="360" step="0.01" @bind="@valPsi" @bind:event="oninput" />
        <input type="text" @bind="@valPsi" />
    </p><br />
    <button class="btn btn-primary" @onclick="GaborFilter2D">GaborFilter</button>
</div>
<br />
<div>
    @if (srcBinary != null)
    {
        <img src="data:image/bmp;base64,@Convert.ToBase64String(srcBinary.ToBytes())" />
    }
    @if (rstBinary != null)
    {
        <img src="data:image/bmp;base64,@Convert.ToBase64String(rstBinary.ToBytes())" />
    }
</div>
<div>
    <div>
        @foreach (var option in options)
        {
            <label style="margin-right: 10px;">
                <input type="radio" id="@option.Value" name="options" @onchange="() => SelectImage(option.Text)" value="@option.Value" />
                @option.Text
            </label>
        }
    </div>
    <p>Threshold (0 ~ 255)</p>
    <input type="range" min="0" max="255" step="1" @bind="@valthreshold" @bind:event="oninput" />
    <input type="text" @bind="@valthreshold" /><br />
    <button class="btn btn-primary" @onclick="Grayscale">Grayscale</button>
    <button class="btn btn-primary" @onclick="PseudoColor">PseudoColor</button>
    <button class="btn btn-primary" @onclick="Threshold">Threshold</button>
</div>


@code {
    private ElementReference canvas;

    private Mat? srcGabor, rstGabor;
    private Mat? srcBinary, rstBinary;
    int width = 256;
    int height = 256;
    private int valKernelSize = 3;
    private double valSigma = 0.0;
    private double valTheta = 0.0;
    private double valLambd = 0.0;
    private double valGamma = 0.0;
    private double valPsi = 0.0;
    public class Option
    {
        public string Value { get; set; }
        public string Text { get; set; }
    }
    private List<Option> options = new List<Option>
    {
        new Option { Value = "0", Text = "Lenna" },
        new Option { Value = "1", Text = "Mandrill" },
    };
    private int valthreshold = 127;

    protected override void OnInitialized()
    {
        string imagePath;
        imagePath = $"wwwroot/images/gabor.bmp";
        srcGabor = Cv2.ImRead(imagePath);
        Cv2.Resize(srcGabor, srcGabor, new Size(width, height));

        imagePath = $"wwwroot/images/lenna.bmp";
        srcBinary = Cv2.ImRead(imagePath);
        Cv2.Resize(srcBinary, srcBinary, new Size(width, height));
        // JSRuntime.InvokeVoidAsync("renderImageOnCanvas", canvas, matBinary.ToBytes());
    }

    private void GaborFilter2D(MouseEventArgs e)
    {
        if (srcGabor is null)
            throw new InvalidOperationException($"{nameof(srcGabor)} is null");

        using var grayMat = new Mat(srcGabor.Size(), MatType.CV_32F);
        Cv2.CvtColor(srcGabor, grayMat, ColorConversionCodes.BGR2GRAY);

        int kernel_size = valKernelSize;
        double sigma = valSigma;
        double theta = valTheta * Cv2.PI / 180.0;
        double lambd = valLambd;
        double gamma = valGamma;
        double psi = valPsi * Cv2.PI / 180.0;

        try
        {
            Mat gabor_filter = Cv2.GetGaborKernel(new Size(kernel_size, kernel_size), sigma, theta, lambd, gamma, psi, MatType.CV_32F);
            Mat kenel = new Mat(5, 5, MatType.CV_8UC1);
            rstGabor = new Mat();
            Cv2.Filter2D(grayMat, rstGabor, grayMat.Type(), kenel);
        }
        catch (Exception ex)
        {
            string log = ex.Message;
        }
    }

    private void Grayscale(MouseEventArgs e)
    {
        if (srcBinary is null)
            throw new InvalidOperationException($"{nameof(srcBinary)} is null");

        rstBinary = new Mat(srcBinary.Size(), MatType.CV_8UC1);
        Cv2.CvtColor(srcBinary, rstBinary, ColorConversionCodes.BGR2GRAY);
    }

    private void PseudoColor(MouseEventArgs e)
    {
        if (srcBinary is null)
            throw new InvalidOperationException($"{nameof(srcBinary)} is null");

        using var grayMat = new Mat();
        rstBinary = new Mat();
        Cv2.CvtColor(srcBinary, grayMat, ColorConversionCodes.BGR2GRAY);
        Cv2.ApplyColorMap(grayMat, rstBinary, ColormapTypes.Jet);
    }

    private void Threshold(MouseEventArgs e)
    {
        if (srcBinary is null)
            throw new InvalidOperationException($"{nameof(srcBinary)} is null");

        using var grayMat = new Mat();
        rstBinary = new Mat(srcBinary.Size(), MatType.CV_8UC1);
        Cv2.CvtColor(srcBinary, grayMat, ColorConversionCodes.BGR2GRAY);
        Cv2.Threshold(grayMat, rstBinary, valthreshold, 255, ThresholdTypes.Binary);
    }

    private void SelectImage(string name)
    {
        string imagePath = $"wwwroot/images/{name}.bmp";
        srcBinary = Cv2.ImRead(imagePath);
        Cv2.Resize(srcBinary, srcBinary, new Size(width, height));
    }
}