From ed39a95d8926b28f7c0404c784b8cb2dae60ddf4 Mon Sep 17 00:00:00 2001 From: danielhorton4001 <115335774+danielhorton4001@users.noreply.github.com> Date: Sat, 21 Oct 2023 12:09:24 +1100 Subject: [PATCH] Windowing histogram (#298) * Add files via upload * Revert "Add files via upload" This reverts commit 6b39071875298e55ba970e34cbbdf73867e72295. * Added histogram. Histogram updates to selected slice. In 3D view, it syncs to the Axial view. The seperate views cannot support their own windowing sliders until the way windowing is handled is changed to accomodate individual slice windowing. --------- Co-authored-by: Daniel Horton --- src/Controller/ActionHandler.py | 4 ++ src/View/mainpage/DicomView.py | 3 ++ src/View/mainpage/MainPage.py | 2 +- src/View/mainpage/WindowingSlider.py | 81 ++++++++++++++++++++-------- 4 files changed, 66 insertions(+), 24 deletions(-) diff --git a/src/Controller/ActionHandler.py b/src/Controller/ActionHandler.py index 4cb22c3e..f5971146 100644 --- a/src/Controller/ActionHandler.py +++ b/src/Controller/ActionHandler.py @@ -380,6 +380,8 @@ def one_view_handler(self): self.__main_page.dicom_single_view.update_view() self.__main_page.dicom_single_view_layout.addWidget( self.__main_page.windowing_slider, 0, 0) + self.__main_page.windowing_slider.set_dicom_view( + self.__main_page.dicom_single_view) if hasattr(self.__main_page, 'image_fusion_view'): self.has_image_registration_four = False @@ -398,6 +400,8 @@ def four_views_handler(self): self.__main_page.dicom_axial_view.update_view() self.__main_page.dicom_four_views_slider_layout.addWidget( self.__main_page.windowing_slider, 0, 0) + self.__main_page.windowing_slider.set_dicom_view( + self.__main_page.dicom_axial_view) if hasattr(self.__main_page, 'image_fusion_view'): self.has_image_registration_four = True diff --git a/src/View/mainpage/DicomView.py b/src/View/mainpage/DicomView.py index c5d421ce..0c8b3d01 100644 --- a/src/View/mainpage/DicomView.py +++ b/src/View/mainpage/DicomView.py @@ -34,6 +34,7 @@ def __init__(self, roi_color=None, iso_color=None, cut_line_color=None): self.vertical_view = None self.cut_lines_color = cut_line_color self.dicom_view_layout = QtWidgets.QHBoxLayout() + self.windowing_slider = None # Create components self.slider = QtWidgets.QSlider(QtCore.Qt.Vertical) @@ -108,6 +109,8 @@ def value_changed(self): if self.horizontal_view is not None and self.vertical_view is not None: self.horizontal_view.update_view() self.vertical_view.update_view() + if self.windowing_slider is not None: + self.windowing_slider.update_density_histogram() def update_view(self, zoom_change=False): """ diff --git a/src/View/mainpage/MainPage.py b/src/View/mainpage/MainPage.py index 68cd49df..9b4c4a21 100644 --- a/src/View/mainpage/MainPage.py +++ b/src/View/mainpage/MainPage.py @@ -165,7 +165,7 @@ def setup_central_widget(self): roi_color=roi_color_dict, iso_color=iso_color_dict, cut_line_color=QtGui.QColor(0, 0, 255)) self.three_dimension_view = DicomView3D() - self.windowing_slider = WindowingSlider() + self.windowing_slider = WindowingSlider(self.dicom_single_view) # Rescale the size of the scenes inside the 3-slice views self.dicom_axial_view.zoom = INITIAL_FOUR_VIEW_ZOOM diff --git a/src/View/mainpage/WindowingSlider.py b/src/View/mainpage/WindowingSlider.py index 0e536d6b..5bad4c03 100644 --- a/src/View/mainpage/WindowingSlider.py +++ b/src/View/mainpage/WindowingSlider.py @@ -1,23 +1,11 @@ from src.Model.PatientDictContainer import PatientDictContainer +from src.Model.Windowing import windowing_model_direct, set_windowing_slider from PySide6.QtWidgets import QWidget, QLabel, QApplication, QGridLayout, QSizePolicy from PySide6.QtCharts import QChart, QChartView, QLineSeries, QHorizontalBarSeries, QBarSet from PySide6.QtGui import QPixmap, QPainter from PySide6 import QtCore from math import ceil - -from src.Model.Windowing import windowing_model_direct, set_windowing_slider - -import random - - -def gen_random_histogram(): - """ - Temporary function to populate histogram - """ - d = [0.5] - for n in range(0, 599): - d.append((d[-1] + random.random()) * random.random()) - return d +import numpy as np class WindowingSlider(QWidget): @@ -38,9 +26,10 @@ class WindowingSlider(QWidget): SINGLETON = None - def __init__(self, width=50): + def __init__(self, dicom_view, width=50): """ Initialise the slider + :param dicom_view: A DICOM View :param width: the fixed width of the widget """ @@ -50,6 +39,10 @@ def __init__(self, width=50): WindowingSlider.SINGLETON = self set_windowing_slider(self) + self.set_dicom_view(dicom_view) + patient_dict_container = PatientDictContainer() + self.pixel_values = patient_dict_container.get("pixel_values") + # Manage size of whole widget self.fixed_width = width self.size_policy = QSizePolicy() @@ -59,18 +52,18 @@ def __init__(self, width=50): self.setFixedWidth(self.fixed_width) # Histogram + self.densities = None self.histogram_view = HistogramChart(self) self.histogram_view.windowing_slider = self self.histogram = QChart() self.histogram_view.setChart(self.histogram) self.histogram.setPlotArea( - QtCore.QRectF(0, 0, self.fixed_width, self.height())) + QtCore.QRectF(0, 0, self.fixed_width-10, self.height())) self.histogram_view.resize(self.fixed_width, self.height()) self.histogram_view.setMouseTracking(True) self.histogram_view.viewport().installEventFilter(self) - self.density = QLineSeries() self.slider_density = int(self.height() / 3) # Create sliders @@ -85,6 +78,7 @@ def __init__(self, width=50): self.sliders.append(self.slider_bars[-1]) self.histogram.addSeries(self.sliders) self.histogram.zoom(2) # at default zoom bars don't fill the chart + self.histogram.addSeries(self.density) # Layout self.layout = QGridLayout() @@ -96,8 +90,6 @@ def __init__(self, width=50): self.bottom = 0 # Get the values for window and level from the dict - patient_dict_container = PatientDictContainer() - print(patient_dict_container.get("dict_windowing")) # testing windowing_limits = patient_dict_container.get("dict_windowing")['Normal'] # Set window and level to the new values @@ -114,8 +106,9 @@ def __init__(self, width=50): self.drag_upper_offset = 0 self.drag_lower_offset = 0 - # Test - self.set_density_histogram(gen_random_histogram()) + # Generate the histogram + self.initialise_density_histogram() + self.update_density_histogram() def set_action_handler(self, action_handler): """ @@ -125,9 +118,14 @@ def set_action_handler(self, action_handler): """ self.action_handler = action_handler + def set_dicom_view(self, dicom_view): + self.dicom_view = dicom_view + self.slice_slider = dicom_view.slider + self.dicom_view.windowing_slider = self + def resizeEvent(self, event): self.histogram.setPlotArea( - QtCore.QRectF(0, 0, self.fixed_width, event.size().height())) + QtCore.QRectF(0, 0, self.fixed_width-10, event.size().height())) def height_to_index(self, pos): """ @@ -179,12 +177,49 @@ def set_density_histogram(self, densities): Index 0 is for the lowest density. :param densities: a list of values from 0-1 """ - + self.histogram.removeSeries(self.density) + self.density = QLineSeries() self.density.setColor("grey") for i in range(0, len(densities)): self.density.append(2-densities[i], i) self.histogram.addSeries(self.density) + def update_density_histogram(self): + """ + Updates the histogram display. + """ + slice_index = self.slice_slider.value() + histogram_values = self.densities[slice_index] + self.set_density_histogram(histogram_values) + + def initialise_density_histogram(self): + """ + Initialises the density histograms. + """ + slices = len(self.pixel_values) + self.densities = [[]] * slices + for s in range(slices): + self.densities[s] = [0] * WindowingSlider.MAX_PIXEL_VALUE + + # Count each pixel value + pixels_flat = np.array(self.pixel_values[s]).flat + for pixel in pixels_flat: + # Clamp value between 0 and MAX_PIXEL_VALUE + p = min(pixel, WindowingSlider.MAX_PIXEL_VALUE) + p = max(p, 0) + p = round(p) + self.densities[s][p] += 1 + max_value = max(self.densities[s]) + min_value = min(self.densities[s]) + avg_value = np.average(self.densities[s]) + + # Normalise values + for i in range(WindowingSlider.MAX_PIXEL_VALUE): + self.densities[s][i] = self.densities[s][i] / max_value * 10000 + self.densities[s][i] = min(self.densities[s][i], 1) + self.densities[s][i] = max(self.densities[s][i], 0) + self.update_density_histogram() + def update_bar(self, index, top_bar=True): """ Moves the chosen bar to the provided index.