Source code for pymedphys.labs.paulking.sinogram

# Copyright (C) 2018 King Paul

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version (the "AGPL-3.0+").

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License and the additional terms for more
# details.

# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# ADDITIONAL TERMS are also included as allowed by Section 7 of the GNU
# Affero General Public License. These additional terms are Sections 1, 5,
# 6, 7, 8, and 9 from the Apache License, Version 2.0 (the "Apache-2.0")
# where all references to the definition "License" are instead defined to
# mean the AGPL-3.0+.

# You should have received a copy of the Apache-2.0 along with this
# program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.


"""
Sinogram Toolbox

@author: king.r.paul@gmail.com
"""
# The following needs to be removed before leaving labs
# pylint: skip-file

import csv
from string import ascii_letters as LETTERS
from string import digits as DIGITS

import numpy as np


[docs]def read_csv_file(file_name): """ read sinogram from csv file Return patient ID and sinogram array produced by reading a RayStation sinogram CSV file with the provided file name. Parameters ---------- file_name : str long file name of csv file Returns ------- document_id, array Notes ----- Files are produced by ExportTomoSinogram.py, Brandon Merz, RaySearch customer forum, 1/18/2018. File first row contains demographics. Subsequent rows correspond to couch positions. Leaf-open time range from zero to one. Examples -------- "Patient name: ANONYMOUS^PATIENT, ID: 00000",,,,,,,,, ,0,0,0,0,0,0,0,0,0,0,0,0,0.39123373,0.366435635 ... """ with open(file_name, "r") as csvfile: pat_name, pat_num = csvfile.readline().split("ID:") pat_name = pat_name.replace("Patient name:", "") pat_name_last, pat_name_first = pat_name.split("^") pat_name_last = "".join([c for c in pat_name_last if c in LETTERS]) pat_name_first = "".join([c for c in pat_name_first if c in LETTERS]) pat_num = "".join([c for c in pat_num if c in DIGITS]) document_id = pat_num + " - " + pat_name_last + ", " + pat_name_first reader = csv.reader(csvfile, delimiter=",") array = np.asarray([line[1:] for line in reader]).astype(float) return document_id, array
[docs]def read_bin_file(file_name): """ read sinogram from binary file Return sinogram np.array produced by reading an Accuray sinogram BIN file with the provided file name. Parameters ---------- file_name : str long file name of csv file Returns ------- sinogram : np.array Notes ----- BIN files are sinograms stored in binary format used in Tomotherapy calibration plans. """ leaf_open_times = np.fromfile(file_name, dtype=float, count=-1, sep="") num_leaves = 64 num_projections = int(len(leaf_open_times) / num_leaves) sinogram = np.reshape(leaf_open_times, (num_projections, num_leaves)) return sinogram
[docs]def crop(sinogram): """ crop sinogram Return a symmetrically cropped sinogram, such that always-closed leaves are excluded and the sinogram center is maintained. Parameters ---------- sinogram : np.array Returns ------- sinogram : np.array """ include = [False for f in range(64)] for i, projection in enumerate(sinogram): for j, leaf in enumerate(projection): if sinogram[i][j] > 0.0: include[j] = True include = include or include[::-1] idx = [i for i, yes in enumerate(include) if yes] sinogram = [[projection[i] for i in idx] for projection in sinogram] return sinogram
[docs]def unshuffle(sinogram): """ unshuffle singram by angle Return a list of 51 sinograms, by unshuffling the provided sinogram; so that all projections in the result correspond to the same gantry rotation angle, analogous to a fluence map. Parameters ---------- sinogram : np.array Returns ------- unshuffled: list of sinograms """ unshufd = [[] for i in range(51)] idx = 0 for prj in sinogram: unshufd[idx].append(prj) idx = (idx + 1) % 51 return unshufd
[docs]def make_histogram(sinogram, num_bins=10): """ make a leaf-open-time histogram Return a histogram of leaf-open-times for the provided sinogram comprised of the specified number of bins, in the form of a list of tuples: [(bin, count)...] where bin is a 2-element array setting the bounds and count in the number leaf-open-times in the bin. Parameters ---------- sinogram : np.array num_bins : int Returns ------- histogram : list of tuples: [(bin, count)...] bin is a 2-element array """ lfts = sinogram.flatten() bin_inc = (max(lfts) - min(lfts)) / num_bins bin_min = min(lfts) bin_max = max(lfts) bins_strt = np.arange(bin_min, bin_max, bin_inc) bins_stop = np.arange(bin_inc, bin_max + bin_inc, bin_inc) bins = np.dstack((bins_strt, bins_stop))[0] counts = [0 for b in bins] for lft in lfts: for idx, bin in enumerate(bins): if lft >= bin[0] and lft < bin[1]: counts[idx] = counts[idx] + 1 histogram = list(zip(bins, counts)) return histogram
[docs]def find_modulation_factor(sinogram): """ read sinogram from csv file Calculate the ratio of the maximum leaf open time (assumed fully open) to the mean leaf open time, as determined over all non-zero leaf open times, where zero is interpreted as blocked versus modulated. Parameters ---------- sinogram : np.array Returns ------- modulation factor : float """ lfts = [lft for lft in sinogram.flatten() if lft > 0.0] modulation_factor = max(lfts) / np.mean(lfts) return modulation_factor