{
  "id": 507572,
  "title": "Dicom Viewer",
  "url": "/competitions/rsna-2024-lumbar-spine-degenerative-classification/discussion/507572",
  "author_name": "Noam",
  "post_date": "2024-05-26T12:11:12.519000",
  "votes": 1,
  "comment_count": 0,
  "views": 0,
  "content": "<p>I've created a dicom viewer for this mission:</p>\n<ol>\n<li>Change the first two lines of the code to the location where your CSV is located, and then run it on your local PC.</li>\n<li>Press the 'Load DICOM folder' button.</li>\n<li>Choose the relevant DICOM folder.</li>\n<li>Now you can see all the DICOM files, and you can move between the images using the keyboard keys or the buttons on the GUI.</li>\n<li>Hovering above the red circles will give you the condition and level data.\"</li>\n</ol>\n<p>inspired by <a href=\"https://www.kaggle.com/code/hasanbasbunar/dicom-image-annotation-visualization\" target=\"_blank\">https://www.kaggle.com/code/hasanbasbunar/dicom-image-annotation-visualization</a></p>\n<p>thanks to chatgpt :)</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1140496%2Fa361400dcc5d060a98c5dbe77b738460%2Fdownload.png?generation=1716725458004160&amp;alt=media\"></p>\n<pre><code>csvFileLocation = \ndicomFolderLocation = \n tkinter  tk\n tkinter  filedialog, messagebox\n tkinter.ttk  Combobox\n PIL  Image, ImageTk\n pydicom\n os\n numpy  np\n pylibjpeg\n matplotlib.pyplot  plt\n matplotlib.backends.backend_tkagg  FigureCanvasTkAgg\n re\n pandas  pd\n matplotlib.patches  Circle\n\n\n :\n     ():\n        self.root = root\n        self.root.title()\n\n        self.dicom_folders = []\n        self.selected_folder = tk.StringVar()\n        self.selected_subfolder = tk.StringVar()\n        self.subfolders = []\n        self.dicom_files = []\n        self.current_index = \n        self.relevant_rows = pd.DataFrame()\n        self.circles = []\n\n        self.folder_select_label = tk.Label(self.root, text=)\n        self.folder_select_label.pack()\n\n        self.folder_combobox = Combobox(self.root, textvariable=self.selected_folder, state=)\n        self.folder_combobox.pack()\n        self.folder_combobox.bind(, self.load_dicom_folder)\n\n        self.subfolder_select_label = tk.Label(self.root, text=)\n        self.subfolder_select_label.pack()\n\n        self.subfolder_combobox = Combobox(self.root, textvariable=self.selected_subfolder, state=)\n        self.subfolder_combobox.pack()\n        self.subfolder_combobox.bind(, self.load_subfolder)\n\n        self.canvas_frame = tk.Frame(self.root)\n        self.canvas_frame.pack(fill=tk.BOTH, expand=)\n\n        self.prev_button = tk.Button(self.root, text=, command=self.show_previous_image)\n        self.prev_button.pack(side=tk.LEFT, padx=)\n\n        self.next_button = tk.Button(self.root, text=, command=self.show_next_image)\n        self.next_button.pack(side=tk.RIGHT, padx=)\n\n        self.root.bind(, self.key_previous_image)\n        self.root.bind(, self.key_next_image)\n\n        self.figure, self.ax = plt.subplots()\n        self.canvas = FigureCanvasTkAgg(self.figure, master=self.canvas_frame)\n        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=)\n\n        self.canvas.mpl_connect(, self.mouse_move)\n\n        \n        self.coord_label = tk.Label(self.root, text=)\n        self.coord_label.pack()\n\n        \n        self.csv_file = csvFileLocation\n        self.df = pd.read_csv(self.csv_file)\n\n        \n        self.populate_dicom_folders()\n\n     ():\n        root_folder = dicomFolderLocation\n        self.dicom_folders = ([f  f  os.listdir(root_folder)  os.path.isdir(os.path.join(root_folder, f))],\n                                    key= x: (x))\n        self.folder_combobox[] = self.dicom_folders\n\n     ():\n        selected_folder = self.selected_folder.get()\n          selected_folder:\n            messagebox.showwarning(, )\n            \n\n        folder_path = os.path.join(dicomFolderLocation,\n                                   selected_folder)\n        subfolders = [f  f  os.listdir(folder_path)  os.path.isdir(os.path.join(folder_path, f))]\n          subfolders:\n            messagebox.showwarning(, )\n            \n\n        self.subfolders = subfolders\n        self.subfolder_combobox[] = self.subfolders\n\n     ():\n        selected_subfolder = self.selected_subfolder.get()\n          selected_subfolder:\n            messagebox.showwarning(, )\n            \n\n        folder_path = os.path.join(dicomFolderLocation,\n                                   self.selected_folder.get(), selected_subfolder)\n        self.dicom_files = [os.path.join(folder_path, f)  f  os.listdir(folder_path)  f.lower().endswith()]\n          self.dicom_files:\n            messagebox.showwarning(, )\n            \n\n        \n        folder_parts = os.path.normpath(folder_path).split(os.sep)\n        :\n            study_id = (folder_parts[-])\n            series_id = (folder_parts[-])\n         ValueError:\n            messagebox.showerror(,\n                                 )\n            \n\n        \n        self.relevant_rows = self.df[(self.df[] == study_id) &amp; (self.df[] == series_id)]\n         self.relevant_rows.empty:\n            ()\n        :\n            ()\n\n        \n        self.dicom_files.sort(key=self.numerical_sort_key)\n\n        self.current_index = \n        self.show_image()\n\n     ():\n        \n        base_name = os.path.basename(filename)\n        numbers = re.findall(, base_name)\n         ((number)  number  numbers)  numbers  (base_name,)\n\n     ():\n          self.dicom_files:\n            \n\n        dicom_file = self.dicom_files[self.current_index]\n        :\n            ds = pydicom.dcmread(dicom_file)\n            pixel_array = ds.pixel_array\n\n            self.ax.clear()\n            self.circles.clear()\n            self.ax.imshow(pixel_array, cmap=)\n\n            \n            instance_number = (re.findall(, os.path.basename(dicom_file))[])\n\n            \n             _, row  self.relevant_rows.iterrows():\n                 row[] == instance_number:\n                    circle = Circle((row[], row[]), radius=, color=, fill=, picker=)\n                    self.ax.add_patch(circle)\n                    self.circles.append((circle, row[], row[]))\n\n            \n            self.ax.set_title(os.path.basename(dicom_file))\n            self.ax.axis()  \n\n            self.canvas.draw()\n\n            self.root.title()\n         Exception  e:\n            messagebox.showerror(, )\n            ()\n\n     ():\n         self.current_index &gt; :\n            self.current_index -= \n            self.show_image()\n\n     ():\n         self.current_index &lt; (self.dicom_files) - :\n            self.current_index += \n            self.show_image()\n\n     ():\n        self.show_previous_image()\n\n     ():\n        self.show_next_image()\n\n     ():\n         self.dicom_files:\n             event.inaxes:\n                x, y = event.xdata, event.ydata\n                self.coord_label.config(text=)\n\n                 circle, condition, level  self.circles:\n                    contains, _ = circle.contains(event)\n                     contains:\n                        self.ax.set_title()\n                        self.canvas.draw_idle()\n                        \n\n                \n                dicom_file = self.dicom_files[self.current_index]\n                self.ax.set_title(os.path.basename(dicom_file))\n                self.canvas.draw_idle()\n\n __name__ == :\n    root = tk.Tk()\n    viewer = DICOMViewer(root)\n    root.mainloop()\n</code></pre>",
  "messages": [
    {
      "id": 2837288,
      "postDate": "2024-05-26T12:11:12.520Z",
      "content": "<p>I've created a dicom viewer for this mission:</p>\n<ol>\n<li>Change the first two lines of the code to the location where your CSV is located, and then run it on your local PC.</li>\n<li>Press the 'Load DICOM folder' button.</li>\n<li>Choose the relevant DICOM folder.</li>\n<li>Now you can see all the DICOM files, and you can move between the images using the keyboard keys or the buttons on the GUI.</li>\n<li>Hovering above the red circles will give you the condition and level data.\"</li>\n</ol>\n<p>inspired by <a href=\"https://www.kaggle.com/code/hasanbasbunar/dicom-image-annotation-visualization\" target=\"_blank\">https://www.kaggle.com/code/hasanbasbunar/dicom-image-annotation-visualization</a></p>\n<p>thanks to chatgpt :)</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1140496%2Fa361400dcc5d060a98c5dbe77b738460%2Fdownload.png?generation=1716725458004160&amp;alt=media\"></p>\n<pre><code>csvFileLocation = \ndicomFolderLocation = \n tkinter  tk\n tkinter  filedialog, messagebox\n tkinter.ttk  Combobox\n PIL  Image, ImageTk\n pydicom\n os\n numpy  np\n pylibjpeg\n matplotlib.pyplot  plt\n matplotlib.backends.backend_tkagg  FigureCanvasTkAgg\n re\n pandas  pd\n matplotlib.patches  Circle\n\n\n :\n     ():\n        self.root = root\n        self.root.title()\n\n        self.dicom_folders = []\n        self.selected_folder = tk.StringVar()\n        self.selected_subfolder = tk.StringVar()\n        self.subfolders = []\n        self.dicom_files = []\n        self.current_index = \n        self.relevant_rows = pd.DataFrame()\n        self.circles = []\n\n        self.folder_select_label = tk.Label(self.root, text=)\n        self.folder_select_label.pack()\n\n        self.folder_combobox = Combobox(self.root, textvariable=self.selected_folder, state=)\n        self.folder_combobox.pack()\n        self.folder_combobox.bind(, self.load_dicom_folder)\n\n        self.subfolder_select_label = tk.Label(self.root, text=)\n        self.subfolder_select_label.pack()\n\n        self.subfolder_combobox = Combobox(self.root, textvariable=self.selected_subfolder, state=)\n        self.subfolder_combobox.pack()\n        self.subfolder_combobox.bind(, self.load_subfolder)\n\n        self.canvas_frame = tk.Frame(self.root)\n        self.canvas_frame.pack(fill=tk.BOTH, expand=)\n\n        self.prev_button = tk.Button(self.root, text=, command=self.show_previous_image)\n        self.prev_button.pack(side=tk.LEFT, padx=)\n\n        self.next_button = tk.Button(self.root, text=, command=self.show_next_image)\n        self.next_button.pack(side=tk.RIGHT, padx=)\n\n        self.root.bind(, self.key_previous_image)\n        self.root.bind(, self.key_next_image)\n\n        self.figure, self.ax = plt.subplots()\n        self.canvas = FigureCanvasTkAgg(self.figure, master=self.canvas_frame)\n        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=)\n\n        self.canvas.mpl_connect(, self.mouse_move)\n\n        \n        self.coord_label = tk.Label(self.root, text=)\n        self.coord_label.pack()\n\n        \n        self.csv_file = csvFileLocation\n        self.df = pd.read_csv(self.csv_file)\n\n        \n        self.populate_dicom_folders()\n\n     ():\n        root_folder = dicomFolderLocation\n        self.dicom_folders = ([f  f  os.listdir(root_folder)  os.path.isdir(os.path.join(root_folder, f))],\n                                    key= x: (x))\n        self.folder_combobox[] = self.dicom_folders\n\n     ():\n        selected_folder = self.selected_folder.get()\n          selected_folder:\n            messagebox.showwarning(, )\n            \n\n        folder_path = os.path.join(dicomFolderLocation,\n                                   selected_folder)\n        subfolders = [f  f  os.listdir(folder_path)  os.path.isdir(os.path.join(folder_path, f))]\n          subfolders:\n            messagebox.showwarning(, )\n            \n\n        self.subfolders = subfolders\n        self.subfolder_combobox[] = self.subfolders\n\n     ():\n        selected_subfolder = self.selected_subfolder.get()\n          selected_subfolder:\n            messagebox.showwarning(, )\n            \n\n        folder_path = os.path.join(dicomFolderLocation,\n                                   self.selected_folder.get(), selected_subfolder)\n        self.dicom_files = [os.path.join(folder_path, f)  f  os.listdir(folder_path)  f.lower().endswith()]\n          self.dicom_files:\n            messagebox.showwarning(, )\n            \n\n        \n        folder_parts = os.path.normpath(folder_path).split(os.sep)\n        :\n            study_id = (folder_parts[-])\n            series_id = (folder_parts[-])\n         ValueError:\n            messagebox.showerror(,\n                                 )\n            \n\n        \n        self.relevant_rows = self.df[(self.df[] == study_id) &amp; (self.df[] == series_id)]\n         self.relevant_rows.empty:\n            ()\n        :\n            ()\n\n        \n        self.dicom_files.sort(key=self.numerical_sort_key)\n\n        self.current_index = \n        self.show_image()\n\n     ():\n        \n        base_name = os.path.basename(filename)\n        numbers = re.findall(, base_name)\n         ((number)  number  numbers)  numbers  (base_name,)\n\n     ():\n          self.dicom_files:\n            \n\n        dicom_file = self.dicom_files[self.current_index]\n        :\n            ds = pydicom.dcmread(dicom_file)\n            pixel_array = ds.pixel_array\n\n            self.ax.clear()\n            self.circles.clear()\n            self.ax.imshow(pixel_array, cmap=)\n\n            \n            instance_number = (re.findall(, os.path.basename(dicom_file))[])\n\n            \n             _, row  self.relevant_rows.iterrows():\n                 row[] == instance_number:\n                    circle = Circle((row[], row[]), radius=, color=, fill=, picker=)\n                    self.ax.add_patch(circle)\n                    self.circles.append((circle, row[], row[]))\n\n            \n            self.ax.set_title(os.path.basename(dicom_file))\n            self.ax.axis()  \n\n            self.canvas.draw()\n\n            self.root.title()\n         Exception  e:\n            messagebox.showerror(, )\n            ()\n\n     ():\n         self.current_index &gt; :\n            self.current_index -= \n            self.show_image()\n\n     ():\n         self.current_index &lt; (self.dicom_files) - :\n            self.current_index += \n            self.show_image()\n\n     ():\n        self.show_previous_image()\n\n     ():\n        self.show_next_image()\n\n     ():\n         self.dicom_files:\n             event.inaxes:\n                x, y = event.xdata, event.ydata\n                self.coord_label.config(text=)\n\n                 circle, condition, level  self.circles:\n                    contains, _ = circle.contains(event)\n                     contains:\n                        self.ax.set_title()\n                        self.canvas.draw_idle()\n                        \n\n                \n                dicom_file = self.dicom_files[self.current_index]\n                self.ax.set_title(os.path.basename(dicom_file))\n                self.canvas.draw_idle()\n\n __name__ == :\n    root = tk.Tk()\n    viewer = DICOMViewer(root)\n    root.mainloop()\n</code></pre>",
      "rawMarkdown": "I've created a dicom viewer for this mission:\n1. Change the first two lines of the code to the location where your CSV is located, and then run it on your local PC.\n2. Press the 'Load DICOM folder' button.\n3. Choose the relevant DICOM folder.\n4. Now you can see all the DICOM files, and you can move between the images using the keyboard keys or the buttons on the GUI.\n5. Hovering above the red circles will give you the condition and level data.\"\n\ninspired by https://www.kaggle.com/code/hasanbasbunar/dicom-image-annotation-visualization\n\nthanks to chatgpt :)\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1140496%2Fa361400dcc5d060a98c5dbe77b738460%2Fdownload.png?generation=1716725458004160&alt=media)\n\n\n```python\ncsvFileLocation = r\"D:\\rsna-2024-lumbar-spine-degenerative-classification\\train_label_coordinates.csv\"\ndicomFolderLocation = r\"D:\\rsna-2024-lumbar-spine-degenerative-classification\\train_images\"\nimport tkinter as tk\nfrom tkinter import filedialog, messagebox\nfrom tkinter.ttk import Combobox\nfrom PIL import Image, ImageTk\nimport pydicom\nimport os\nimport numpy as np\nimport pylibjpeg\nimport matplotlib.pyplot as plt\nfrom matplotlib.backends.backend_tkagg import FigureCanvasTkAgg\nimport re\nimport pandas as pd\nfrom matplotlib.patches import Circle\n\n\nclass DICOMViewer:\n    def __init__(self, root):\n        self.root = root\n        self.root.title(\"DICOM Viewer\")\n\n        self.dicom_folders = []\n        self.selected_folder = tk.StringVar()\n        self.selected_subfolder = tk.StringVar()\n        self.subfolders = []\n        self.dicom_files = []\n        self.current_index = 0\n        self.relevant_rows = pd.DataFrame()\n        self.circles = []\n\n        self.folder_select_label = tk.Label(self.root, text=\"Select DICOM Folder:\")\n        self.folder_select_label.pack()\n\n        self.folder_combobox = Combobox(self.root, textvariable=self.selected_folder, state=\"readonly\")\n        self.folder_combobox.pack()\n        self.folder_combobox.bind(\"<<ComboboxSelected>>\", self.load_dicom_folder)\n\n        self.subfolder_select_label = tk.Label(self.root, text=\"Select DICOM Subfolder:\")\n        self.subfolder_select_label.pack()\n\n        self.subfolder_combobox = Combobox(self.root, textvariable=self.selected_subfolder, state=\"readonly\")\n        self.subfolder_combobox.pack()\n        self.subfolder_combobox.bind(\"<<ComboboxSelected>>\", self.load_subfolder)\n\n        self.canvas_frame = tk.Frame(self.root)\n        self.canvas_frame.pack(fill=tk.BOTH, expand=True)\n\n        self.prev_button = tk.Button(self.root, text=\"Previous\", command=self.show_previous_image)\n        self.prev_button.pack(side=tk.LEFT, padx=10)\n\n        self.next_button = tk.Button(self.root, text=\"Next\", command=self.show_next_image)\n        self.next_button.pack(side=tk.RIGHT, padx=10)\n\n        self.root.bind('<Left>', self.key_previous_image)\n        self.root.bind('<Right>', self.key_next_image)\n\n        self.figure, self.ax = plt.subplots()\n        self.canvas = FigureCanvasTkAgg(self.figure, master=self.canvas_frame)\n        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)\n\n        self.canvas.mpl_connect('motion_notify_event', self.mouse_move)\n\n        # Mouse coordinates label\n        self.coord_label = tk.Label(self.root, text=\"X: , Y: \")\n        self.coord_label.pack()\n\n        # Load the CSV file\n        self.csv_file = csvFileLocation\n        self.df = pd.read_csv(self.csv_file)\n\n        # Populate DICOM folders\n        self.populate_dicom_folders()\n\n    def populate_dicom_folders(self):\n        root_folder = dicomFolderLocation\n        self.dicom_folders = sorted([f for f in os.listdir(root_folder) if os.path.isdir(os.path.join(root_folder, f))],\n                                    key=lambda x: int(x))\n        self.folder_combobox['values'] = self.dicom_folders\n\n    def load_dicom_folder(self, event=None):\n        selected_folder = self.selected_folder.get()\n        if not selected_folder:\n            messagebox.showwarning(\"No folder selected\", \"Please select a DICOM folder.\")\n            return\n\n        folder_path = os.path.join(dicomFolderLocation,\n                                   selected_folder)\n        subfolders = [f for f in os.listdir(folder_path) if os.path.isdir(os.path.join(folder_path, f))]\n        if not subfolders:\n            messagebox.showwarning(\"No subfolders\", \"The selected folder does not contain any subfolders.\")\n            return\n\n        self.subfolders = subfolders\n        self.subfolder_combobox['values'] = self.subfolders\n\n    def load_subfolder(self, event=None):\n        selected_subfolder = self.selected_subfolder.get()\n        if not selected_subfolder:\n            messagebox.showwarning(\"No subfolder selected\", \"Please select a DICOM subfolder.\")\n            return\n\n        folder_path = os.path.join(dicomFolderLocation,\n                                   self.selected_folder.get(), selected_subfolder)\n        self.dicom_files = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.lower().endswith('.dcm')]\n        if not self.dicom_files:\n            messagebox.showwarning(\"No DICOM files\", \"The selected subfolder does not contain any DICOM files.\")\n            return\n\n        # Extract study_id and series_id from folder path\n        folder_parts = os.path.normpath(folder_path).split(os.sep)\n        try:\n            study_id = int(folder_parts[-2])\n            series_id = int(folder_parts[-1])\n        except ValueError:\n            messagebox.showerror(\"Invalid folder structure\",\n                                 \"Folder structure must be .../study_id/series_id/subfolder_name\")\n            return\n\n        # Find and store relevant rows from the CSV file\n        self.relevant_rows = self.df[(self.df['study_id'] == study_id) & (self.df['series_id'] == series_id)]\n        if self.relevant_rows.empty:\n            print(f\"No matching records found for study_id: {study_id}, series_id: {series_id}\")\n        else:\n            print(f\"Matching records for study_id: {study_id}, series_id: {series_id}:\\n{self.relevant_rows}\")\n\n        # Sort files numerically\n        self.dicom_files.sort(key=self.numerical_sort_key)\n\n        self.current_index = 0\n        self.show_image()\n\n    def numerical_sort_key(self, filename):\n        \"\"\" Extract numbers from filename for proper numerical sorting. \"\"\"\n        base_name = os.path.basename(filename)\n        numbers = re.findall(r'\\d+', base_name)\n        return tuple(int(number) for number in numbers) if numbers else (base_name,)\n\n    def show_image(self):\n        if not self.dicom_files:\n            return\n\n        dicom_file = self.dicom_files[self.current_index]\n        try:\n            ds = pydicom.dcmread(dicom_file)\n            pixel_array = ds.pixel_array\n\n            self.ax.clear()\n            self.circles.clear()\n            self.ax.imshow(pixel_array, cmap='gray')\n\n            # Get the instance number from the file name\n            instance_number = int(re.findall(r'\\d+', os.path.basename(dicom_file))[0])\n\n            # Plot circles for matching instance_number\n            for _, row in self.relevant_rows.iterrows():\n                if row['instance_number'] == instance_number:\n                    circle = Circle((row['x'], row['y']), radius=10, color='red', fill=False, picker=True)\n                    self.ax.add_patch(circle)\n                    self.circles.append((circle, row['condition'], row['level']))\n\n            # Set the default title with the file name\n            self.ax.set_title(os.path.basename(dicom_file))\n            self.ax.axis('on')  # Show axes\n\n            self.canvas.draw()\n\n            self.root.title(f\"DICOM Viewer - {os.path.basename(dicom_file)}\")\n        except Exception as e:\n            messagebox.showerror(\"Error\", f\"Failed to load image: {e}\")\n            print(f\"Failed to load image from {dicom_file}: {e}\")\n\n    def show_previous_image(self):\n        if self.current_index > 0:\n            self.current_index -= 1\n            self.show_image()\n\n    def show_next_image(self):\n        if self.current_index < len(self.dicom_files) - 1:\n            self.current_index += 1\n            self.show_image()\n\n    def key_previous_image(self, event):\n        self.show_previous_image()\n\n    def key_next_image(self, event):\n        self.show_next_image()\n\n    def mouse_move(self, event):\n        if self.dicom_files:\n            if event.inaxes:\n                x, y = event.xdata, event.ydata\n                self.coord_label.config(text=f\"X: {x:.2f}, Y: {y:.2f}\")\n\n                for circle, condition, level in self.circles:\n                    contains, _ = circle.contains(event)\n                    if contains:\n                        self.ax.set_title(f\"{condition}, {level}\")\n                        self.canvas.draw_idle()\n                        return\n\n                # If no circle is hovered, reset title to default\n                dicom_file = self.dicom_files[self.current_index]\n                self.ax.set_title(os.path.basename(dicom_file))\n                self.canvas.draw_idle()\n\nif __name__ == \"__main__\":\n    root = tk.Tk()\n    viewer = DICOMViewer(root)\n    root.mainloop()\n\n```\n",
      "votes": 1
    }
  ],
  "comments": [],
  "raw_markdown_by_id": {
    "2837288": "I've created a dicom viewer for this mission:\n1. Change the first two lines of the code to the location where your CSV is located, and then run it on your local PC.\n2. Press the 'Load DICOM folder' button.\n3. Choose the relevant DICOM folder.\n4. Now you can see all the DICOM files, and you can move between the images using the keyboard keys or the buttons on the GUI.\n5. Hovering above the red circles will give you the condition and level data.\"\n\ninspired by https://www.kaggle.com/code/hasanbasbunar/dicom-image-annotation-visualization\n\nthanks to chatgpt :)\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F1140496%2Fa361400dcc5d060a98c5dbe77b738460%2Fdownload.png?generation=1716725458004160&alt=media)\n\n\n```python\ncsvFileLocation = r\"D:\\rsna-2024-lumbar-spine-degenerative-classification\\train_label_coordinates.csv\"\ndicomFolderLocation = r\"D:\\rsna-2024-lumbar-spine-degenerative-classification\\train_images\"\nimport tkinter as tk\nfrom tkinter import filedialog, messagebox\nfrom tkinter.ttk import Combobox\nfrom PIL import Image, ImageTk\nimport pydicom\nimport os\nimport numpy as np\nimport pylibjpeg\nimport matplotlib.pyplot as plt\nfrom matplotlib.backends.backend_tkagg import FigureCanvasTkAgg\nimport re\nimport pandas as pd\nfrom matplotlib.patches import Circle\n\n\nclass DICOMViewer:\n    def __init__(self, root):\n        self.root = root\n        self.root.title(\"DICOM Viewer\")\n\n        self.dicom_folders = []\n        self.selected_folder = tk.StringVar()\n        self.selected_subfolder = tk.StringVar()\n        self.subfolders = []\n        self.dicom_files = []\n        self.current_index = 0\n        self.relevant_rows = pd.DataFrame()\n        self.circles = []\n\n        self.folder_select_label = tk.Label(self.root, text=\"Select DICOM Folder:\")\n        self.folder_select_label.pack()\n\n        self.folder_combobox = Combobox(self.root, textvariable=self.selected_folder, state=\"readonly\")\n        self.folder_combobox.pack()\n        self.folder_combobox.bind(\"<<ComboboxSelected>>\", self.load_dicom_folder)\n\n        self.subfolder_select_label = tk.Label(self.root, text=\"Select DICOM Subfolder:\")\n        self.subfolder_select_label.pack()\n\n        self.subfolder_combobox = Combobox(self.root, textvariable=self.selected_subfolder, state=\"readonly\")\n        self.subfolder_combobox.pack()\n        self.subfolder_combobox.bind(\"<<ComboboxSelected>>\", self.load_subfolder)\n\n        self.canvas_frame = tk.Frame(self.root)\n        self.canvas_frame.pack(fill=tk.BOTH, expand=True)\n\n        self.prev_button = tk.Button(self.root, text=\"Previous\", command=self.show_previous_image)\n        self.prev_button.pack(side=tk.LEFT, padx=10)\n\n        self.next_button = tk.Button(self.root, text=\"Next\", command=self.show_next_image)\n        self.next_button.pack(side=tk.RIGHT, padx=10)\n\n        self.root.bind('<Left>', self.key_previous_image)\n        self.root.bind('<Right>', self.key_next_image)\n\n        self.figure, self.ax = plt.subplots()\n        self.canvas = FigureCanvasTkAgg(self.figure, master=self.canvas_frame)\n        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)\n\n        self.canvas.mpl_connect('motion_notify_event', self.mouse_move)\n\n        # Mouse coordinates label\n        self.coord_label = tk.Label(self.root, text=\"X: , Y: \")\n        self.coord_label.pack()\n\n        # Load the CSV file\n        self.csv_file = csvFileLocation\n        self.df = pd.read_csv(self.csv_file)\n\n        # Populate DICOM folders\n        self.populate_dicom_folders()\n\n    def populate_dicom_folders(self):\n        root_folder = dicomFolderLocation\n        self.dicom_folders = sorted([f for f in os.listdir(root_folder) if os.path.isdir(os.path.join(root_folder, f))],\n                                    key=lambda x: int(x))\n        self.folder_combobox['values'] = self.dicom_folders\n\n    def load_dicom_folder(self, event=None):\n        selected_folder = self.selected_folder.get()\n        if not selected_folder:\n            messagebox.showwarning(\"No folder selected\", \"Please select a DICOM folder.\")\n            return\n\n        folder_path = os.path.join(dicomFolderLocation,\n                                   selected_folder)\n        subfolders = [f for f in os.listdir(folder_path) if os.path.isdir(os.path.join(folder_path, f))]\n        if not subfolders:\n            messagebox.showwarning(\"No subfolders\", \"The selected folder does not contain any subfolders.\")\n            return\n\n        self.subfolders = subfolders\n        self.subfolder_combobox['values'] = self.subfolders\n\n    def load_subfolder(self, event=None):\n        selected_subfolder = self.selected_subfolder.get()\n        if not selected_subfolder:\n            messagebox.showwarning(\"No subfolder selected\", \"Please select a DICOM subfolder.\")\n            return\n\n        folder_path = os.path.join(dicomFolderLocation,\n                                   self.selected_folder.get(), selected_subfolder)\n        self.dicom_files = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.lower().endswith('.dcm')]\n        if not self.dicom_files:\n            messagebox.showwarning(\"No DICOM files\", \"The selected subfolder does not contain any DICOM files.\")\n            return\n\n        # Extract study_id and series_id from folder path\n        folder_parts = os.path.normpath(folder_path).split(os.sep)\n        try:\n            study_id = int(folder_parts[-2])\n            series_id = int(folder_parts[-1])\n        except ValueError:\n            messagebox.showerror(\"Invalid folder structure\",\n                                 \"Folder structure must be .../study_id/series_id/subfolder_name\")\n            return\n\n        # Find and store relevant rows from the CSV file\n        self.relevant_rows = self.df[(self.df['study_id'] == study_id) & (self.df['series_id'] == series_id)]\n        if self.relevant_rows.empty:\n            print(f\"No matching records found for study_id: {study_id}, series_id: {series_id}\")\n        else:\n            print(f\"Matching records for study_id: {study_id}, series_id: {series_id}:\\n{self.relevant_rows}\")\n\n        # Sort files numerically\n        self.dicom_files.sort(key=self.numerical_sort_key)\n\n        self.current_index = 0\n        self.show_image()\n\n    def numerical_sort_key(self, filename):\n        \"\"\" Extract numbers from filename for proper numerical sorting. \"\"\"\n        base_name = os.path.basename(filename)\n        numbers = re.findall(r'\\d+', base_name)\n        return tuple(int(number) for number in numbers) if numbers else (base_name,)\n\n    def show_image(self):\n        if not self.dicom_files:\n            return\n\n        dicom_file = self.dicom_files[self.current_index]\n        try:\n            ds = pydicom.dcmread(dicom_file)\n            pixel_array = ds.pixel_array\n\n            self.ax.clear()\n            self.circles.clear()\n            self.ax.imshow(pixel_array, cmap='gray')\n\n            # Get the instance number from the file name\n            instance_number = int(re.findall(r'\\d+', os.path.basename(dicom_file))[0])\n\n            # Plot circles for matching instance_number\n            for _, row in self.relevant_rows.iterrows():\n                if row['instance_number'] == instance_number:\n                    circle = Circle((row['x'], row['y']), radius=10, color='red', fill=False, picker=True)\n                    self.ax.add_patch(circle)\n                    self.circles.append((circle, row['condition'], row['level']))\n\n            # Set the default title with the file name\n            self.ax.set_title(os.path.basename(dicom_file))\n            self.ax.axis('on')  # Show axes\n\n            self.canvas.draw()\n\n            self.root.title(f\"DICOM Viewer - {os.path.basename(dicom_file)}\")\n        except Exception as e:\n            messagebox.showerror(\"Error\", f\"Failed to load image: {e}\")\n            print(f\"Failed to load image from {dicom_file}: {e}\")\n\n    def show_previous_image(self):\n        if self.current_index > 0:\n            self.current_index -= 1\n            self.show_image()\n\n    def show_next_image(self):\n        if self.current_index < len(self.dicom_files) - 1:\n            self.current_index += 1\n            self.show_image()\n\n    def key_previous_image(self, event):\n        self.show_previous_image()\n\n    def key_next_image(self, event):\n        self.show_next_image()\n\n    def mouse_move(self, event):\n        if self.dicom_files:\n            if event.inaxes:\n                x, y = event.xdata, event.ydata\n                self.coord_label.config(text=f\"X: {x:.2f}, Y: {y:.2f}\")\n\n                for circle, condition, level in self.circles:\n                    contains, _ = circle.contains(event)\n                    if contains:\n                        self.ax.set_title(f\"{condition}, {level}\")\n                        self.canvas.draw_idle()\n                        return\n\n                # If no circle is hovered, reset title to default\n                dicom_file = self.dicom_files[self.current_index]\n                self.ax.set_title(os.path.basename(dicom_file))\n                self.canvas.draw_idle()\n\nif __name__ == \"__main__\":\n    root = tk.Tk()\n    viewer = DICOMViewer(root)\n    root.mainloop()\n\n```\n"
  }
}