| Title: | PET Voxels and Radiomics from 'OsiriX' Regions of Interest |
|---|---|
| Description: | Reads regions of interest (ROIs) drawn in the 'OsiriX' medical image viewer and exported as XML files, and recovers the exact voxels of each ROI on the positron emission tomography (PET) series stored as Digital Imaging and Communications in Medicine (DICOM) files. The voxels are found by matching the voxel values stored in the XML file to the standardised uptake values (SUV) computed from the DICOM header. The resulting voxel table (positions in pixels and millimetres, and SUV) can be passed to the 'Python' library 'PyRadiomics' (van Griethuysen et al. (2017) <doi:10.1158/0008-5472.CAN-17-0339>) through 'reticulate' to compute radiomics features. |
| Authors: | Tao Lan [aut, cre, cph], Eric Wolsztynski [aut] |
| Maintainer: | Tao Lan <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 0.1.0 |
| Built: | 2026-11-07 17:47:26 UTC |
| Source: | https://github.com/cran/osirad |
Finds the position and the SUV of every voxel inside the regions of
interest (ROIs) of an 'OsiriX' ROI XML file. For each ROI slice, the PET
DICOM file is read and converted to SUV, and the pixels whose SUV equals the
voxel values stored in the XML file are found with match_roi_voxels().
extract_roi_voxels(xml_file, pet_dir, roi_name = NULL)extract_roi_voxels(xml_file, pet_dir, roi_name = NULL)
xml_file |
Path to the ROI XML file exported from 'OsiriX'. |
pet_dir |
Folder with the DICOM files of the PET series the ROIs were
drawn on (one file per slice, no other files). The files are sorted by
name; file number |
roi_name |
Optional character vector of ROI names to keep. |
An object of class osirad_roi, a list with:
Data frame, one row per ROI voxel: roi_id, roi_name,
image_index, pixel position (x_px = column, y_px = row, both
starting at 0), position in mm (x_mm, y_mm, z_mm, patient
coordinates) and suv.
Data frame, one row per ROI: the counts of
match_roi_voxels() and the number of voxels found.
SUV factor from suv_factor_from_header().
List of SUV slices from read_pet_slice(), one per ROI
slice, named by image_index.
xml_file = system.file("extdata", "phantom", "ROI.xml", package = "osirad") pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") x = extract_roi_voxels(xml_file, pet_dir) x head(x$voxels)xml_file = system.file("extdata", "phantom", "ROI.xml", package = "osirad") pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") x = extract_roi_voxels(xml_file, pet_dir) x head(x$voxels)
Finds the pixels whose SUV equals the voxel values that 'OsiriX' stored for one region of interest (ROI). The search area is the range of the ROI corner points plus 2 pixels on each side.
match_roi_voxels(point_values, suv, points_px, tol = 1e-05)match_roi_voxels(point_values, suv, points_px, tol = 1e-05)
point_values |
Voxel values of the ROI ( |
suv |
SUV matrix of the slice ( |
points_px |
ROI corner points in pixels ( |
tol |
Relative tolerance for two values to be equal. The default allows for 'OsiriX' storing the values as 32-bit floating point numbers. |
For each distinct value that 'OsiriX' lists times and that
is found at pixels of the search area:
: clear match.
: 'OsiriX' listed the same pixel more than once; the
pixels found are kept.
: tie (the same SUV at several pixels). The
pixels with the most clear matches of this ROI among their 8
neighbours are kept (an ROI is one connected region); if this is still
equal, the pixels nearest the centre of the corner points are kept.
: not found.
Each ROI is searched with its own values, so several ROIs on one slice are handled one by one.
A list with:
Two-column matrix (row, col) of the ROI pixels in suv.
Named vector: number of XML values (xml_values) and how
many of them were a clear match (clear), listed twice
(listed_twice), a tie decided by neighbours (tie_by_neighbours), a
tie decided by distance (tie_by_distance) or not found
(not_found).
xml_file = system.file("extdata", "phantom", "ROI.xml", package = "osirad") pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") rois = read_osirix_rois(xml_file) r = rois[[1]] f = list.files(pet_dir, full.names = TRUE) hdr = oro.dicom::readDICOMFile(f[1], pixelData = FALSE)$hdr sl = read_pet_slice(f[r$k], suv_factor_from_header(hdr)) m = match_roi_voxels(r$point_values, sl$suv, r$points_px) m$countsxml_file = system.file("extdata", "phantom", "ROI.xml", package = "osirad") pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") rois = read_osirix_rois(xml_file) r = rois[[1]] f = list.files(pet_dir, full.names = TRUE) hdr = oro.dicom::readDICOMFile(f[1], pixelData = FALSE)$hdr sl = read_pet_slice(f[r$k], suv_factor_from_header(hdr)) m = match_roi_voxels(r$point_values, sl$suv, r$points_px) m$counts
Draws each ROI slice in grey scale (SUV) with the ROI voxels as red squares, four slices per page. Row 1 of the image is at the top, as in 'OsiriX'.
plot_roi_slices(x, image_index = NULL, ncol = 2, nrow = 2)plot_roi_slices(x, image_index = NULL, ncol = 2, nrow = 2)
x |
Result of |
image_index |
Slices to draw ( |
ncol, nrow
|
Number of panels per page. |
x, invisibly. Called for the plot.
xml_file = system.file("extdata", "phantom", "ROI.xml", package = "osirad") pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") x = extract_roi_voxels(xml_file, pet_dir) plot_roi_slices(x, image_index = 4)xml_file = system.file("extdata", "phantom", "ROI.xml", package = "osirad") pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") x = extract_roi_voxels(xml_file, pet_dir) plot_roi_slices(x, image_index = 4)
Computes radiomics features of the ROI voxels with the 'Python' library 'PyRadiomics' (van Griethuysen et al. 2017), called through 'reticulate'. The PET series is read in 'Python' and converted to SUV with the same SUV factor as in R; the ROI voxels form one mask (label 1). Only the original image is used (no filters).
pyradiomics_features( x, pet_dir, settings = list(binWidth = 0.25, normalize = FALSE), feature_classes = c("shape", "firstorder", "glcm", "glrlm", "glszm", "gldm", "ngtdm"), python = NULL )pyradiomics_features( x, pet_dir, settings = list(binWidth = 0.25, normalize = FALSE), feature_classes = c("shape", "firstorder", "glcm", "glrlm", "glszm", "gldm", "ngtdm"), python = NULL )
x |
Result of |
pet_dir |
Folder with the DICOM files of the PET series (the same as in
|
settings |
Named list of 'PyRadiomics' settings, see https://pyradiomics.readthedocs.io/en/latest/customization.html. The default is a fixed bin width of 0.25 SUV and no intensity normalisation; no resampling. |
feature_classes |
'PyRadiomics' feature classes to compute; all features of each class are computed. |
python |
Optional path to the 'Python' executable that has 'PyRadiomics' installed. It must be set before 'Python' is first used in the R session. |
A list with:
Data frame, one row per feature: feature (name) and
value.
Data frame, one row per 'PyRadiomics' diagnostics
item (versions, settings, image and mask information): item and
value (as text).
Named vector: ROI voxels in x, voxels in the 'Python'
mask, largest distance (mm) between a voxel and its slice, and
largest difference between the SUV in 'Python' and in R.
van Griethuysen JJM, Fedorov A, Parmar C, et al. (2017). Computational radiomics system to decode the radiographic phenotype. Cancer Research, 77(21), e104-e107. doi:10.1158/0008-5472.CAN-17-0339
# Needs 'Python' with 'PyRadiomics'; the path is read from the # environment variable OSIRAD_PYTHON xml_file = system.file("extdata", "phantom", "ROI.xml", package = "osirad") pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") x = extract_roi_voxels(xml_file, pet_dir, roi_name = "Tumour") rad = pyradiomics_features(x, pet_dir, python = Sys.getenv("OSIRAD_PYTHON")) head(rad$features) rad$check# Needs 'Python' with 'PyRadiomics'; the path is read from the # environment variable OSIRAD_PYTHON xml_file = system.file("extdata", "phantom", "ROI.xml", package = "osirad") pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") x = extract_roi_voxels(xml_file, pet_dir, roi_name = "Tumour") rad = pyradiomics_features(x, pet_dir, python = Sys.getenv("OSIRAD_PYTHON")) head(rad$features) rad$check
Reads the XML file written by the 'OsiriX' menu
"Plugins > ROI Tools > Export ROIs" (XML format). The file stores, for each
region of interest (ROI) on each slice, the polygon corner points, the value
of every voxel inside the ROI and summary statistics. It does not store the
voxel positions; extract_roi_voxels() finds them.
read_osirix_rois(xml_file, roi_name = NULL)read_osirix_rois(xml_file, roi_name = NULL)
xml_file |
Path to the ROI XML file. |
roi_name |
Optional character vector. Only the ROIs with these names
are kept. |
A list with one element per ROI per slice. Each element is a list with:
Slice number in 'OsiriX' (ImageIndex, starts at 0).
image_index + 1, the file number in the sorted DICOM series.
Slice size in pixels and number of slices in the series.
ROI name.
SeriesInstanceUID of the image series the ROI was
drawn on.
Polygon corner points, in pixels (columns
x, y) and in mm (columns x, y, z).
Value of every voxel inside the ROI
(Point_value).
Length of point_values.
ROI statistics computed by 'OsiriX'.
xml_file = system.file("extdata", "phantom", "ROI.xml", package = "osirad") rois = read_osirix_rois(xml_file) length(rois) str(rois[[1]])xml_file = system.file("extdata", "phantom", "ROI.xml", package = "osirad") rois = read_osirix_rois(xml_file) length(rois) str(rois[[1]])
Reads one DICOM file of a PET series and converts the pixel values to SUV
with the slice's own RescaleSlope and RescaleIntercept and the SUV
factor.
read_pet_slice(file, suv_factor)read_pet_slice(file, suv_factor)
file |
Path to the DICOM file. |
suv_factor |
SUV factor, from |
A list with:
Matrix of SUV, [row, column] = [y_px + 1, x_px + 1], in
the DICOM orientation (row 1 at the top of the image).
ImagePositionPatient: position (mm) of the first pixel.
ImageOrientationPatient: direction cosines of the rows and
columns.
PixelSpacing (mm): row spacing, column spacing.
pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") f = list.files(pet_dir, full.names = TRUE) hdr = oro.dicom::readDICOMFile(f[1], pixelData = FALSE)$hdr sl = read_pet_slice(f[4], suv_factor_from_header(hdr)) range(sl$suv)pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") f = list.files(pet_dir, full.names = TRUE) hdr = oro.dicom::readDICOMFile(f[1], pixelData = FALSE)$hdr sl = read_pet_slice(f[4], suv_factor_from_header(hdr)) range(sl$suv)
Computes the factor that converts PET activity concentration (Bq/ml) to body-weight standardised uptake value (SUV):
with the patient weight in g and the injected dose (Bq) decayed from the
injection time (RadiopharmaceuticalStartTime) to the series start
(SeriesTime).
suv_factor_from_header(hdr)suv_factor_from_header(hdr)
hdr |
DICOM header table of one slice of the PET series, as returned in
the |
The image values must be in Bq/ml (Units = BQML) and decay
corrected to the series start (DecayCorrection = START); otherwise the
function stops.
The SUV factor, a single number (g/Bq). Multiply the rescaled pixel values (Bq/ml) by it to get SUV.
pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") f = list.files(pet_dir, full.names = TRUE)[1] hdr = oro.dicom::readDICOMFile(f, pixelData = FALSE)$hdr suv_factor_from_header(hdr)pet_dir = system.file("extdata", "phantom", "pet", package = "osirad") f = list.files(pet_dir, full.names = TRUE)[1] hdr = oro.dicom::readDICOMFile(f, pixelData = FALSE)$hdr suv_factor_from_header(hdr)