Skip to content

Commit

Permalink
Windowing histogram (#298)
Browse files Browse the repository at this point in the history
* Add files via upload

* Revert "Add files via upload"

This reverts commit 6b39071.

* 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 <danielhorton4001>
  • Loading branch information
danielhorton4001 authored Oct 21, 2023
1 parent b11d334 commit ed39a95
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 24 deletions.
4 changes: 4 additions & 0 deletions src/Controller/ActionHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/View/mainpage/DicomView.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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):
"""
Expand Down
2 changes: 1 addition & 1 deletion src/View/mainpage/MainPage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 58 additions & 23 deletions src/View/mainpage/WindowingSlider.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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
"""

Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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):
"""
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit ed39a95

Please sign in to comment.