Skip to content

Commit

Permalink
Merge pull request #8 from LuisOlivaresJ/uniformity
Browse files Browse the repository at this point in the history
Uniformity
  • Loading branch information
LuisOlivaresJ authored Dec 11, 2023
2 parents 6c28a8b + 3a5608e commit 055bdd7
Show file tree
Hide file tree
Showing 31 changed files with 1,702 additions and 321 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Luis Alfonso Olivares Jimenez

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
73 changes: 70 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,47 @@
![Logo](/docs/images/logo.png)

# Pyportal

Pyportal provides [TG-307](https://doi.org/10.1002/mp.16536) quality assurance (QA) tools for routine QA of EPID systems.
Pyportal is a graphical user interface for performing [TG-307](https://doi.org/10.1002/mp.16536) quality assurance (QA) tests for routine QA of (R) Varian EPID systems.
The program is still in early development and may contain bugs.

## Features

* SQL database storage for constancy analysis.
* One-click fast test assessment.

## Recommended tests to ensure the EPID will function accurately as a dosimeter.

### EPID positioning
* EPID positioning
* EPID positioning with gantry rotation
* Linearity with dose
* Uniformity
* Reproducibility (In progress...)

## Requirements

The program depends on the following:
* Python (3.10+)
* PySide6 (6.6+)
* Pylinac (3.16+)
* Pandas (2.1+)

## Installation

1. Download the source code from the repository.
2. (Optional but highly recommended) Create a virtual environment for Pyportal to avoid dependency conflicts
with existing python libraries.
3. Install all the required dependencies using `pip` (e.g `pip install pyside6`).

## Usage

To run the application simply navigate to the source code directory and run the following command:\
`python GUI.py`

Click on `open` button and slect the folder/directory with only the images or DICOM files (with .dcm extension) to perform one test.
> **_NOTE:_** If it is the fist time the program is running, it will ask for a reference image. Such image should be acquired with a 10 cm x 10 cm field size and a source to detector distance of 100 cm after EPID calibration.
## EPID positioning

Tolerance: Vendor specification or ≤2 mm

Expand All @@ -16,19 +53,49 @@ Determine the reproducibility of EPID positioning at gantry zero by three EPID d

Repeat the 10 cm × 10 cm image at 45° gantry angle increments. Measure the center of each field in X and Y. Measure the difference relative to gantry zero position. Convert to mm using the known pixel dimension.

### Results

![Positioning](/docs/images/Positioning.PNG)

From the columns

* SID: Source to detector distance.
* G°: Gantry angle.
* x (or y): Distance from beam center to the center of the panel in x (y) direction.
* dx (or dy): Difference in x (y) with respect the first loaded data in the dataset (first row in the table).

## Linearity with dose

Tolerance: Vendor specification or within 2% (5% for MU < 5)

First determine that the Linac MU linearity is within TG-142 tolerance, then irradiate a 10 cm × 10 cm field with varying MU settings from 2 to 500. Measure the IPV at the center of each field. Determine the IPV per MU relative to 100 MU.

### Results

![Positioning](/docs/images/Linearity_results.png)

* MU: Monitor units used for irradiation.
* CU: Mean Calibration Unit (ROI area of 10% respect to entire image, at the center).
* CU/MU: CU per MU.
* Variation: Percentage difference between CU/MU with the acquired using 100 MU.

## Uniformity

Tolerance: Uniformity following panel calibration ±2% (SD as a percentage of the mean)

After flood-field calibration, irradiate the entire EPID sensitive area with 100 MU at the same SID. Measure the mean and SD of the pixel values within a large region that excludes the detector edges and penumbra (2 cm inside the panel edges and 0.5 cm inside field edges). Calculate the SD as a percentage of the mean.

## Reproducibility
### Results

![Positioning](/docs/images/Uniformity.png)

* Mean: Mean pixel over the image. 7.5% of each border is excluded (3 cm and 2.25 cm for a 40 cm x 30 cm detector size).
* STD: Standar deviation.
* Uniformity: STD as a percentage of the mean.
* Num. pixels: Number of pixels in the ROI.

## Reproducibility (in progress...)

Tolerance: Dosimetric reproducibility of ±2%

EPID reproducibility is important and needs to be stable over months and this should be confirmed over the first months following installation. The EPID response reproducibility at the center of the 10 cm × 10 cm field needs to be sampled weekly for at least 1 month period and be stable (adjusted for linac output). The frequency of EPID dose response recalibrations should be tracked over time to ensure EPID panel has consistent performance.
Binary file added docs/images/Linearity_results.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/Positioning.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/PositioningResults.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/Uniformity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/logo.odg
Binary file not shown.
Binary file added docs/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions src/QPortal/GUI.py → src/Pyportal/GUI.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-

"""This module provides QPortal application."""
"""This module provides Pyportal application access point."""

import sys
from PySide6.QtWidgets import QApplication
Expand All @@ -9,12 +9,12 @@
from views import Window


"""QPortal main function."""
"""Pyportal main function."""

# Create the application
app = QApplication(sys.argv)
# Connect to the database before creating any window
if not createConnection("positions.sqlite"):
if not createConnection("database.sqlite"):
sys.exit(1)

# Create the main window if the connection succeeded
Expand Down
3 changes: 3 additions & 0 deletions src/Pyportal/__init__py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""This module provides the Pyportal package."""

__version__ = "0.1.0"
File renamed without changes.
File renamed without changes.
3 changes: 0 additions & 3 deletions src/QPortal/To do.txt → src/Pyportal/_for_testing/To do.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
---Modificar las acciones de los botones de QDialog Results

Cómo agregar segunda tabla para almacenar configuraciones
actualizar grafica según tolerancia

Si en primer inicio no se carga el archivo de referencia, se produce un error. database.py xy = getXY()
File renamed without changes.
File renamed without changes.
File renamed without changes.
80 changes: 76 additions & 4 deletions src/QPortal/database.py → src/Pyportal/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ def createConnection(databaseName):
print("SQL Connection Successfully Opened!")
_createPositionsTable()
_createUserToleranceTable()
_createLinearityTable()
_createUniformityTable()
_positions_is_empty()
_tolerances_is_empty()
#_linearity_is_empty()
return True

def _createPositionsTable():
Expand All @@ -57,7 +60,7 @@ def _createPositionsTable():
)

def _createUserToleranceTable():
"""Used to create user settings like tolerances for test."""
"""Table used to create save linearity constancy."""
createTableQuery = QSqlQuery()
createTableQuery.exec(
"""
Expand All @@ -71,10 +74,42 @@ def _createUserToleranceTable():
)
createTableQuery.finish()

def _createLinearityTable():
"""Used to create user settings like tolerances for test."""
createTableQuery = QSqlQuery()
createTableQuery.exec(
"""
CREATE TABLE IF NOT EXISTS linearity (
date VARCHAR(25) NOT NULL,
mu REAL NOT NULL,
cu REAL NOT NULL,
cu_mu REAL NOT NULL,
variation REAL NOT NULL
)
"""
)
createTableQuery.finish()

def _createUniformityTable():
"""Used to create uniformity."""
createTableQuery = QSqlQuery()
createTableQuery.exec(
"""
CREATE TABLE IF NOT EXISTS uniformity (
Date VARCHAR(25) NOT NULL,
Mean REAL NOT NULL,
STD REAL NOT NULL,
Uniformity REAL NOT NULL,
Num_pixels REAL NOT NULL
)
"""
)
createTableQuery.finish()

def _positions_is_empty():
"""
If there are no fields in the record database, opens a QDialog window to ask for a file that is going to be
used to get the reference portal position, saving it as the first row. Otherwise, returns
used to set the reference portal position, saving it as the first row. Otherwise, returns
"""
isEmptyQuery = QSqlQuery()
isEmptyQuery.exec("SELECT Date, SID, Gantry, x, y, dx, dy FROM positions")
Expand Down Expand Up @@ -116,6 +151,23 @@ def _positions_is_empty():

else:
return

def _linearity_is_empty():
"""
If there are no fields in the record database, returns True
"""
isEmptyQuery = QSqlQuery()
isEmptyQuery.exec("SELECT * FROM linearity")
return isEmptyQuery.first()

def _uniformity_is_empty():
"""
If there are no fields in the record database, returns True
"""
isEmptyQuery = QSqlQuery()
isEmptyQuery.exec("SELECT * FROM uniformity")
return isEmptyQuery.first()

def _tolerances_is_empty():
"""
Set default tolerances when app is first used.
Expand Down Expand Up @@ -218,10 +270,30 @@ def load_tolerances():

def get_as_pd_dataframe():
"""Get database as pandas DataFrame instance."""
get_db_con = sqlite3.connect("positions.sqlite")
get_db_con = sqlite3.connect("database.sqlite")
df = pandas.read_sql_query("SELECT * FROM positions", get_db_con)
get_db_con.close()


df["Date"] = pandas.to_datetime(df["Date"], format = "ISO8601")
df["Date"] = pandas.to_datetime(df["Date"], format="%Y-%m-%d")
return df

def get_linearity_as_pd_dataframe():
"""Get linearity database table as pandas DataFrame instance."""
get_db_con = sqlite3.connect("database.sqlite")
df = pandas.read_sql_query("SELECT * FROM linearity", get_db_con)
get_db_con.close()


df["date"] = pandas.to_datetime(df["date"], format="%Y-%m-%d")
return df

def get_uniformity_as_pd_dataframe():
"""Get uniformity database table as pandas DataFrame instance."""
get_db_con = sqlite3.connect("database.sqlite")
df = pandas.read_sql_query("SELECT * FROM uniformity", get_db_con)
get_db_con.close()


df["Date"] = pandas.to_datetime(df["Date"], format="%Y-%m-%d")
return df
File renamed without changes
File renamed without changes
Loading

0 comments on commit 055bdd7

Please sign in to comment.