Film dosimetry#

Prerequisites for the tiff image#

  • A minimum distance of 5 mm between films.

  • Rectangular films aligned to the sides of the scanner.

  • The tiff image has to be 16-bit per channel, RGB.

  • For lateral response artifact:

    • The image must have the full width of the scanner.

    • The isocenter (on films) has to be in the center of the scanner.

This is a common example for film calibration.

example scanned films

Optional

Optical filters are added as a quality control test for error detection. Useful for example if auto correction for color or brightness has been applied unconsciously. Also, as a way to measure inter-scan reproducibility of the lamp/detector system.

Usage#

Example 1#

Start importing the load funtion and LUT class.

from Dosepy.image import load
from Dosepy.calibration import LUT

Read the tiff file for film calibration

# Change this to your file path
path_to_file = "/home/luis/Dosepy/tests/unit/fixtures/CAL20241106_001.tif"

cal_image = load(path_to_file)

Create a LUT object with the image as argument. This will store all the data needed for calibration.

cal = LUT(cal_image)

Dosepy automatically detects each object in the image.

Create central rois on each film and show them.

cal.set_central_rois()

cal.plot_rois()
<Axes: >
_images/7678da5e30aefe4c22753eaae9aa1f948436859e3ab1ae75894a9a1b62544514.png

Change the ROI size to 16 x 8 mm, (if needed)

cal.set_central_rois(size = (16, 8))
cal.plot_rois()
<Axes: >
_images/d3b9da0a868533d1fe9f56de2e8637b9864e2140a83f8f2bb8151fee644c1907.png

To set-up the calibration or cal object we need to perform the following steps:

  • Set the imparted doses to the films [Gy].

  • Compute the lut.

  • Optional, apply a filer of size of 3 pixels for noise reduction.

cal.set_doses([0, 0.5, 1, 2, 4, 6, 8, 10])

cal.compute_central_lut(filter=3)

Show results#

Plot the calibration curve, using the red channel and rational function of the form

\[ y = -c + \frac{b}{x-a} \]

where $\( x = \frac{I}{I_0} \)$ represents the film response as the quotient of intensities of unexposed and exposed films, respectively.

cal.plot_fit(fit="rational", channel="red")
_images/1e0a6660a058d8b4cbfe7888d23b75e1b49f96005aba9b8eb1f98cadc9fa713f.png

Plot dose uncertainty with plot_dose_fit_unertainty method

cal.plot_dose_fit_uncertainty(fit="rational", channel="red")
_images/c5c5d14a74b4cff0aabc2bad12a1479b4aa910321a719c88615ba69b3c95eb18.png

Example 2#

Lateral response#

Lateral response artifact is handled by computing a calibration curve at every milimeter in lateral direction.

If film irradiation has been done in a x-ray beam from a linear acelerator, we need to account for the “beam horns”. To do that, we need to load a relative dose profile of the field used. In this example, a 6 MV beam of size 150 mm x 150 mm and 5 mm deep was used for film calibration.

The file must have an array of two columns:

  • First column should be for positions, given in mm, with 0 being at beam center.

  • Second column should be the relative values [%], normalized to 100 at center.

cal = LUT(cal_image)

cal.set_central_rois(size = (180, 8))
cal.set_doses([0, 0.5, 1, 2, 4, 6, 8, 10])
cal.plot_rois()

cal.set_beam_profile("/home/luis/Dosepy/tests/unit/fixtures/CAL/BeamProfile.csv")  # Change this to your path
_images/b43a5330a83b2ba9ba31a1e1e0c2e7b9e50847378fcbb7fe482ace853d8655dd.png

Next we need to call the compute_lateral_lut() (instead of compute_central_lut()). We’ll use a filter of size 3 pixels.

cal.compute_lateral_lut(filter=3)

Optional, show green intensity values on lateral direction. The zero position is located in the center of the image, i.e. the center of the scanner if the full width has been acquired.

cal.plot_lateral_response(channel="green")
_images/726a90384ebb411645e12c9a6ff5451959a7ef875720e799708bb8d3db0c02e5.png

We can use the green channel and a polynomial fit function of the form

\[ y = ax + bx^n \]

To see the calibration curve at say, position 5 mm from the center, we can use the plot_fit() method.

cal.plot_fit(
    fit="polynomial",
    channel="green",
    position=5,
    )
_images/3ff96d4ce38d1990d06930d0f59b613da2fbcd9cf29e76a98084967c9c4ffc70.png

Show uncertainty at 5 mm.

cal.plot_dose_fit_uncertainty(
    position = 5,
    fit="polynomial",
    channel="green"
    )
_images/5438d1d3e92689b0242bc6e4467a338a21484290905cfc0a3e3f15b2549e2aaa.png

Example 3#

Load multiple files for noise reduction#

Multiple files can be averaged. As a prerequisite, the file name has to be the same, ignoring the last three characters.

from Dosepy.image import load_multiples
files = [
    "/media/luis/TOMO/Tiff Films/EBT4/6NOV24_CAL/CAL20241106_001.tif",
    "/media/luis/TOMO/Tiff Films/EBT4/6NOV24_CAL/CAL20241106_002.tif",
    "/media/luis/TOMO/Tiff Films/EBT4/6NOV24_CAL/CAL20241106_003.tif",
]

