{
  "id": 532675,
  "title": "DICOM file viewer with Gradio",
  "url": "/competitions/rsna-2024-lumbar-spine-degenerative-classification/discussion/532675",
  "author_name": "",
  "post_date": "2024-09-07T13:46:16.077516400Z",
  "votes": 29,
  "comment_count": 7,
  "views": 0,
  "content": "<p>I created DICOM viewer with <a href=\"https://www.gradio.app/\" target=\"_blank\">Gradio</a>.</p>\n<p>Since it's my first app, so it has some weird behaviors (e.g. DataFrame row increase as scroll).</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4910466%2Fa8e09a8e7cad9aefa6a54738eaf575a6%2FScreenshot%202024-09-07%20at%2022.38.56.png?generation=1725716387457540&amp;alt=media\" alt=\"\"></p>\n<p><code>make_image_metadata.py</code></p>\n<pre><code> ():\n    df_meta_f = pl.read_csv(comp_data_path / )\n    image_path = Path(comp_data_path / )\n    part_1 = (image_path.glob())\n    id2desc = {\n        (item[], item[]): item[]\n         item  df_meta_f.to_dicts()\n    }\n    meta_obj = {\n        (p.stem): {\n            : p.as_posix(),\n            : [(sub.stem)  sub  (p.glob())],\n            : [\n                id2desc.get(((p.stem), (sub.stem)), )\n                 sub  (p.glob())\n            ],\n        }\n         p  part_1\n    }\n    image_meta = pd.DataFrame(meta_obj).transpose()\n    image_meta.index.name = \n    image_meta = image_meta.explode([, ])\n    image_meta = image_meta.reset_index()\n    image_meta = pl.from_pandas(image_meta)\n    image_meta = image_meta.with_columns(\n        pl.col()\n        .count()\n        .over(, )\n        .alias(),\n    )\n    image_meta.write_csv(output_path / )\n     image_meta, meta_obj\n\n\nimage_meta, meta_obj = make_image_metadata(, comp_data_path, output_path)\n</code></pre>\n<p><code>dcm_viewer.py</code></p>\n<pre><code> datetime  datetime\n pathlib  Path\n\n gradio  gr\n numpy  np\n polars  pl\n pydicom  dcm\n\nDATAFRAME_FILE_PATH = (\n    \n)\nROOT_PATH = Path(\n    \n)\n\n ():\n    data = []\n    params = [p  p  (ds)   p.startswith()  p[].isupper()]\n     p  params:\n        d = ds.get(p)\n         p:\n             :\n                d = datetime.strptime(d, ).strftime()\n             :\n                d = d[:]\n             _:\n                \n        data.append()\n     .join(data)\n\n\n ():\n    lower, upper = np.percentile(x, (, ))\n    x = np.clip(x, lower, upper)\n    x -= x.()\n    x /= x.()\n     (x * ).astype(np.uint8)\n\n\n ():\n     (\n        pl.read_csv(\n            DATAFRAME_FILE_PATH,\n            columns=[, , ],\n        )\n        .with_row_index()\n        .to_pandas()\n    )\n\n\nIMAGE_CACHE = []\nMETADATA_CACHE = []\n\n\n ():\n    IMAGE_CACHE.clear()\n    METADATA_CACHE.clear()\n\n     dcm_file  dcm_files:\n        path = ROOT_PATH / (study_id) / (instance_uid) / dcm_file\n        dc = dcm.dcmread(path)\n        dicom_metadata = get_metadata(dc)\n        dicom_image = normalize_image(dc.pixel_array)\n        IMAGE_CACHE.append(dicom_image)\n        METADATA_CACHE.append(dicom_metadata)\n\n\n ():\n    row = df.iloc[data.index[], :]\n    study_id = row[]\n    instance_uid = row[]\n    description = row[]\n    path = ROOT_PATH / (study_id) / (instance_uid)\n    paths = (path.glob())\n    num_files = (paths)\n    file_pool = [  n  (num_files)]\n    existing_dcm_files = [p.name  p  paths  p.name]\n     (f  existing_dcm_files  f  file_pool)\n    load_all_dicom_image(study_id, instance_uid, file_pool)\n\n     (\n        study_id,\n        instance_uid,\n        num_files,\n        description,\n    )\n\n\n ():\n     gr.Slider(, num_files - , step=, label=, interactive=)\n\n\n ():\n     METADATA_CACHE[idx], IMAGE_CACHE[idx]\n\n\n gr.Blocks()  demo:\n    metadata = load_metadata()\n\n    gr.Markdown()\n    gr.Markdown()\n    df_display = gr.DataFrame(metadata, height=, interactive=)\n     gr.Row():\n         gr.Column(scale=):\n            gr.Markdown()\n            dicom_image = gr.Image(=, label=, height=)\n\n         gr.Column(scale=):\n            gr.Markdown()\n            dicom_metadata = gr.Textbox(label=)\n\n         gr.Column(scale=):\n            gr.Markdown()\n            study_id = gr.Number(label=)\n            instance_uid = gr.Number(label=)\n            num_files = gr.Number(label=)\n            desc = gr.Textbox(label=)\n\n            df_display.select(\n                get_all_dcm_files_from_row,\n                inputs=[df_display],\n                outputs=[study_id, instance_uid, num_files, desc],\n            )\n            slider = gr.Slider(\n                minimum=,\n                maximum=num_files.value,\n                step=,\n                label=,\n                interactive=,\n            )\n            num_files.change(on_num_files_chaned, inputs=num_files, outputs=slider)\n            slider.change(\n                on_slider_changed, inputs=slider, outputs=[dicom_metadata, dicom_image]\n            )\n\ndemo.launch(height=)\n</code></pre>",
  "messages": [
    {
      "id": "2982129",
      "postDate": "09/07/2024 13:46:16",
      "content": "<p>I created DICOM viewer with <a href=\"https://www.gradio.app/\" target=\"_blank\">Gradio</a>.</p>\n<p>Since it's my first app, so it has some weird behaviors (e.g. DataFrame row increase as scroll).</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4910466%2Fa8e09a8e7cad9aefa6a54738eaf575a6%2FScreenshot%202024-09-07%20at%2022.38.56.png?generation=1725716387457540&amp;alt=media\" alt=\"\"></p>\n<p><code>make_image_metadata.py</code></p>\n<pre><code> ():\n    df_meta_f = pl.read_csv(comp_data_path / )\n    image_path = Path(comp_data_path / )\n    part_1 = (image_path.glob())\n    id2desc = {\n        (item[], item[]): item[]\n         item  df_meta_f.to_dicts()\n    }\n    meta_obj = {\n        (p.stem): {\n            : p.as_posix(),\n            : [(sub.stem)  sub  (p.glob())],\n            : [\n                id2desc.get(((p.stem), (sub.stem)), )\n                 sub  (p.glob())\n            ],\n        }\n         p  part_1\n    }\n    image_meta = pd.DataFrame(meta_obj).transpose()\n    image_meta.index.name = \n    image_meta = image_meta.explode([, ])\n    image_meta = image_meta.reset_index()\n    image_meta = pl.from_pandas(image_meta)\n    image_meta = image_meta.with_columns(\n        pl.col()\n        .count()\n        .over(, )\n        .alias(),\n    )\n    image_meta.write_csv(output_path / )\n     image_meta, meta_obj\n\n\nimage_meta, meta_obj = make_image_metadata(, comp_data_path, output_path)\n</code></pre>\n<p><code>dcm_viewer.py</code></p>\n<pre><code> datetime  datetime\n pathlib  Path\n\n gradio  gr\n numpy  np\n polars  pl\n pydicom  dcm\n\nDATAFRAME_FILE_PATH = (\n    \n)\nROOT_PATH = Path(\n    \n)\n\n ():\n    data = []\n    params = [p  p  (ds)   p.startswith()  p[].isupper()]\n     p  params:\n        d = ds.get(p)\n         p:\n             :\n                d = datetime.strptime(d, ).strftime()\n             :\n                d = d[:]\n             _:\n                \n        data.append()\n     .join(data)\n\n\n ():\n    lower, upper = np.percentile(x, (, ))\n    x = np.clip(x, lower, upper)\n    x -= x.()\n    x /= x.()\n     (x * ).astype(np.uint8)\n\n\n ():\n     (\n        pl.read_csv(\n            DATAFRAME_FILE_PATH,\n            columns=[, , ],\n        )\n        .with_row_index()\n        .to_pandas()\n    )\n\n\nIMAGE_CACHE = []\nMETADATA_CACHE = []\n\n\n ():\n    IMAGE_CACHE.clear()\n    METADATA_CACHE.clear()\n\n     dcm_file  dcm_files:\n        path = ROOT_PATH / (study_id) / (instance_uid) / dcm_file\n        dc = dcm.dcmread(path)\n        dicom_metadata = get_metadata(dc)\n        dicom_image = normalize_image(dc.pixel_array)\n        IMAGE_CACHE.append(dicom_image)\n        METADATA_CACHE.append(dicom_metadata)\n\n\n ():\n    row = df.iloc[data.index[], :]\n    study_id = row[]\n    instance_uid = row[]\n    description = row[]\n    path = ROOT_PATH / (study_id) / (instance_uid)\n    paths = (path.glob())\n    num_files = (paths)\n    file_pool = [  n  (num_files)]\n    existing_dcm_files = [p.name  p  paths  p.name]\n     (f  existing_dcm_files  f  file_pool)\n    load_all_dicom_image(study_id, instance_uid, file_pool)\n\n     (\n        study_id,\n        instance_uid,\n        num_files,\n        description,\n    )\n\n\n ():\n     gr.Slider(, num_files - , step=, label=, interactive=)\n\n\n ():\n     METADATA_CACHE[idx], IMAGE_CACHE[idx]\n\n\n gr.Blocks()  demo:\n    metadata = load_metadata()\n\n    gr.Markdown()\n    gr.Markdown()\n    df_display = gr.DataFrame(metadata, height=, interactive=)\n     gr.Row():\n         gr.Column(scale=):\n            gr.Markdown()\n            dicom_image = gr.Image(=, label=, height=)\n\n         gr.Column(scale=):\n            gr.Markdown()\n            dicom_metadata = gr.Textbox(label=)\n\n         gr.Column(scale=):\n            gr.Markdown()\n            study_id = gr.Number(label=)\n            instance_uid = gr.Number(label=)\n            num_files = gr.Number(label=)\n            desc = gr.Textbox(label=)\n\n            df_display.select(\n                get_all_dcm_files_from_row,\n                inputs=[df_display],\n                outputs=[study_id, instance_uid, num_files, desc],\n            )\n            slider = gr.Slider(\n                minimum=,\n                maximum=num_files.value,\n                step=,\n                label=,\n                interactive=,\n            )\n            num_files.change(on_num_files_chaned, inputs=num_files, outputs=slider)\n            slider.change(\n                on_slider_changed, inputs=slider, outputs=[dicom_metadata, dicom_image]\n            )\n\ndemo.launch(height=)\n</code></pre>",
      "rawMarkdown": "I created DICOM viewer with [Gradio](https://www.gradio.app/).\n\nSince it's my first app, so it has some weird behaviors (e.g. DataFrame row increase as scroll).\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4910466%2Fa8e09a8e7cad9aefa6a54738eaf575a6%2FScreenshot%202024-09-07%20at%2022.38.56.png?generation=1725716387457540&alt=media)\n\n`make_image_metadata.py`\n```python\ndef make_image_metadata(mode, comp_data_path: Path, output_path: Path):\n    df_meta_f = pl.read_csv(comp_data_path / f\"{mode}_series_descriptions.csv\")\n    image_path = Path(comp_data_path / f\"{mode}_images\")\n    part_1 = list(image_path.glob(\"*\"))\n    id2desc = {\n        (item[\"study_id\"], item[\"series_id\"]): item[\"series_description\"]\n        for item in df_meta_f.to_dicts()\n    }\n    meta_obj = {\n        int(p.stem): {\n            \"folder_path\": p.as_posix(),\n            \"SeriesInstanceUIDs\": [int(sub.stem) for sub in sorted(p.glob(\"*\"))],\n            \"SeriesDescriptions\": [\n                id2desc.get((int(p.stem), int(sub.stem)), \"N/A\")\n                for sub in sorted(p.glob(\"*\"))\n            ],\n        }\n        for p in part_1\n    }\n    image_meta = pd.DataFrame(meta_obj).transpose()\n    image_meta.index.name = \"study_id\"\n    image_meta = image_meta.explode([\"SeriesInstanceUIDs\", \"SeriesDescriptions\"])\n    image_meta = image_meta.reset_index()\n    image_meta = pl.from_pandas(image_meta)\n    image_meta = image_meta.with_columns(\n        pl.col(\"SeriesDescriptions\")\n        .count()\n        .over(\"study_id\", \"SeriesDescriptions\")\n        .alias(\"number_of_takes\"),\n    )\n    image_meta.write_csv(output_path / f\"{mode}_image_meta.csv\")\n    return image_meta, meta_obj\n\n\nimage_meta, meta_obj = make_image_metadata(\"train\", comp_data_path, output_path)\n```\n\n\n`dcm_viewer.py`\n```python\nfrom datetime import datetime\nfrom pathlib import Path\n\nimport gradio as gr\nimport numpy as np\nimport polars as pl\nimport pydicom as dcm\n\nDATAFRAME_FILE_PATH = (\n    \"/ml-docker/working/kaggle-rsna-2024/data/eda/train_image_meta.csv\"\n)\nROOT_PATH = Path(\n    \"/ml-docker/input/rsna-2024-lumbar-spine-degenerative-classification/train_images\"\n)\n\ndef get_metadata(ds):\n    data = []\n    params = [p for p in dir(ds) if not p.startswith(\"_\") and p[0].isupper()]\n    for p in params:\n        d = ds.get(p)\n        match p:\n            case \"ContentTime\":\n                d = datetime.strptime(d, \"%H%M%S.%f\").strftime(\"%H:%M:%S.%f\")\n            case \"PixelData\":\n                d = d[:3]\n            case _:\n                pass\n        data.append(f\"* {p}: {d}\")\n    return \"\\n\".join(data)\n\n\ndef normalize_image(x):\n    lower, upper = np.percentile(x, (1, 99))\n    x = np.clip(x, lower, upper)\n    x -= x.min()\n    x /= x.max()\n    return (x * 255).astype(np.uint8)\n\n\ndef load_metadata():\n    return (\n        pl.read_csv(\n            DATAFRAME_FILE_PATH,\n            columns=[\"study_id\", \"SeriesInstanceUIDs\", \"SeriesDescriptions\"],\n        )\n        .with_row_index(\"index\")\n        .to_pandas()\n    )\n\n\nIMAGE_CACHE = []\nMETADATA_CACHE = []\n\n\ndef load_all_dicom_image(study_id, instance_uid, dcm_files):\n    IMAGE_CACHE.clear()\n    METADATA_CACHE.clear()\n\n    for dcm_file in dcm_files:\n        path = ROOT_PATH / str(study_id) / str(instance_uid) / dcm_file\n        dc = dcm.dcmread(path)\n        dicom_metadata = get_metadata(dc)\n        dicom_image = normalize_image(dc.pixel_array)\n        IMAGE_CACHE.append(dicom_image)\n        METADATA_CACHE.append(dicom_metadata)\n\n\ndef get_all_dcm_files_from_row(df, data: gr.SelectData):\n    row = df.iloc[data.index[0], :]\n    study_id = row[\"study_id\"]\n    instance_uid = row[\"SeriesInstanceUIDs\"]\n    description = row[\"SeriesDescriptions\"]\n    path = ROOT_PATH / str(study_id) / str(instance_uid)\n    paths = list(path.glob(\"*.dcm\"))\n    num_files = len(paths)\n    file_pool = [f\"{n+1}.dcm\" for n in range(num_files)]\n    existing_dcm_files = [p.name for p in paths if p.name]\n    assert all(f in existing_dcm_files for f in file_pool)\n    load_all_dicom_image(study_id, instance_uid, file_pool)\n\n    return (\n        study_id,\n        instance_uid,\n        num_files,\n        description,\n    )\n\n\ndef on_num_files_chaned(num_files):\n    return gr.Slider(0, num_files - 1, step=1, label=\"Select Slice\", interactive=True)\n\n\ndef on_slider_changed(idx):\n    return METADATA_CACHE[idx], IMAGE_CACHE[idx]\n\n\nwith gr.Blocks() as demo:\n    metadata = load_metadata()\n\n    gr.Markdown(\"# DICOM file Viewer\")\n    gr.Markdown(\"## Select a row\")\n    df_display = gr.DataFrame(metadata, height=200, interactive=False)\n    with gr.Row():\n        with gr.Column(scale=3):\n            gr.Markdown(\"## DICOM Image\")\n            dicom_image = gr.Image(type=\"numpy\", label=\"DICOM Image\", height=600)\n\n        with gr.Column(scale=2):\n            gr.Markdown(\"## DICOM Metadata\")\n            dicom_metadata = gr.Textbox(label=\"DICOM Metadata\")\n\n        with gr.Column(scale=2):\n            gr.Markdown(\"## Select Slice\")\n            study_id = gr.Number(label=\"study_id\")\n            instance_uid = gr.Number(label=\"SeriesInstanceUID\")\n            num_files = gr.Number(label=\"#files\")\n            desc = gr.Textbox(label=\"SeriesDescription\")\n\n            df_display.select(\n                get_all_dcm_files_from_row,\n                inputs=[df_display],\n                outputs=[study_id, instance_uid, num_files, desc],\n            )\n            slider = gr.Slider(\n                minimum=0,\n                maximum=num_files.value,\n                step=1,\n                label=\"Select Slice\",\n                interactive=True,\n            )\n            num_files.change(on_num_files_chaned, inputs=num_files, outputs=slider)\n            slider.change(\n                on_slider_changed, inputs=slider, outputs=[dicom_metadata, dicom_image]\n            )\n\ndemo.launch(height=1200)\n\n```",
      "votes": null
    },
    {
      "id": "2982152",
      "postDate": "09/07/2024 14:09:02",
      "content": "<p>Wow! This looks convenient and certainly useful for conducting analyses. Thanks!</p>",
      "rawMarkdown": "Wow! This looks convenient and certainly useful for conducting analyses. Thanks!",
      "votes": null
    },
    {
      "id": "2982280",
      "postDate": "09/07/2024 16:44:55",
      "content": "<p>Looks great! You should definitely consider serving it on HuggingFace!</p>",
      "rawMarkdown": "Looks great! You should definitely consider serving it on HuggingFace!",
      "votes": null
    },
    {
      "id": "2982348",
      "postDate": "09/07/2024 17:53:36",
      "content": "<p>This tool helps a lot. This recommendation is very good.</p>",
      "rawMarkdown": "This tool helps a lot. This recommendation is very good.",
      "votes": null
    },
    {
      "id": "2982351",
      "postDate": "09/07/2024 17:55:01",
      "content": "<p>Did you find it too complicated to implement this tool?</p>",
      "rawMarkdown": "Did you find it too complicated to implement this tool?",
      "votes": null
    },
    {
      "id": "2982544",
      "postDate": "09/07/2024 23:54:53",
      "content": "<p>I once consider this option, but I decided to serve local because of slow latency.</p>",
      "rawMarkdown": "I once consider this option, but I decided to serve local because of slow latency.",
      "votes": null
    },
    {
      "id": "2982552",
      "postDate": "09/08/2024 00:14:14",
      "content": "<p>I think we can’t serve on HF because completion rule prohibit redistribution of dataset.</p>\n<blockquote>\n  <p>DATA ACCESS AND USE: Non-Commercial Use, for the purposes of the competition. Re-distribution or re-identification of any data is strictly prohibited.</p>\n</blockquote>",
      "rawMarkdown": "I think we can’t serve on HF because completion rule prohibit redistribution of dataset.\n\n> DATA ACCESS AND USE: Non-Commercial Use, for the purposes of the competition. Re-distribution or re-identification of any data is strictly prohibited.",
      "votes": null
    },
    {
      "id": "2983080",
      "postDate": "09/08/2024 13:41:11",
      "content": "<p>That’s right. But leaving the uploading the dicom file to the interface to the user might be a solution for that restriction. I’m not sure though.</p>",
      "rawMarkdown": "That’s right. But leaving the uploading the dicom file to the interface to the user might be a solution for that restriction. I’m not sure though.",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 2982152,
      "author_name": "samson8",
      "author_url": "",
      "post_date": "09/07/2024 14:09:02",
      "content": "<p>Wow! This looks convenient and certainly useful for conducting analyses. Thanks!</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2982280,
      "author_name": "nlztrk",
      "author_url": "",
      "post_date": "09/07/2024 16:44:55",
      "content": "<p>Looks great! You should definitely consider serving it on HuggingFace!</p>",
      "votes": null,
      "replies": [
        {
          "id": 2982544,
          "author_name": "tatamikenn",
          "author_url": "",
          "post_date": "09/07/2024 23:54:53",
          "content": "<p>I once consider this option, but I decided to serve local because of slow latency.</p>",
          "votes": null,
          "replies": [
            {
              "id": 2982552,
              "author_name": "tatamikenn",
              "author_url": "",
              "post_date": "09/08/2024 00:14:14",
              "content": "<p>I think we can’t serve on HF because completion rule prohibit redistribution of dataset.</p>\n<blockquote>\n  <p>DATA ACCESS AND USE: Non-Commercial Use, for the purposes of the competition. Re-distribution or re-identification of any data is strictly prohibited.</p>\n</blockquote>",
              "votes": null,
              "replies": [
                {
                  "id": 2983080,
                  "author_name": "nlztrk",
                  "author_url": "",
                  "post_date": "09/08/2024 13:41:11",
                  "content": "<p>That’s right. But leaving the uploading the dicom file to the interface to the user might be a solution for that restriction. I’m not sure though.</p>",
                  "votes": null,
                  "replies": []
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "id": 2982348,
      "author_name": "sc0v1n0",
      "author_url": "",
      "post_date": "09/07/2024 17:53:36",
      "content": "<p>This tool helps a lot. This recommendation is very good.</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2982351,
      "author_name": "sc0v1n0",
      "author_url": "",
      "post_date": "09/07/2024 17:55:01",
      "content": "<p>Did you find it too complicated to implement this tool?</p>",
      "votes": null,
      "replies": []
    }
  ],
  "raw_markdown_by_id": {
    "2982129": "I created DICOM viewer with [Gradio](https://www.gradio.app/).\n\nSince it's my first app, so it has some weird behaviors (e.g. DataFrame row increase as scroll).\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4910466%2Fa8e09a8e7cad9aefa6a54738eaf575a6%2FScreenshot%202024-09-07%20at%2022.38.56.png?generation=1725716387457540&alt=media)\n\n`make_image_metadata.py`\n```python\ndef make_image_metadata(mode, comp_data_path: Path, output_path: Path):\n    df_meta_f = pl.read_csv(comp_data_path / f\"{mode}_series_descriptions.csv\")\n    image_path = Path(comp_data_path / f\"{mode}_images\")\n    part_1 = list(image_path.glob(\"*\"))\n    id2desc = {\n        (item[\"study_id\"], item[\"series_id\"]): item[\"series_description\"]\n        for item in df_meta_f.to_dicts()\n    }\n    meta_obj = {\n        int(p.stem): {\n            \"folder_path\": p.as_posix(),\n            \"SeriesInstanceUIDs\": [int(sub.stem) for sub in sorted(p.glob(\"*\"))],\n            \"SeriesDescriptions\": [\n                id2desc.get((int(p.stem), int(sub.stem)), \"N/A\")\n                for sub in sorted(p.glob(\"*\"))\n            ],\n        }\n        for p in part_1\n    }\n    image_meta = pd.DataFrame(meta_obj).transpose()\n    image_meta.index.name = \"study_id\"\n    image_meta = image_meta.explode([\"SeriesInstanceUIDs\", \"SeriesDescriptions\"])\n    image_meta = image_meta.reset_index()\n    image_meta = pl.from_pandas(image_meta)\n    image_meta = image_meta.with_columns(\n        pl.col(\"SeriesDescriptions\")\n        .count()\n        .over(\"study_id\", \"SeriesDescriptions\")\n        .alias(\"number_of_takes\"),\n    )\n    image_meta.write_csv(output_path / f\"{mode}_image_meta.csv\")\n    return image_meta, meta_obj\n\n\nimage_meta, meta_obj = make_image_metadata(\"train\", comp_data_path, output_path)\n```\n\n\n`dcm_viewer.py`\n```python\nfrom datetime import datetime\nfrom pathlib import Path\n\nimport gradio as gr\nimport numpy as np\nimport polars as pl\nimport pydicom as dcm\n\nDATAFRAME_FILE_PATH = (\n    \"/ml-docker/working/kaggle-rsna-2024/data/eda/train_image_meta.csv\"\n)\nROOT_PATH = Path(\n    \"/ml-docker/input/rsna-2024-lumbar-spine-degenerative-classification/train_images\"\n)\n\ndef get_metadata(ds):\n    data = []\n    params = [p for p in dir(ds) if not p.startswith(\"_\") and p[0].isupper()]\n    for p in params:\n        d = ds.get(p)\n        match p:\n            case \"ContentTime\":\n                d = datetime.strptime(d, \"%H%M%S.%f\").strftime(\"%H:%M:%S.%f\")\n            case \"PixelData\":\n                d = d[:3]\n            case _:\n                pass\n        data.append(f\"* {p}: {d}\")\n    return \"\\n\".join(data)\n\n\ndef normalize_image(x):\n    lower, upper = np.percentile(x, (1, 99))\n    x = np.clip(x, lower, upper)\n    x -= x.min()\n    x /= x.max()\n    return (x * 255).astype(np.uint8)\n\n\ndef load_metadata():\n    return (\n        pl.read_csv(\n            DATAFRAME_FILE_PATH,\n            columns=[\"study_id\", \"SeriesInstanceUIDs\", \"SeriesDescriptions\"],\n        )\n        .with_row_index(\"index\")\n        .to_pandas()\n    )\n\n\nIMAGE_CACHE = []\nMETADATA_CACHE = []\n\n\ndef load_all_dicom_image(study_id, instance_uid, dcm_files):\n    IMAGE_CACHE.clear()\n    METADATA_CACHE.clear()\n\n    for dcm_file in dcm_files:\n        path = ROOT_PATH / str(study_id) / str(instance_uid) / dcm_file\n        dc = dcm.dcmread(path)\n        dicom_metadata = get_metadata(dc)\n        dicom_image = normalize_image(dc.pixel_array)\n        IMAGE_CACHE.append(dicom_image)\n        METADATA_CACHE.append(dicom_metadata)\n\n\ndef get_all_dcm_files_from_row(df, data: gr.SelectData):\n    row = df.iloc[data.index[0], :]\n    study_id = row[\"study_id\"]\n    instance_uid = row[\"SeriesInstanceUIDs\"]\n    description = row[\"SeriesDescriptions\"]\n    path = ROOT_PATH / str(study_id) / str(instance_uid)\n    paths = list(path.glob(\"*.dcm\"))\n    num_files = len(paths)\n    file_pool = [f\"{n+1}.dcm\" for n in range(num_files)]\n    existing_dcm_files = [p.name for p in paths if p.name]\n    assert all(f in existing_dcm_files for f in file_pool)\n    load_all_dicom_image(study_id, instance_uid, file_pool)\n\n    return (\n        study_id,\n        instance_uid,\n        num_files,\n        description,\n    )\n\n\ndef on_num_files_chaned(num_files):\n    return gr.Slider(0, num_files - 1, step=1, label=\"Select Slice\", interactive=True)\n\n\ndef on_slider_changed(idx):\n    return METADATA_CACHE[idx], IMAGE_CACHE[idx]\n\n\nwith gr.Blocks() as demo:\n    metadata = load_metadata()\n\n    gr.Markdown(\"# DICOM file Viewer\")\n    gr.Markdown(\"## Select a row\")\n    df_display = gr.DataFrame(metadata, height=200, interactive=False)\n    with gr.Row():\n        with gr.Column(scale=3):\n            gr.Markdown(\"## DICOM Image\")\n            dicom_image = gr.Image(type=\"numpy\", label=\"DICOM Image\", height=600)\n\n        with gr.Column(scale=2):\n            gr.Markdown(\"## DICOM Metadata\")\n            dicom_metadata = gr.Textbox(label=\"DICOM Metadata\")\n\n        with gr.Column(scale=2):\n            gr.Markdown(\"## Select Slice\")\n            study_id = gr.Number(label=\"study_id\")\n            instance_uid = gr.Number(label=\"SeriesInstanceUID\")\n            num_files = gr.Number(label=\"#files\")\n            desc = gr.Textbox(label=\"SeriesDescription\")\n\n            df_display.select(\n                get_all_dcm_files_from_row,\n                inputs=[df_display],\n                outputs=[study_id, instance_uid, num_files, desc],\n            )\n            slider = gr.Slider(\n                minimum=0,\n                maximum=num_files.value,\n                step=1,\n                label=\"Select Slice\",\n                interactive=True,\n            )\n            num_files.change(on_num_files_chaned, inputs=num_files, outputs=slider)\n            slider.change(\n                on_slider_changed, inputs=slider, outputs=[dicom_metadata, dicom_image]\n            )\n\ndemo.launch(height=1200)\n\n```",
    "2982152": "Wow! This looks convenient and certainly useful for conducting analyses. Thanks!",
    "2982280": "Looks great! You should definitely consider serving it on HuggingFace!",
    "2982348": "This tool helps a lot. This recommendation is very good.",
    "2982351": "Did you find it too complicated to implement this tool?",
    "2982544": "I once consider this option, but I decided to serve local because of slow latency.",
    "2982552": "I think we can’t serve on HF because completion rule prohibit redistribution of dataset.\n\n> DATA ACCESS AND USE: Non-Commercial Use, for the purposes of the competition. Re-distribution or re-identification of any data is strictly prohibited.",
    "2983080": "That’s right. But leaving the uploading the dicom file to the interface to the user might be a solution for that restriction. I’m not sure though."
  },
  "source": "meta"
}