Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

NDVI Standalone

ndvi_standalone.livemd

NDVI Standalone

Mix.install(
  [
    {:nx, "~> 0.9"},
    {:evision, "~> 0.2"},
    {:exla, "~> 0.9"},
    {:kino, "~> 0.14"},
    {:flow, "~> 1.2"}
  ],
  config: [nx: [default_backend: EXLA.Backend]]
)

Define modules

defmodule BandInfo do
  defstruct gain: 0.0, offset: 0.0
end

defmodule HeaderInfo do
  defstruct red: %BandInfo{},
            nir: %BandInfo{},
            date: nil

  def get_string(info, start, value) do
    info
    |> String.slice(start, value)
    |> String.trim()
  end

  def get_value(info, start, value) do
    info
    |> get_string(start, value)
    |> String.to_float()
  end

  def read(hdr_file_path) do
    info = File.read!(hdr_file_path)

    %HeaderInfo{
      # 赤色光バンド
      red: %BandInfo{
        gain: get_value(info, 1752, 8),
        offset: get_value(info, 1760, 8)
      },
      # 近赤外線バンド
      nir: %BandInfo{
        gain: get_value(info, 1768, 8),
        offset: get_value(info, 1776, 8)
      },
      date: get_string(info, 192, 8)
    }
  end
end
frame = Kino.Frame.new()
defmodule NDVIClient do
  import Nx.Defn

  def read_header(file_path_list) do
    file_path_list
    |> Enum.find(fn file -> Path.extname(file) == ".txt" end)
    |> HeaderInfo.read()
  end

  def get_band_tensor(file_path_list, prefix) do
    file_path_list
    |> Enum.find(fn file ->
      file
      |> Path.basename()
      |> String.starts_with?(prefix)
    end)
    |> Evision.imread(flags: Evision.Constant.cv_IMREAD_GRAYSCALE())
    |> Evision.resize({640, 640})
    |> Evision.Mat.to_nx(EXLA.Backend)
  end

  defn get_luminance(tensor, gain, offset) do
    tensor * gain + offset
  end

  defn calc_ndvi(red_tensor, nir_tensor) do
    ndvi_tensor =
      Nx.select(
        # 0 除算をしないため、 NIR と Red の両方が 0 でないところだけ演算する
        (red_tensor != 0) * (nir_tensor != 0),
        # NDVI の演算
        (nir_tensor - red_tensor) / (nir_tensor + red_tensor),
        0
      )
      |> then(fn tensor -> tensor * 128 + 128 end)
      |> Nx.as_type(:u8)

    ndvi_tensor
  end

  def calc(file_path_list, frame) do
    header_info = read_header(file_path_list)

    red_tensor =
      file_path_list
      |> get_band_tensor("IMG-03")
      |> get_luminance(header_info.red.gain, header_info.red.offset)

    nir_tensor =
      file_path_list
      |> get_band_tensor("IMG-04")
      |> get_luminance(header_info.nir.gain, header_info.nir.offset)

    ndvi_img =
      calc_ndvi(red_tensor, nir_tensor)
      |> Evision.Mat.from_nx_2d()

    ndvi_img
    |> Evision.resize({320, 320})
    |> then(&[src: &1, colormap: Evision.Constant.cv_COLORMAP_WINTER()])
    |> Evision.applyColorMap()
    |> then(&Kino.Frame.render(frame, &1))

    {header_info.date, ndvi_img}
  end
end

Get NDVI

scene_id_list = [
  "202ce08d-ba4b-4ffe-8165-109fd3a8b917",
  "34d8dc6f-fdd1-4542-a038-c1235a5a97fa",
  "12ad308b-6ce1-40ec-9ebf-f0215c30500e",
  "e2e85b2e-a208-4a65-87fd-b92721b037a8",
  "208a3618-7700-421b-bf05-fd59551cc1aa",
  "d5ce7320-5b25-4ced-bda5-0e25a9d75940",
  "9d14706f-cee7-4eb4-9151-2558609c3de0",
  "3f4555ac-eaf3-4066-a1ba-20bb1ec1c0b3"
]
ndvi_list =
  scene_id_list
  |> Flow.from_enumerable(stages: 4, max_demand: 1)
  |> Flow.map(fn scene_id ->
    "/tmp/#{scene_id}"
    |> File.ls!()
    |> Enum.map(fn filename -> Path.join(["/tmp", scene_id, filename]) end)
    |> NDVIClient.calc(frame)
  end)
  |> Enum.to_list()
ndvi_list
|> Enum.map(fn {date, ndvi_tensor} ->
  img =
    Evision.applyColorMap(src: ndvi_tensor, colormap: Evision.Constant.cv_COLORMAP_WINTER())
    |> Evision.cvtColor(Evision.Constant.cv_COLOR_RGB2BGR())
    |> Evision.Mat.to_nx()
    |> Kino.Image.new()

  {date, img}
end)
|> Kino.Layout.tabs()