img_m = load_multiples(files)
img_m.plot()
_images/c4b8fca4dc423138f0d79a6c3c3b511c8dfb2cc4713ca6d9fc22a35b6d16f7af.png
<Axes: >
cal = LUT(img_m)

cal.set_central_rois(size = (180, 8))
cal.set_doses([0, 0.5, 1, 2, 4, 6, 8, 10])

cal.plot_rois()

cal.set_beam_profile("/home/luis/Dosepy/tests/unit/fixtures/CAL/BeamProfile.csv")

cal.compute_lateral_lut(filter = 5)
_images/b3b26ea0c95293935f4045ca5e034f4cdca626dd67ece1d6f40d086c62aecd82.png

Plot calibration curve, for example, at 50 mm.#

cal.plot_fit(
    fit="polynomial",
    channel="green",
    position=50,
    )
_images/25155fb88b7efe1289a46d9e5435d4e6cd1a160094d132bd4aafa128c2c8c65d.png

Show uncertainty at different lateral positions#

import matplotlib.pyplot as plt

colors = ["b", "g", "r", "c", "m", "y", "k"]
positions = [-50, -20, -10, 0, 10, 20, 50]  # In milimeters

fig, ax = plt.subplots()

for c, p in zip(colors, positions):
    cal.plot_dose_fit_uncertainty(
        position = p,
        fit="polynomial",
        channel="green",
        ax=ax,
        label=f"{p} mm",
        markeredgecolor=c,
        markerfacecolor=c,
        )

ax.legend()
<matplotlib.legend.Legend at 0x7476a9f60650>
_images/805a6e00496b98ad6dbe6cb0bbcd90278c7f8270536f0fd9aa4cf10caab5400d.png
# Clear memory
del img_m, cal_image

Film to dose#

The next image is going to be used for verification. It was irradiated with 5.0 Gy (a dose value not used for calibration).

Note

The image must also have an unirradiated film as a reference for 0 Gy.

# Load the images

files = [
    "/media/luis/TOMO/Tiff Films/EBT4/Verif 15x15/Ver_050dpi20241106_001.tif",
    "/media/luis/TOMO/Tiff Films/EBT4/Verif 15x15/Ver_050dpi20241106_002.tif",
    "/media/luis/TOMO/Tiff Films/EBT4/Verif 15x15/Ver_050dpi20241106_003.tif",
]

qa_image = load_multiples(files)
qa_image.plot()
_images/ca01e4e14e0886b7011abd771568526a3781a915a88995ba0d571eb86ddcafd8.png
<Axes: >

(Optional) Quality control using optical filters#

Quality control test to check reproducibility of the scanner using mean intensity of optical filters using red channel. The test pass if the intensity of the optical filters in the image is equal to the intensity of the optical filters in the image used for calibration, within a relative tolerance.

from Dosepy.calibration import passed_QC

passed_QC(img=qa_image, lut=cal, rtol=0.01)  # Relative tolerance of 1%
Intensity of optical filter(s) in calibration.
([np.float64(9345.198660714286), np.float64(13275.963083604778), np.float64(20718.336886993602)],)
Intensity of optical filter(s) in the image.
[np.float64(9429.098324022347), np.float64(13383.234273318873), np.float64(20924.68770053476)]
True

Import the tiff to dose manager. Use “GP” for green channel and a polynomial fit function.

from Dosepy.tiff2dose import Tiff2DoseM

t2d = Tiff2DoseM()

dose = t2d.get_dose(
    img=qa_image,
    format="GP",
    lut=cal
    )
/home/luis/miniconda3/envs/dp/lib/python3.11/site-packages/Dosepy/tools/functions.py:19: RuntimeWarning: invalid value encountered in power
  return a*x + b*x**n

Plot the dose distribution

import matplotlib.pyplot as plt
import numpy as np


fig, ax = plt.subplots()
axeI = ax.imshow(
    dose.array,
    cmap="nipy_spectral",
    vmin=4.5,
    vmax=5.5)

fig.colorbar(axeI, ax=ax)
<matplotlib.colorbar.Colorbar at 0x7476a80f1c10>
_images/3387e4e374a0e62e34093e61bf4dafa7eb5cbce9da5ce28d0c51742f3f09f338.png

Save the dose distribution as a tif file (in cGy). Useful for further analysis using ImageJ.

#dose.save_as_tif("dose_in_tif_file")

Dose at center#

dose_at_center = dose.array[110:140, 450:480]
np.mean(dose_at_center)
np.float64(5.12134267080905)
# Show the ROI

roi = plt.Rectangle((450, 110), 30, 30, fill=False)

ax.add_patch(roi)

fig
_images/a1f42cf568189f0db2b94b89b511960f42b5a528134a04b98a9b717061016347.png

A difference of 0.12 Gy (2.4%) is obtainded, with respect to the known dose of 5.0 Gy. As previously showed, it is within dose uncertainty.