OiO.lk Community platform!

Oio.lk is an excellent forum for developers, providing a wide range of resources, discussions, and support for those in the developer community. Join oio.lk today to connect with like-minded professionals, share insights, and stay updated on the latest trends and technologies in the development field.
  You need to log in or register to access the solved answers to this problem.
  • You have reached the maximum number of guest views allowed
  • Please register below to remove this limitation

PyQt5 simpleITK DICOM Viewer: Sagittal and Coronal Images Stretched Vertically in basic MPR

  • Thread starter Thread starter Miguel Nobre Menezes
  • Start date Start date
M

Miguel Nobre Menezes

Guest
Question:

I am developing a DICOM viewer application using PyQt5 and SimpleITK. The viewer is supposed to display axial, sagittal, and coronal slices of a DICOM series. However, the sagittal and coronal images are stretched vertically, and I can't seem to fix it.

Code Overview

I use SimpleITK to read the DICOM series and extract the slices. The display_slice function adjusts the aspect ratio of the images based on pixel spacing and slice thickness. Below is the full code with the relevant parts highlighted:

Code:
import sys
import os
import SimpleITK as sitk
import numpy as np
import pydicom
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QLabel, QMessageBox, QVBoxLayout, QSizePolicy, QListWidgetItem
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QImage, QPixmap
from PyQt5 import uic, QtGui

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi('main.ui', self)
        self.setup_connections()
        self.image = None
        self.series = []
        self.current_series = 0
        self.dicom_series = {}
        self.current_index = 0
        self.series_images = np.array([])  # Initialize as an empty NumPy array
        self.pixel_spacing = None  # Placeholder for pixel spacing
        self.z_spacing = None  # Placeholder for Z spacing
        self.crosshair_x = 0
        self.crosshair_y = 0
        self.crosshair_z = 0

        # Create QLabel widgets for displaying images
        self.axialLabel = QLabel(self.axialContainer)
        self.sagittalLabel = QLabel(self.sagittalContainer)
        self.coronalLabel = QLabel(self.coronalContainer)

        # Set size policies to expand and fill the containers
        for label in (self.axialLabel, self.sagittalLabel, self.coronalLabel):
            label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
            label.setAlignment(Qt.AlignCenter)

        # Create layouts for the containers
        self.axialLayout = QVBoxLayout(self.axialContainer)
        self.sagittalLayout = QVBoxLayout(self.sagittalContainer)
        self.coronalLayout = QVBoxLayout(self.coronalContainer)

        # Add labels to the layouts
        self.axialLayout.addWidget(self.axialLabel)
        self.sagittalLayout.addWidget(self.sagittalLabel)
        self.coronalLayout.addWidget(self.coronalLabel)

    def setup_connections(self):
        self.loadButton.clicked.connect(self.load_dicom_folder)
        self.prevButton.clicked.connect(self.previous_series)
        self.nextButton.clicked.connect(self.next_series)
        self.mprButton.clicked.connect(self.show_mpr)

        self.axialScrollBar.valueChanged.connect(self.update_axial_view)
        self.sagittalScrollBar.valueChanged.connect(self.update_sagittal_view)
        self.coronalScrollBar.valueChanged.connect(self.update_coronal_view)
        self.seriesList.currentRowChanged.connect(self.series_selected)  # Connect the selection event

    def load_dicom_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Select DICOM folder")
        if folder:
            try:
                # Recursively search for DICOM files in all subfolders
                dicom_files = self.find_dicom_files(folder)
                
                if not dicom_files:
                    raise RuntimeError("No DICOM files found in the selected folder or its subfolders")
                
                # Group DICOM files by series
                series_dict = self.group_dicom_by_series(dicom_files)
                
                if not series_dict:
                    raise RuntimeError("No valid DICOM series found in the selected folder or its subfolders")
                
                self.dicom_series = series_dict
                self.seriesList.clear()
                
                total_series = len(series_dict)
                for idx, (series_uid, files) in enumerate(series_dict.items(), start=1):
                    ds = pydicom.dcmread(files[0])
                    description = ds.SeriesDescription if 'SeriesDescription' in ds else 'No Description'
                    item = QListWidgetItem(f"Series {idx}/{total_series} - <p>Question:</p>
<p>I am developing a DICOM viewer application using PyQt5 and SimpleITK. The viewer is supposed to display axial, sagittal, and coronal slices of a DICOM series. However, the sagittal and coronal images are stretched vertically, and I can't seem to fix it.</p>
<p>Code Overview</p>
<p>I use SimpleITK to read the DICOM series and extract the slices. The display_slice function adjusts the aspect ratio of the images based on pixel spacing and slice thickness. Below is the full code with the relevant parts highlighted:</p>
<pre><code>import sys
import os
import SimpleITK as sitk
import numpy as np
import pydicom
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QLabel, QMessageBox, QVBoxLayout, QSizePolicy, QListWidgetItem
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QImage, QPixmap
from PyQt5 import uic, QtGui

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi('main.ui', self)
        self.setup_connections()
        self.image = None
        self.series = []
        self.current_series = 0
        self.dicom_series = {}
        self.current_index = 0
        self.series_images = np.array([])  # Initialize as an empty NumPy array
        self.pixel_spacing = None  # Placeholder for pixel spacing
        self.z_spacing = None  # Placeholder for Z spacing
        self.crosshair_x = 0
        self.crosshair_y = 0
        self.crosshair_z = 0

        # Create QLabel widgets for displaying images
        self.axialLabel = QLabel(self.axialContainer)
        self.sagittalLabel = QLabel(self.sagittalContainer)
        self.coronalLabel = QLabel(self.coronalContainer)

        # Set size policies to expand and fill the containers
        for label in (self.axialLabel, self.sagittalLabel, self.coronalLabel):
            label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
            label.setAlignment(Qt.AlignCenter)

        # Create layouts for the containers
        self.axialLayout = QVBoxLayout(self.axialContainer)
        self.sagittalLayout = QVBoxLayout(self.sagittalContainer)
        self.coronalLayout = QVBoxLayout(self.coronalContainer)

        # Add labels to the layouts
        self.axialLayout.addWidget(self.axialLabel)
        self.sagittalLayout.addWidget(self.sagittalLabel)
        self.coronalLayout.addWidget(self.coronalLabel)

    def setup_connections(self):
        self.loadButton.clicked.connect(self.load_dicom_folder)
        self.prevButton.clicked.connect(self.previous_series)
        self.nextButton.clicked.connect(self.next_series)
        self.mprButton.clicked.connect(self.show_mpr)

        self.axialScrollBar.valueChanged.connect(self.update_axial_view)
        self.sagittalScrollBar.valueChanged.connect(self.update_sagittal_view)
        self.coronalScrollBar.valueChanged.connect(self.update_coronal_view)
        self.seriesList.currentRowChanged.connect(self.series_selected)  # Connect the selection event

    def load_dicom_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Select DICOM folder")
        if folder:
            try:
                # Recursively search for DICOM files in all subfolders
                dicom_files = self.find_dicom_files(folder)
                
                if not dicom_files:
                    raise RuntimeError("No DICOM files found in the selected folder or its subfolders")
                
                # Group DICOM files by series
                series_dict = self.group_dicom_by_series(dicom_files)
                
                if not series_dict:
                    raise RuntimeError("No valid DICOM series found in the selected folder or its subfolders")
                
                self.dicom_series = series_dict
                self.seriesList.clear()
                
                total_series = len(series_dict)
                for idx, (series_uid, files) in enumerate(series_dict.items(), start=1):
                    ds = pydicom.dcmread(files[0])
                    description = ds.SeriesDescription if 'SeriesDescription' in ds else 'No Description'
                    item = QListWidgetItem(f"Series {idx}/{total_series} - {description} ({len(files)} images)")
                    item.setData(Qt.UserRole, series_uid)
                    self.seriesList.addItem(item)

                if self.seriesList.count() > 0:
                    self.seriesList.setCurrentRow(0)
                    self.series_selected(0)
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Error loading DICOM: {str(e)}")
                print(f"Error loading DICOM: {str(e)}")

    def find_dicom_files(self, root_dir):
        dicom_files = []
        for root, _, files in os.walk(root_dir):
            for file in files:
                if file.lower().endswith('.dcm'):
                    full_path = os.path.join(root, file)
                    if self.is_dicom_image(full_path):
                        dicom_files.append(full_path)
                    else:
                        print(f"Skipping non-image DICOM file: {full_path}")
        return dicom_files

    def is_dicom_image(self, file_path):
        try:
            ds = pydicom.dcmread(file_path)
            return 'PixelData' in ds
        except:
            return False

    def group_dicom_by_series(self, dicom_files):
        series_dict = {}
        for file in dicom_files:
            try:
                ds = pydicom.dcmread(file)
                series_id = ds.SeriesInstanceUID
                if series_id not in series_dict:
                    series_dict[series_id] = []
                series_dict[series_id].append(file)
            except Exception as e:
                print(f"Warning: Could not read file {file} with pydicom. Error: {str(e)}")
        return series_dict

    def series_selected(self, index):
        series_uid = self.seriesList.item(index).data(Qt.UserRole)
        self.load_series(self.dicom_series[series_uid])

    def load_series(self, dicom_files):
        sorted_files = self.sort_dicom_files(dicom_files)
            
        if not sorted_files:
            raise RuntimeError("No valid DICOM files found in the series")

        # Read all slices using SimpleITK
        reader = sitk.ImageSeriesReader()
        reader.SetFileNames(sorted_files)
        self.image = reader.Execute()
        # Print image data
        print(f"Image Size: {self.image.GetSize()}")
        print(f"Image Spacing: {self.image.GetSpacing()}")
        print(f"Image Direction: {self.image.GetDirection()}")
        print(f"Image Origin: {self.image.GetOrigin()}")

        # Get spacing, size, and direction
        spacing = self.image.GetSpacing()
        self.pixel_spacing = spacing[:2]
        self.z_spacing = spacing[2]

        # Extract slice thickness from the first DICOM file
        first_dicom = pydicom.dcmread(sorted_files[0])
        self.slice_thickness = float(first_dicom.SliceThickness)

        print(f"Pixel Spacing: {self.pixel_spacing}, Z Spacing: {self.z_spacing}, Slice Thickness: {self.slice_thickness}")

        self.direction = self.image.GetDirection()
        self.size = self.image.GetSize()
        
        # Calculate slice spacing
        self.slice_spacing = self.calculate_slice_spacing(sorted_files)
        print(f"Calculated Slice Spacing: {self.slice_spacing}")

        # Store the sorted files as an attribute
        self.sorted_files = sorted_files 

        # Set up scrollbars
        self.axialScrollBar.setMaximum(self.size[2] - 1)
        self.sagittalScrollBar.setMaximum(self.size[0] - 1)
        self.coronalScrollBar.setMaximum(self.size[1] - 1)

        self.update_views()

    def calculate_slice_spacing(self, dicom_files):
        positions = []
        orientations = []
        for file in dicom_files[:2]:  # We only need the first two files
            ds = pydicom.dcmread(file)
            pos = tuple(map(float, ds.ImagePositionPatient))  # Convert to tuple of floats
            ori = list(map(float, ds.ImageOrientationPatient))  # Convert to list of floats
            positions.append(np.array(pos))
            orientations.append(np.array(ori))
        
        # Calculate the normal vector to the image plane
        row_cosine = orientations[0][:3]
        col_cosine = orientations[0][3:]
        normal_vector = np.cross(row_cosine, col_cosine)
        
        # Calculate the distance between the first two slices
        spacing = np.abs(np.dot(positions[1] - positions[0], normal_vector))
        return spacing

    def sort_dicom_files(self, dicom_files):
        valid_files = []
        for file in dicom_files:
            try:
                ds = pydicom.dcmread(file)
                position = tuple(map(float, ds.ImagePositionPatient))  # Convert to tuple of floats
                valid_files.append((file, position))
            except Exception as e:
                print(f"Warning: Could not read file {file}. Error: {str(e)}")
                continue

        # Sort based on the z-coordinate (3rd element) of the ImagePositionPatient
        return [file for file, _ in sorted(valid_files, key=lambda x: x[1][2])]

    def update_views(self):
        if self.image is None:
            print("No image loaded")
            return
        self.update_axial_view(self.axialScrollBar.value())
        self.update_sagittal_view(self.sagittalScrollBar.value())
        self.update_coronal_view(self.coronalScrollBar.value())

    def update_axial_view(self, slice_index):
        if self.image is not None:
            axial_slice = sitk.GetArrayViewFromImage(self.image)[slice_index, :, :]
            self.display_slice(axial_slice, self.axialLabel, 'axial')

    def update_sagittal_view(self, slice_index):
        if self.image is not None:
            sagittal_slice = sitk.GetArrayViewFromImage(self.image)[:, :, slice_index]
            sagittal_slice = np.flipud(sagittal_slice)
            self.display_slice(sagittal_slice, self.sagittalLabel, 'sagittal')

    def update_coronal_view(self, slice_index):
        if self.image is not None:
            coronal_slice = sitk.GetArrayViewFromImage(self.image)[:, slice_index, :]
            coronal_slice = np.flipud(coronal_slice)
            self.display_slice(coronal_slice, self.coronalLabel, 'coronal')

    def display_slice(self, slice_array, label, plane):
        if isinstance(label, QLabel):
            print(f"Displaying {plane} slice. Shape: {slice_array.shape}")
            normalized_slice = self.normalize_slice(slice_array)
            height, width = normalized_slice.shape

            qimage = QImage(normalized_slice.data, width, height, width, QImage.Format_Grayscale8)
            pixmap = QPixmap.fromImage(qimage)

            label_size = label.size()
            print(f"{plane} label size: {label_size.width()} x {label_size.height()}")

            if plane == 'axial':
                x_scale = self.pixel_spacing[0]
                y_scale = self.pixel_spacing[1]
            elif plane == 'sagittal':
                x_scale = self.pixel_spacing[0]
                y_scale = self.z_spacing
            elif plane == 'coronal':
                x_scale = self.z_spacing
                y_scale = self.pixel_spacing[1]
            else:
                x_scale = y_scale = 1.0

            aspect_ratio = (width * x_scale) / (height * y_scale)
            print(f"{plane} - Aspect ratio: {aspect_ratio:.2f}")

            if label_size.width() / label_size.height() > aspect_ratio:
                scaled_pixmap = pixmap.scaledToHeight(label_size.height(), Qt.SmoothTransformation)
            else:
                scaled_pixmap = pixmap.scaledToWidth(label_size.width(), Qt.SmoothTransformation)

            label.setPixmap(scaled_pixmap)
            label.setAlignment(Qt.AlignCenter)
        else:
            print(f"Label is not a QLabel: {type(label)}")

    def normalize_slice(self, slice_array):
        min_val = np.min(slice_array)
        max_val = np.max(slice_array)

        if min_val == max_val:
            return np.zeros_like(slice_array, dtype=np.uint8)
        else:
            return ((slice_array - min_val) / (max_val - min_val) * 255).astype(np.uint8)

    def previous_series(self):
        if self.current_series > 0:
            self.current_series -= 1
            self.load_current_series()

    def next_series(self):
        if self.current_series < len(self.series) - 1:
            self.current_series += 1
            self.load_current_series()

    def load_current_series(self):
        if self.series:
            _, _, dicom_files = self.series[self.current_series]
            self.load_series(dicom_files)
            self.update_series_info()
    
    def show_mpr(self):
        if self.image is not None:
            # Reset scroll bars to middle of each dimension
            size = self.image.GetSize()
            self.axialScrollBar.setValue(size[2] // 2)
            self.sagittalScrollBar.setValue(size[0] // 2)
            self.coronalScrollBar.setValue(size[1] // 2)
            self.update_views()

    def update_series_info(self):
        if not self.series:
            self.seriesInfoLabel.setText("No series loaded")
        else:
            series_id, description, modality, num_images, _ = self.series[self.current_series]
            self.seriesInfoLabel.setText(f"Series ID: {series_id}\nDescription: {description}\nModality: {modality}\nNumber of Images: {num_images}")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())
</code></pre>
<p>Issue:
The sagittal and coronal images are being stretched vertically, and I suspect it has something to do with how the spacing is handled or how the aspect ratio is calculated in the display_slice function.</p>
<p>What I've Tried:
Calculated the aspect ratio using pixel spacing and z-spacing.
Adjusted the scaling of the QPixmap based on the calculated aspect ratio.
Verified that spacing information is correctly extracted from the DICOM files.
Observations:
Axial images display correctly.
Sagittal and coronal images are stretched vertically.
Specific Question:
How can I correctly display the sagittal and coronal images without vertical stretching? Is there an issue with how I am calculating the aspect ratio or scaling the images?</p>
<p>Thank you for your help!</p> ({len(files)} images)")
                    item.setData(Qt.UserRole, series_uid)
                    self.seriesList.addItem(item)

                if self.seriesList.count() > 0:
                    self.seriesList.setCurrentRow(0)
                    self.series_selected(0)
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Error loading DICOM: {str(e)}")
                print(f"Error loading DICOM: {str(e)}")

    def find_dicom_files(self, root_dir):
        dicom_files = []
        for root, _, files in os.walk(root_dir):
            for file in files:
                if file.lower().endswith('.dcm'):
                    full_path = os.path.join(root, file)
                    if self.is_dicom_image(full_path):
                        dicom_files.append(full_path)
                    else:
                        print(f"Skipping non-image DICOM file: {full_path}")
        return dicom_files

    def is_dicom_image(self, file_path):
        try:
            ds = pydicom.dcmread(file_path)
            return 'PixelData' in ds
        except:
            return False

    def group_dicom_by_series(self, dicom_files):
        series_dict = {}
        for file in dicom_files:
            try:
                ds = pydicom.dcmread(file)
                series_id = ds.SeriesInstanceUID
                if series_id not in series_dict:
                    series_dict[series_id] = []
                series_dict[series_id].append(file)
            except Exception as e:
                print(f"Warning: Could not read file {file} with pydicom. Error: {str(e)}")
        return series_dict

    def series_selected(self, index):
        series_uid = self.seriesList.item(index).data(Qt.UserRole)
        self.load_series(self.dicom_series[series_uid])

    def load_series(self, dicom_files):
        sorted_files = self.sort_dicom_files(dicom_files)
            
        if not sorted_files:
            raise RuntimeError("No valid DICOM files found in the series")

        # Read all slices using SimpleITK
        reader = sitk.ImageSeriesReader()
        reader.SetFileNames(sorted_files)
        self.image = reader.Execute()
        # Print image data
        print(f"Image Size: {self.image.GetSize()}")
        print(f"Image Spacing: {self.image.GetSpacing()}")
        print(f"Image Direction: {self.image.GetDirection()}")
        print(f"Image Origin: {self.image.GetOrigin()}")

        # Get spacing, size, and direction
        spacing = self.image.GetSpacing()
        self.pixel_spacing = spacing[:2]
        self.z_spacing = spacing[2]

        # Extract slice thickness from the first DICOM file
        first_dicom = pydicom.dcmread(sorted_files[0])
        self.slice_thickness = float(first_dicom.SliceThickness)

        print(f"Pixel Spacing: {self.pixel_spacing}, Z Spacing: {self.z_spacing}, Slice Thickness: {self.slice_thickness}")

        self.direction = self.image.GetDirection()
        self.size = self.image.GetSize()
        
        # Calculate slice spacing
        self.slice_spacing = self.calculate_slice_spacing(sorted_files)
        print(f"Calculated Slice Spacing: {self.slice_spacing}")

        # Store the sorted files as an attribute
        self.sorted_files = sorted_files 

        # Set up scrollbars
        self.axialScrollBar.setMaximum(self.size[2] - 1)
        self.sagittalScrollBar.setMaximum(self.size[0] - 1)
        self.coronalScrollBar.setMaximum(self.size[1] - 1)

        self.update_views()

    def calculate_slice_spacing(self, dicom_files):
        positions = []
        orientations = []
        for file in dicom_files[:2]:  # We only need the first two files
            ds = pydicom.dcmread(file)
            pos = tuple(map(float, ds.ImagePositionPatient))  # Convert to tuple of floats
            ori = list(map(float, ds.ImageOrientationPatient))  # Convert to list of floats
            positions.append(np.array(pos))
            orientations.append(np.array(ori))
        
        # Calculate the normal vector to the image plane
        row_cosine = orientations[0][:3]
        col_cosine = orientations[0][3:]
        normal_vector = np.cross(row_cosine, col_cosine)
        
        # Calculate the distance between the first two slices
        spacing = np.abs(np.dot(positions[1] - positions[0], normal_vector))
        return spacing

    def sort_dicom_files(self, dicom_files):
        valid_files = []
        for file in dicom_files:
            try:
                ds = pydicom.dcmread(file)
                position = tuple(map(float, ds.ImagePositionPatient))  # Convert to tuple of floats
                valid_files.append((file, position))
            except Exception as e:
                print(f"Warning: Could not read file {file}. Error: {str(e)}")
                continue

        # Sort based on the z-coordinate (3rd element) of the ImagePositionPatient
        return [file for file, _ in sorted(valid_files, key=lambda x: x[1][2])]

    def update_views(self):
        if self.image is None:
            print("No image loaded")
            return
        self.update_axial_view(self.axialScrollBar.value())
        self.update_sagittal_view(self.sagittalScrollBar.value())
        self.update_coronal_view(self.coronalScrollBar.value())

    def update_axial_view(self, slice_index):
        if self.image is not None:
            axial_slice = sitk.GetArrayViewFromImage(self.image)[slice_index, :, :]
            self.display_slice(axial_slice, self.axialLabel, 'axial')

    def update_sagittal_view(self, slice_index):
        if self.image is not None:
            sagittal_slice = sitk.GetArrayViewFromImage(self.image)[:, :, slice_index]
            sagittal_slice = np.flipud(sagittal_slice)
            self.display_slice(sagittal_slice, self.sagittalLabel, 'sagittal')

    def update_coronal_view(self, slice_index):
        if self.image is not None:
            coronal_slice = sitk.GetArrayViewFromImage(self.image)[:, slice_index, :]
            coronal_slice = np.flipud(coronal_slice)
            self.display_slice(coronal_slice, self.coronalLabel, 'coronal')

    def display_slice(self, slice_array, label, plane):
        if isinstance(label, QLabel):
            print(f"Displaying {plane} slice. Shape: {slice_array.shape}")
            normalized_slice = self.normalize_slice(slice_array)
            height, width = normalized_slice.shape

            qimage = QImage(normalized_slice.data, width, height, width, QImage.Format_Grayscale8)
            pixmap = QPixmap.fromImage(qimage)

            label_size = label.size()
            print(f"{plane} label size: {label_size.width()} x {label_size.height()}")

            if plane == 'axial':
                x_scale = self.pixel_spacing[0]
                y_scale = self.pixel_spacing[1]
            elif plane == 'sagittal':
                x_scale = self.pixel_spacing[0]
                y_scale = self.z_spacing
            elif plane == 'coronal':
                x_scale = self.z_spacing
                y_scale = self.pixel_spacing[1]
            else:
                x_scale = y_scale = 1.0

            aspect_ratio = (width * x_scale) / (height * y_scale)
            print(f"{plane} - Aspect ratio: {aspect_ratio:.2f}")

            if label_size.width() / label_size.height() > aspect_ratio:
                scaled_pixmap = pixmap.scaledToHeight(label_size.height(), Qt.SmoothTransformation)
            else:
                scaled_pixmap = pixmap.scaledToWidth(label_size.width(), Qt.SmoothTransformation)

            label.setPixmap(scaled_pixmap)
            label.setAlignment(Qt.AlignCenter)
        else:
            print(f"Label is not a QLabel: {type(label)}")

    def normalize_slice(self, slice_array):
        min_val = np.min(slice_array)
        max_val = np.max(slice_array)

        if min_val == max_val:
            return np.zeros_like(slice_array, dtype=np.uint8)
        else:
            return ((slice_array - min_val) / (max_val - min_val) * 255).astype(np.uint8)

    def previous_series(self):
        if self.current_series > 0:
            self.current_series -= 1
            self.load_current_series()

    def next_series(self):
        if self.current_series < len(self.series) - 1:
            self.current_series += 1
            self.load_current_series()

    def load_current_series(self):
        if self.series:
            _, _, dicom_files = self.series[self.current_series]
            self.load_series(dicom_files)
            self.update_series_info()
    
    def show_mpr(self):
        if self.image is not None:
            # Reset scroll bars to middle of each dimension
            size = self.image.GetSize()
            self.axialScrollBar.setValue(size[2] // 2)
            self.sagittalScrollBar.setValue(size[0] // 2)
            self.coronalScrollBar.setValue(size[1] // 2)
            self.update_views()

    def update_series_info(self):
        if not self.series:
            self.seriesInfoLabel.setText("No series loaded")
        else:
            series_id, description, modality, num_images, _ = self.series[self.current_series]
            self.seriesInfoLabel.setText(f"Series ID: {series_id}\nDescription: <p>Question:</p>
<p>I am developing a DICOM viewer application using PyQt5 and SimpleITK. The viewer is supposed to display axial, sagittal, and coronal slices of a DICOM series. However, the sagittal and coronal images are stretched vertically, and I can't seem to fix it.</p>
<p>Code Overview</p>
<p>I use SimpleITK to read the DICOM series and extract the slices. The display_slice function adjusts the aspect ratio of the images based on pixel spacing and slice thickness. Below is the full code with the relevant parts highlighted:</p>
<pre><code>import sys
import os
import SimpleITK as sitk
import numpy as np
import pydicom
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QLabel, QMessageBox, QVBoxLayout, QSizePolicy, QListWidgetItem
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QImage, QPixmap
from PyQt5 import uic, QtGui

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi('main.ui', self)
        self.setup_connections()
        self.image = None
        self.series = []
        self.current_series = 0
        self.dicom_series = {}
        self.current_index = 0
        self.series_images = np.array([])  # Initialize as an empty NumPy array
        self.pixel_spacing = None  # Placeholder for pixel spacing
        self.z_spacing = None  # Placeholder for Z spacing
        self.crosshair_x = 0
        self.crosshair_y = 0
        self.crosshair_z = 0

        # Create QLabel widgets for displaying images
        self.axialLabel = QLabel(self.axialContainer)
        self.sagittalLabel = QLabel(self.sagittalContainer)
        self.coronalLabel = QLabel(self.coronalContainer)

        # Set size policies to expand and fill the containers
        for label in (self.axialLabel, self.sagittalLabel, self.coronalLabel):
            label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
            label.setAlignment(Qt.AlignCenter)

        # Create layouts for the containers
        self.axialLayout = QVBoxLayout(self.axialContainer)
        self.sagittalLayout = QVBoxLayout(self.sagittalContainer)
        self.coronalLayout = QVBoxLayout(self.coronalContainer)

        # Add labels to the layouts
        self.axialLayout.addWidget(self.axialLabel)
        self.sagittalLayout.addWidget(self.sagittalLabel)
        self.coronalLayout.addWidget(self.coronalLabel)

    def setup_connections(self):
        self.loadButton.clicked.connect(self.load_dicom_folder)
        self.prevButton.clicked.connect(self.previous_series)
        self.nextButton.clicked.connect(self.next_series)
        self.mprButton.clicked.connect(self.show_mpr)

        self.axialScrollBar.valueChanged.connect(self.update_axial_view)
        self.sagittalScrollBar.valueChanged.connect(self.update_sagittal_view)
        self.coronalScrollBar.valueChanged.connect(self.update_coronal_view)
        self.seriesList.currentRowChanged.connect(self.series_selected)  # Connect the selection event

    def load_dicom_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Select DICOM folder")
        if folder:
            try:
                # Recursively search for DICOM files in all subfolders
                dicom_files = self.find_dicom_files(folder)
                
                if not dicom_files:
                    raise RuntimeError("No DICOM files found in the selected folder or its subfolders")
                
                # Group DICOM files by series
                series_dict = self.group_dicom_by_series(dicom_files)
                
                if not series_dict:
                    raise RuntimeError("No valid DICOM series found in the selected folder or its subfolders")
                
                self.dicom_series = series_dict
                self.seriesList.clear()
                
                total_series = len(series_dict)
                for idx, (series_uid, files) in enumerate(series_dict.items(), start=1):
                    ds = pydicom.dcmread(files[0])
                    description = ds.SeriesDescription if 'SeriesDescription' in ds else 'No Description'
                    item = QListWidgetItem(f"Series {idx}/{total_series} - {description} ({len(files)} images)")
                    item.setData(Qt.UserRole, series_uid)
                    self.seriesList.addItem(item)

                if self.seriesList.count() > 0:
                    self.seriesList.setCurrentRow(0)
                    self.series_selected(0)
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Error loading DICOM: {str(e)}")
                print(f"Error loading DICOM: {str(e)}")

    def find_dicom_files(self, root_dir):
        dicom_files = []
        for root, _, files in os.walk(root_dir):
            for file in files:
                if file.lower().endswith('.dcm'):
                    full_path = os.path.join(root, file)
                    if self.is_dicom_image(full_path):
                        dicom_files.append(full_path)
                    else:
                        print(f"Skipping non-image DICOM file: {full_path}")
        return dicom_files

    def is_dicom_image(self, file_path):
        try:
            ds = pydicom.dcmread(file_path)
            return 'PixelData' in ds
        except:
            return False

    def group_dicom_by_series(self, dicom_files):
        series_dict = {}
        for file in dicom_files:
            try:
                ds = pydicom.dcmread(file)
                series_id = ds.SeriesInstanceUID
                if series_id not in series_dict:
                    series_dict[series_id] = []
                series_dict[series_id].append(file)
            except Exception as e:
                print(f"Warning: Could not read file {file} with pydicom. Error: {str(e)}")
        return series_dict

    def series_selected(self, index):
        series_uid = self.seriesList.item(index).data(Qt.UserRole)
        self.load_series(self.dicom_series[series_uid])

    def load_series(self, dicom_files):
        sorted_files = self.sort_dicom_files(dicom_files)
            
        if not sorted_files:
            raise RuntimeError("No valid DICOM files found in the series")

        # Read all slices using SimpleITK
        reader = sitk.ImageSeriesReader()
        reader.SetFileNames(sorted_files)
        self.image = reader.Execute()
        # Print image data
        print(f"Image Size: {self.image.GetSize()}")
        print(f"Image Spacing: {self.image.GetSpacing()}")
        print(f"Image Direction: {self.image.GetDirection()}")
        print(f"Image Origin: {self.image.GetOrigin()}")

        # Get spacing, size, and direction
        spacing = self.image.GetSpacing()
        self.pixel_spacing = spacing[:2]
        self.z_spacing = spacing[2]

        # Extract slice thickness from the first DICOM file
        first_dicom = pydicom.dcmread(sorted_files[0])
        self.slice_thickness = float(first_dicom.SliceThickness)

        print(f"Pixel Spacing: {self.pixel_spacing}, Z Spacing: {self.z_spacing}, Slice Thickness: {self.slice_thickness}")

        self.direction = self.image.GetDirection()
        self.size = self.image.GetSize()
        
        # Calculate slice spacing
        self.slice_spacing = self.calculate_slice_spacing(sorted_files)
        print(f"Calculated Slice Spacing: {self.slice_spacing}")

        # Store the sorted files as an attribute
        self.sorted_files = sorted_files 

        # Set up scrollbars
        self.axialScrollBar.setMaximum(self.size[2] - 1)
        self.sagittalScrollBar.setMaximum(self.size[0] - 1)
        self.coronalScrollBar.setMaximum(self.size[1] - 1)

        self.update_views()

    def calculate_slice_spacing(self, dicom_files):
        positions = []
        orientations = []
        for file in dicom_files[:2]:  # We only need the first two files
            ds = pydicom.dcmread(file)
            pos = tuple(map(float, ds.ImagePositionPatient))  # Convert to tuple of floats
            ori = list(map(float, ds.ImageOrientationPatient))  # Convert to list of floats
            positions.append(np.array(pos))
            orientations.append(np.array(ori))
        
        # Calculate the normal vector to the image plane
        row_cosine = orientations[0][:3]
        col_cosine = orientations[0][3:]
        normal_vector = np.cross(row_cosine, col_cosine)
        
        # Calculate the distance between the first two slices
        spacing = np.abs(np.dot(positions[1] - positions[0], normal_vector))
        return spacing

    def sort_dicom_files(self, dicom_files):
        valid_files = []
        for file in dicom_files:
            try:
                ds = pydicom.dcmread(file)
                position = tuple(map(float, ds.ImagePositionPatient))  # Convert to tuple of floats
                valid_files.append((file, position))
            except Exception as e:
                print(f"Warning: Could not read file {file}. Error: {str(e)}")
                continue

        # Sort based on the z-coordinate (3rd element) of the ImagePositionPatient
        return [file for file, _ in sorted(valid_files, key=lambda x: x[1][2])]

    def update_views(self):
        if self.image is None:
            print("No image loaded")
            return
        self.update_axial_view(self.axialScrollBar.value())
        self.update_sagittal_view(self.sagittalScrollBar.value())
        self.update_coronal_view(self.coronalScrollBar.value())

    def update_axial_view(self, slice_index):
        if self.image is not None:
            axial_slice = sitk.GetArrayViewFromImage(self.image)[slice_index, :, :]
            self.display_slice(axial_slice, self.axialLabel, 'axial')

    def update_sagittal_view(self, slice_index):
        if self.image is not None:
            sagittal_slice = sitk.GetArrayViewFromImage(self.image)[:, :, slice_index]
            sagittal_slice = np.flipud(sagittal_slice)
            self.display_slice(sagittal_slice, self.sagittalLabel, 'sagittal')

    def update_coronal_view(self, slice_index):
        if self.image is not None:
            coronal_slice = sitk.GetArrayViewFromImage(self.image)[:, slice_index, :]
            coronal_slice = np.flipud(coronal_slice)
            self.display_slice(coronal_slice, self.coronalLabel, 'coronal')

    def display_slice(self, slice_array, label, plane):
        if isinstance(label, QLabel):
            print(f"Displaying {plane} slice. Shape: {slice_array.shape}")
            normalized_slice = self.normalize_slice(slice_array)
            height, width = normalized_slice.shape

            qimage = QImage(normalized_slice.data, width, height, width, QImage.Format_Grayscale8)
            pixmap = QPixmap.fromImage(qimage)

            label_size = label.size()
            print(f"{plane} label size: {label_size.width()} x {label_size.height()}")

            if plane == 'axial':
                x_scale = self.pixel_spacing[0]
                y_scale = self.pixel_spacing[1]
            elif plane == 'sagittal':
                x_scale = self.pixel_spacing[0]
                y_scale = self.z_spacing
            elif plane == 'coronal':
                x_scale = self.z_spacing
                y_scale = self.pixel_spacing[1]
            else:
                x_scale = y_scale = 1.0

            aspect_ratio = (width * x_scale) / (height * y_scale)
            print(f"{plane} - Aspect ratio: {aspect_ratio:.2f}")

            if label_size.width() / label_size.height() > aspect_ratio:
                scaled_pixmap = pixmap.scaledToHeight(label_size.height(), Qt.SmoothTransformation)
            else:
                scaled_pixmap = pixmap.scaledToWidth(label_size.width(), Qt.SmoothTransformation)

            label.setPixmap(scaled_pixmap)
            label.setAlignment(Qt.AlignCenter)
        else:
            print(f"Label is not a QLabel: {type(label)}")

    def normalize_slice(self, slice_array):
        min_val = np.min(slice_array)
        max_val = np.max(slice_array)

        if min_val == max_val:
            return np.zeros_like(slice_array, dtype=np.uint8)
        else:
            return ((slice_array - min_val) / (max_val - min_val) * 255).astype(np.uint8)

    def previous_series(self):
        if self.current_series > 0:
            self.current_series -= 1
            self.load_current_series()

    def next_series(self):
        if self.current_series < len(self.series) - 1:
            self.current_series += 1
            self.load_current_series()

    def load_current_series(self):
        if self.series:
            _, _, dicom_files = self.series[self.current_series]
            self.load_series(dicom_files)
            self.update_series_info()
    
    def show_mpr(self):
        if self.image is not None:
            # Reset scroll bars to middle of each dimension
            size = self.image.GetSize()
            self.axialScrollBar.setValue(size[2] // 2)
            self.sagittalScrollBar.setValue(size[0] // 2)
            self.coronalScrollBar.setValue(size[1] // 2)
            self.update_views()

    def update_series_info(self):
        if not self.series:
            self.seriesInfoLabel.setText("No series loaded")
        else:
            series_id, description, modality, num_images, _ = self.series[self.current_series]
            self.seriesInfoLabel.setText(f"Series ID: {series_id}\nDescription: {description}\nModality: {modality}\nNumber of Images: {num_images}")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())
</code></pre>
<p>Issue:
The sagittal and coronal images are being stretched vertically, and I suspect it has something to do with how the spacing is handled or how the aspect ratio is calculated in the display_slice function.</p>
<p>What I've Tried:
Calculated the aspect ratio using pixel spacing and z-spacing.
Adjusted the scaling of the QPixmap based on the calculated aspect ratio.
Verified that spacing information is correctly extracted from the DICOM files.
Observations:
Axial images display correctly.
Sagittal and coronal images are stretched vertically.
Specific Question:
How can I correctly display the sagittal and coronal images without vertical stretching? Is there an issue with how I am calculating the aspect ratio or scaling the images?</p>
<p>Thank you for your help!</p>\nModality: {modality}\nNumber of Images: {num_images}")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

Issue: The sagittal and coronal images are being stretched vertically, and I suspect it has something to do with how the spacing is handled or how the aspect ratio is calculated in the display_slice function.

What I've Tried: Calculated the aspect ratio using pixel spacing and z-spacing. Adjusted the scaling of the QPixmap based on the calculated aspect ratio. Verified that spacing information is correctly extracted from the DICOM files. Observations: Axial images display correctly. Sagittal and coronal images are stretched vertically. Specific Question: How can I correctly display the sagittal and coronal images without vertical stretching? Is there an issue with how I am calculating the aspect ratio or scaling the images?

Thank you for your help!
<p>Question:</p>
<p>I am developing a DICOM viewer application using PyQt5 and SimpleITK. The viewer is supposed to display axial, sagittal, and coronal slices of a DICOM series. However, the sagittal and coronal images are stretched vertically, and I can't seem to fix it.</p>
<p>Code Overview</p>
<p>I use SimpleITK to read the DICOM series and extract the slices. The display_slice function adjusts the aspect ratio of the images based on pixel spacing and slice thickness. Below is the full code with the relevant parts highlighted:</p>
<pre><code>import sys
import os
import SimpleITK as sitk
import numpy as np
import pydicom
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QLabel, QMessageBox, QVBoxLayout, QSizePolicy, QListWidgetItem
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QImage, QPixmap
from PyQt5 import uic, QtGui

class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi('main.ui', self)
self.setup_connections()
self.image = None
self.series = []
self.current_series = 0
self.dicom_series = {}
self.current_index = 0
self.series_images = np.array([]) # Initialize as an empty NumPy array
self.pixel_spacing = None # Placeholder for pixel spacing
self.z_spacing = None # Placeholder for Z spacing
self.crosshair_x = 0
self.crosshair_y = 0
self.crosshair_z = 0

# Create QLabel widgets for displaying images
self.axialLabel = QLabel(self.axialContainer)
self.sagittalLabel = QLabel(self.sagittalContainer)
self.coronalLabel = QLabel(self.coronalContainer)

# Set size policies to expand and fill the containers
for label in (self.axialLabel, self.sagittalLabel, self.coronalLabel):
label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
label.setAlignment(Qt.AlignCenter)

# Create layouts for the containers
self.axialLayout = QVBoxLayout(self.axialContainer)
self.sagittalLayout = QVBoxLayout(self.sagittalContainer)
self.coronalLayout = QVBoxLayout(self.coronalContainer)

# Add labels to the layouts
self.axialLayout.addWidget(self.axialLabel)
self.sagittalLayout.addWidget(self.sagittalLabel)
self.coronalLayout.addWidget(self.coronalLabel)

def setup_connections(self):
self.loadButton.clicked.connect(self.load_dicom_folder)
self.prevButton.clicked.connect(self.previous_series)
self.nextButton.clicked.connect(self.next_series)
self.mprButton.clicked.connect(self.show_mpr)

self.axialScrollBar.valueChanged.connect(self.update_axial_view)
self.sagittalScrollBar.valueChanged.connect(self.update_sagittal_view)
self.coronalScrollBar.valueChanged.connect(self.update_coronal_view)
self.seriesList.currentRowChanged.connect(self.series_selected) # Connect the selection event

def load_dicom_folder(self):
folder = QFileDialog.getExistingDirectory(self, "Select DICOM folder")
if folder:
try:
# Recursively search for DICOM files in all subfolders
dicom_files = self.find_dicom_files(folder)

if not dicom_files:
raise RuntimeError("No DICOM files found in the selected folder or its subfolders")

# Group DICOM files by series
series_dict = self.group_dicom_by_series(dicom_files)

if not series_dict:
raise RuntimeError("No valid DICOM series found in the selected folder or its subfolders")

self.dicom_series = series_dict
self.seriesList.clear()

total_series = len(series_dict)
for idx, (series_uid, files) in enumerate(series_dict.items(), start=1):
ds = pydicom.dcmread(files[0])
description = ds.SeriesDescription if 'SeriesDescription' in ds else 'No Description'
item = QListWidgetItem(f"Series {idx}/{total_series} - {description} ({len(files)} images)")
item.setData(Qt.UserRole, series_uid)
self.seriesList.addItem(item)

if self.seriesList.count() > 0:
self.seriesList.setCurrentRow(0)
self.series_selected(0)
except Exception as e:
QMessageBox.critical(self, "Error", f"Error loading DICOM: {str(e)}")
print(f"Error loading DICOM: {str(e)}")

def find_dicom_files(self, root_dir):
dicom_files = []
for root, _, files in os.walk(root_dir):
for file in files:
if file.lower().endswith('.dcm'):
full_path = os.path.join(root, file)
if self.is_dicom_image(full_path):
dicom_files.append(full_path)
else:
print(f"Skipping non-image DICOM file: {full_path}")
return dicom_files

def is_dicom_image(self, file_path):
try:
ds = pydicom.dcmread(file_path)
return 'PixelData' in ds
except:
return False

def group_dicom_by_series(self, dicom_files):
series_dict = {}
for file in dicom_files:
try:
ds = pydicom.dcmread(file)
series_id = ds.SeriesInstanceUID
if series_id not in series_dict:
series_dict[series_id] = []
series_dict[series_id].append(file)
except Exception as e:
print(f"Warning: Could not read file {file} with pydicom. Error: {str(e)}")
return series_dict

def series_selected(self, index):
series_uid = self.seriesList.item(index).data(Qt.UserRole)
self.load_series(self.dicom_series[series_uid])

def load_series(self, dicom_files):
sorted_files = self.sort_dicom_files(dicom_files)

if not sorted_files:
raise RuntimeError("No valid DICOM files found in the series")

# Read all slices using SimpleITK
reader = sitk.ImageSeriesReader()
reader.SetFileNames(sorted_files)
self.image = reader.Execute()
# Print image data
print(f"Image Size: {self.image.GetSize()}")
print(f"Image Spacing: {self.image.GetSpacing()}")
print(f"Image Direction: {self.image.GetDirection()}")
print(f"Image Origin: {self.image.GetOrigin()}")

# Get spacing, size, and direction
spacing = self.image.GetSpacing()
self.pixel_spacing = spacing[:2]
self.z_spacing = spacing[2]

# Extract slice thickness from the first DICOM file
first_dicom = pydicom.dcmread(sorted_files[0])
self.slice_thickness = float(first_dicom.SliceThickness)

print(f"Pixel Spacing: {self.pixel_spacing}, Z Spacing: {self.z_spacing}, Slice Thickness: {self.slice_thickness}")

self.direction = self.image.GetDirection()
self.size = self.image.GetSize()

# Calculate slice spacing
self.slice_spacing = self.calculate_slice_spacing(sorted_files)
print(f"Calculated Slice Spacing: {self.slice_spacing}")

# Store the sorted files as an attribute
self.sorted_files = sorted_files

# Set up scrollbars
self.axialScrollBar.setMaximum(self.size[2] - 1)
self.sagittalScrollBar.setMaximum(self.size[0] - 1)
self.coronalScrollBar.setMaximum(self.size[1] - 1)

self.update_views()

def calculate_slice_spacing(self, dicom_files):
positions = []
orientations = []
for file in dicom_files[:2]: # We only need the first two files
ds = pydicom.dcmread(file)
pos = tuple(map(float, ds.ImagePositionPatient)) # Convert to tuple of floats
ori = list(map(float, ds.ImageOrientationPatient)) # Convert to list of floats
positions.append(np.array(pos))
orientations.append(np.array(ori))

# Calculate the normal vector to the image plane
row_cosine = orientations[0][:3]
col_cosine = orientations[0][3:]
normal_vector = np.cross(row_cosine, col_cosine)

# Calculate the distance between the first two slices
spacing = np.abs(np.dot(positions[1] - positions[0], normal_vector))
return spacing

def sort_dicom_files(self, dicom_files):
valid_files = []
for file in dicom_files:
try:
ds = pydicom.dcmread(file)
position = tuple(map(float, ds.ImagePositionPatient)) # Convert to tuple of floats
valid_files.append((file, position))
except Exception as e:
print(f"Warning: Could not read file {file}. Error: {str(e)}")
continue

# Sort based on the z-coordinate (3rd element) of the ImagePositionPatient
return [file for file, _ in sorted(valid_files, key=lambda x: x[1][2])]

def update_views(self):
if self.image is None:
print("No image loaded")
return
self.update_axial_view(self.axialScrollBar.value())
self.update_sagittal_view(self.sagittalScrollBar.value())
self.update_coronal_view(self.coronalScrollBar.value())

def update_axial_view(self, slice_index):
if self.image is not None:
axial_slice = sitk.GetArrayViewFromImage(self.image)[slice_index, :, :]
self.display_slice(axial_slice, self.axialLabel, 'axial')

def update_sagittal_view(self, slice_index):
if self.image is not None:
sagittal_slice = sitk.GetArrayViewFromImage(self.image)[:, :, slice_index]
sagittal_slice = np.flipud(sagittal_slice)
self.display_slice(sagittal_slice, self.sagittalLabel, 'sagittal')

def update_coronal_view(self, slice_index):
if self.image is not None:
coronal_slice = sitk.GetArrayViewFromImage(self.image)[:, slice_index, :]
coronal_slice = np.flipud(coronal_slice)
self.display_slice(coronal_slice, self.coronalLabel, 'coronal')

def display_slice(self, slice_array, label, plane):
if isinstance(label, QLabel):
print(f"Displaying {plane} slice. Shape: {slice_array.shape}")
normalized_slice = self.normalize_slice(slice_array)
height, width = normalized_slice.shape

qimage = QImage(normalized_slice.data, width, height, width, QImage.Format_Grayscale8)
pixmap = QPixmap.fromImage(qimage)

label_size = label.size()
print(f"{plane} label size: {label_size.width()} x {label_size.height()}")

if plane == 'axial':
x_scale = self.pixel_spacing[0]
y_scale = self.pixel_spacing[1]
elif plane == 'sagittal':
x_scale = self.pixel_spacing[0]
y_scale = self.z_spacing
elif plane == 'coronal':
x_scale = self.z_spacing
y_scale = self.pixel_spacing[1]
else:
x_scale = y_scale = 1.0

aspect_ratio = (width * x_scale) / (height * y_scale)
print(f"{plane} - Aspect ratio: {aspect_ratio:.2f}")

if label_size.width() / label_size.height() > aspect_ratio:
scaled_pixmap = pixmap.scaledToHeight(label_size.height(), Qt.SmoothTransformation)
else:
scaled_pixmap = pixmap.scaledToWidth(label_size.width(), Qt.SmoothTransformation)

label.setPixmap(scaled_pixmap)
label.setAlignment(Qt.AlignCenter)
else:
print(f"Label is not a QLabel: {type(label)}")

def normalize_slice(self, slice_array):
min_val = np.min(slice_array)
max_val = np.max(slice_array)

if min_val == max_val:
return np.zeros_like(slice_array, dtype=np.uint8)
else:
return ((slice_array - min_val) / (max_val - min_val) * 255).astype(np.uint8)

def previous_series(self):
if self.current_series > 0:
self.current_series -= 1
self.load_current_series()

def next_series(self):
if self.current_series < len(self.series) - 1:
self.current_series += 1
self.load_current_series()

def load_current_series(self):
if self.series:
_, _, dicom_files = self.series[self.current_series]
self.load_series(dicom_files)
self.update_series_info()

def show_mpr(self):
if self.image is not None:
# Reset scroll bars to middle of each dimension
size = self.image.GetSize()
self.axialScrollBar.setValue(size[2] // 2)
self.sagittalScrollBar.setValue(size[0] // 2)
self.coronalScrollBar.setValue(size[1] // 2)
self.update_views()

def update_series_info(self):
if not self.series:
self.seriesInfoLabel.setText("No series loaded")
else:
series_id, description, modality, num_images, _ = self.series[self.current_series]
self.seriesInfoLabel.setText(f"Series ID: {series_id}\nDescription: {description}\nModality: {modality}\nNumber of Images: {num_images}")

if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
</code></pre>
<p>Issue:
The sagittal and coronal images are being stretched vertically, and I suspect it has something to do with how the spacing is handled or how the aspect ratio is calculated in the display_slice function.</p>
<p>What I've Tried:
Calculated the aspect ratio using pixel spacing and z-spacing.
Adjusted the scaling of the QPixmap based on the calculated aspect ratio.
Verified that spacing information is correctly extracted from the DICOM files.
Observations:
Axial images display correctly.
Sagittal and coronal images are stretched vertically.
Specific Question:
How can I correctly display the sagittal and coronal images without vertical stretching? Is there an issue with how I am calculating the aspect ratio or scaling the images?</p>
<p>Thank you for your help!</p>
 

Latest posts

Online statistics

Members online
0
Guests online
4
Total visitors
4
Ads by Eonads
Top