Module pept
Source code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File : __init__.py
# License: License: GNU v3.0
# Author : Andrei Leonard Nicusan <a.l.nicusan@bham.ac.uk>
# Date : 19.08.2019
# Import base data structures
from .data.base import LineData
from .data.base import PointData
# Import subpackages
from . import data
from . import scanners
from . import simulation
from . import diagnostics
from . import tracking
from . import visualisation
__all__ = [
'LineData',
'PointData',
'data',
'scanners',
'simulation',
'diagnostics',
'tracking',
'visualisation'
]
__author__ = "Andrei Leonard Nicusan"
__credits__ = ["Andrei Leonard Nicusan", "Kit Windows-Yule", "Sam Manger"]
__license__ = "GNU v3.0"
__version__ = "0.1"
__maintainer__ = "Andrei Leonard Nicusan"
__email__ = "a.l.nicusan@bham.ac.uk"
__status__ = "Development"
Sub-modules
pept.data
pept.diagnostics
pept.scanners
pept.simulation
pept.tests
pept.tracking
pept.visualisation
Classes
class LineData (line_data, sample_size=200, overlap=0, verbose=False)
-
A class for PEPT LoR data iteration, manipulation and visualisation.
Generally, PEPT LoRs are lines in 3D space, each defined by two points, irrespective of the geometry of the scanner used. This class is used for LoRs (or any lines!) encapsulation. It can yield samples of the
line_data
of an adaptivesample_size
andoverlap
, without requiring additional storage.Parameters
line_data
: (N
,7
)numpy.ndarray
- An (N, 7) numpy array that stores the PEPT LoRs (or any generic set of lines) as time and cartesian (3D) coordinates of two points defining each line, in mm. A row is then [time, x1, y1, z1, x2, y2, z2].
sample_size
:int
, optional- An
int`` that defines the number of lines that should be returned when iterating over
line_data. A
sample_size` of 0 yields all the data as one single sample. (Default is 200) overlap
:int
, optional- An
int
that defines the overlap between two consecutive samples that are returned when iterating overline_data
. An overlap of 0 means consecutive samples, while an overlap of (sample_size
- 1) means incrementing the samples by one. A negative overlap means skipping values between samples. An error is raised ifoverlap
is larger than or equal tosample_size
. (Default is 0) verbose
:bool
, optional- An option that enables printing the time taken for the initialisation of an instance of the class. Useful when reading large files (10gb files for PEPT data is not unheard of). (Default is True)
Attributes
line_data
: (N
,7
)numpy.ndarray
- An (N, 7) numpy array that stores the PEPT LoRs as time and
cartesian (3D) coordinates of two points defining a line, in mm.
Each row is then
[time, x1, y1, z1, x2, y2, z2]
. sample_size
:int
- An
int
that defines the number of lines that should be returned when iterating overline_data
. (Default is 200) overlap
:int
- An
int
that defines the overlap between two consecutive samples that are returned when iterating overline_data
. An overlap of 0 means consecutive samples, while an overlap of (sample_size
- 1) means incrementing the samples by one. A negative overlap means skipping values between samples. It is required to be smaller thansample_size
. (Default is 0) number_of_lines
:int
- An
int
that corresponds to len(line_data
), or the number of LoRs stored byline_data
. number_of_samples
:int
- An
int
that corresponds to the number of samples that can be accessed from the class. It takesoverlap
into consideration.
Raises
ValueError
- If
overlap
>=sample_size
unlesssample_size
is 0. Overlap has to be smaller thansample_size
. Note that it can also be negative. ValueError
- If
line_data
does not have (N, 7) shape.
Notes
The class saves
line_data
as a contiguous numpy array for efficient access in C functions. It should not be changed after instantiating the class.Source code
class LineData: '''A class for PEPT LoR data iteration, manipulation and visualisation. Generally, PEPT LoRs are lines in 3D space, each defined by two points, irrespective of the geometry of the scanner used. This class is used for LoRs (or any lines!) encapsulation. It can yield samples of the `line_data` of an adaptive `sample_size` and `overlap`, without requiring additional storage. Parameters ---------- line_data : (N, 7) numpy.ndarray An (N, 7) numpy array that stores the PEPT LoRs (or any generic set of lines) as time and cartesian (3D) coordinates of two points defining each line, **in mm**. A row is then [time, x1, y1, z1, x2, y2, z2]. sample_size : int, optional An `int`` that defines the number of lines that should be returned when iterating over `line_data`. A `sample_size` of 0 yields all the data as one single sample. (Default is 200) overlap : int, optional An `int` that defines the overlap between two consecutive samples that are returned when iterating over `line_data`. An overlap of 0 means consecutive samples, while an overlap of (`sample_size` - 1) means incrementing the samples by one. A negative overlap means skipping values between samples. An error is raised if `overlap` is larger than or equal to `sample_size`. (Default is 0) verbose : bool, optional An option that enables printing the time taken for the initialisation of an instance of the class. Useful when reading large files (10gb files for PEPT data is not unheard of). (Default is True) Attributes ---------- line_data : (N, 7) numpy.ndarray An (N, 7) numpy array that stores the PEPT LoRs as time and cartesian (3D) coordinates of two points defining a line, **in mm**. Each row is then `[time, x1, y1, z1, x2, y2, z2]`. sample_size : int An `int` that defines the number of lines that should be returned when iterating over `line_data`. (Default is 200) overlap : int An `int` that defines the overlap between two consecutive samples that are returned when iterating over `line_data`. An overlap of 0 means consecutive samples, while an overlap of (`sample_size` - 1) means incrementing the samples by one. A negative overlap means skipping values between samples. It is required to be smaller than `sample_size`. (Default is 0) number_of_lines : int An `int` that corresponds to len(`line_data`), or the number of LoRs stored by `line_data`. number_of_samples : int An `int` that corresponds to the number of samples that can be accessed from the class. It takes `overlap` into consideration. Raises ------ ValueError If `overlap` >= `sample_size` unless `sample_size` is 0. Overlap has to be smaller than `sample_size`. Note that it can also be negative. ValueError If `line_data` does not have (N, 7) shape. Notes ----- The class saves `line_data` as a **contiguous** numpy array for efficient access in C functions. It should not be changed after instantiating the class. ''' def __init__(self, line_data, sample_size = 200, overlap = 0, verbose = False): if verbose: start = time.time() # If sample_size != 0 (in which case the class returns all data in one # sample), check the `overlap` is not larger or equal to `sample_size`. if sample_size != 0 and overlap >= sample_size: raise ValueError('\n[ERROR]: overlap = {} must be smaller than sample_size = {}\n'.format(overlap, sample_size)) # Initialise the inner parameters of the class self._index = 0 self._sample_size = sample_size self._overlap = overlap # If `line_data` is not C-contiguous, create a C-contiguous copy self._line_data = np.asarray(line_data, order = 'C', dtype = float) # Check that line_data has shape (N, 7) if self._line_data.ndim != 2 or self._line_data.shape[1] != 7: raise ValueError('\n[ERROR]: line_data should have dimensions (N, 7). Received {}\n'.format(self._line_data.shape)) self._number_of_lines = len(self._line_data) if verbose: end = time.time() print("Initialising the PEPT data took {} seconds\n".format(end - start)) @property def line_data(self): '''Get the lines stored in the class. Returns ------- (, 7) numpy.ndarray A memory view of the lines stored in `line_data`. ''' return self._line_data @property def sample_size(self): '''Get the number of lines in one sample returned by the class. Returns ------- int The sample size (number of lines) in one sample returned by the class. ''' return self._sample_size @sample_size.setter def sample_size(self, new_sample_size): '''Change `sample_size` without instantiating a new object It also resets the inner index of the class. Parameters ---------- new_sample_size : int The new sample size. It has to be larger than `overlap`, unless it is 0 (in which case all `line_data` will be returned as one sample). Raises ------ ValueError If `overlap` >= `new_sample_size`. Overlap has to be smaller than `sample_size`, unless `sample_size` is 0. Note that it can also be negative. ''' if new_sample_size != 0 and self._overlap >= new_sample_size: raise ValueError('\n[ERROR]: overlap = {} must be smaller than new_sample_size = {}\n'.format(self._overlap, new_sample_size)) self._index = 0 self._sample_size = new_sample_size @property def overlap(self): '''Get the overlap between every two samples returned by the class. Returns ------- int The overlap (number of lines) between every two samples returned by the class. ''' return self._overlap @overlap.setter def overlap(self, new_overlap): '''Change `overlap` without instantiating a new object It also resets the inner index of the class. Parameters ---------- new_overlap : int The new overlap. It has to be smaller than `sample_size`, unless `sample_size` is 0 (in which case all `line_data` will be returned as one sample and so overlap does not play any role). Raises ------ ValueError If `new_overlap` >= `sample_size`. `new_overlap` has to be smaller than `sample_size`, unless `sample_size` is 0. Note that it can also be negative. ''' if self._sample_size != 0 and new_overlap >= self._sample_size: raise ValueError('\n[ERROR]: new_overlap = {} must be smaller than sample_size = {}\n'.format(new_overlap, self._sample_size)) self._index = 0 self._overlap = new_overlap @property def number_of_samples(self): '''Get number of samples, considering overlap. If `sample_size == 0`, all data is returned as a single sample, and so `number_of_samples` will be 1. Otherwise, it checks the number of samples every time it is called, taking `overlap` into consideration. Returns ------- int The number of samples, taking `overlap` into consideration. ''' # If self.sample_size == 0, all data is returned as a single sample if self._sample_size == 0: return 1 # If self.sample_size != 0, check there is at least one sample if self._number_of_lines >= self._sample_size: return (self._number_of_lines - self._sample_size) // (self.sample_size - self.overlap) + 1 else: return 0 @property def number_of_lines(self): '''Get the number of lines stored in the class. Returns ------- int The number of lines stored in `line_data`. ''' return self._number_of_lines def sample_n(self, n): '''Get sample number n (indexed from 1, i.e. `n > 0`) Returns the lines from `line_data` included in sample number `n`. Samples are numbered starting from 1. Parameters ---------- n : int The number of the sample required. Note that `1 <= n <= number_of_samples`. Returns ------- (, 7) numpy.ndarray A shallow copy of the lines from `line_data` included in sample number n. Raises ------ IndexError If `sample_size == 0`, all data is returned as one single sample. Raised if `n` is not 1. IndexError If `n > number_of_samples` or `n <= 0`. ''' if self._sample_size == 0: if n == 1: return self._line_data else: raise IndexError("\n\n[ERROR]: Trying to access a non-existent sample (samples are indexed from 1): asked for sample number {}, when there is only 1 sample (sample_size == 0)\n".format(n)) elif (n > self.number_of_samples) or n <= 0: raise IndexError("\n\n[ERROR]: Trying to access a non-existent sample (samples are indexed from 1): asked for sample number {}, when there are {} samples\n".format(n, self.number_of_samples)) start_index = (n - 1) * (self._sample_size - self._overlap) return self._line_data[start_index:(start_index + self._sample_size)] def to_csv(self, filepath, delimiter = ' ', newline = '\n'): '''Write `line_data` to a CSV file Write all LoRs stored in the class to a CSV file. Parameters ---------- filepath : filename or file handle If filepath is a path (rather than file handle), it is relative to where python is called. delimiter : str, optional The delimiter between values. The default is two spaces ' ', such that numbers in the format '123,456.78' are well-understood. newline : str, optional The sequence of characters at the end of every line. The default is a new line '\n' ''' np.savetxt(filepath, self._line_data, delimiter = delimiter, newline = newline) def plot_all_lines(self, ax = None, color='r', alpha=1.0 ): '''Plot all lines using matplotlib Given a **mpl_toolkits.mplot3d.Axes3D** axis `ax`, plots all lines on it. Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. color : matplotlib color option (default 'r') alpha : matplotlib opacity option (default 1.0) Returns ------- fig, ax : matplotlib figure and axes objects Note ---- Plotting all lines in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `plot_lines_sample_n` is recommended. ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() p1 = self._line_data[:, 1:4] p2 = self._line_data[:, 4:7] for i in range(0, self._number_of_lines): ax.plot([ p1[i][0], p2[i][0] ], [ p1[i][1], p2[i][1] ], [ p1[i][2], p2[i][2] ], c = color, alpha = alpha) return fig, ax def plot_all_lines_alt_axes(self, ax, color='r', alpha=1.0): '''Plot all lines using matplotlib on PEPT-style axes Given a **mpl_toolkits.mplot3d.Axes3D** axis `ax`, plots all lines on the PEPT-style convention: **x** is *parallel and horizontal* to the screens, **y** is *parallel and vertical* to the screens, **z** is *perpendicular* to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y) Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. color : matplotlib color option (default 'r') alpha : matplotlib opacity option (default 1.0) Returns ------- fig, ax : matplotlib figure and axes objects Note ---- Plotting all lines in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `plot_lines_sample_n_alt_axes` is recommended. ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() p1 = self._line_data[:, 1:4] p2 = self._line_data[:, 4:7] for i in range(0, self._number_of_lines): ax.plot([ p1[i][2], p2[i][2] ], [ p1[i][0], p2[i][0] ], [ p1[i][1], p2[i][1] ], c = color, alpha=alpha) return fig, ax def plot_lines_sample_n(self, n, ax = None, color = 'r', alpha = 1.0): '''Plot lines from sample `n` using matplotlib Given a **mpl_toolkits.mplot3d.Axes3D** axis `ax`, plots all lines from sample number `n`. Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. sampleN : int The number of the sample to be plotted. color : matplotlib color option (default 'r') alpha : matplotlib opacity option (default 1.0) Returns ------- fig, ax : matplotlib figure and axes objects ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() sample = self.sample_n(n) for i in range(0, len(sample)): ax.plot([ sample[i][1], sample[i][4] ], [ sample[i][2], sample[i][5] ], [ sample[i][3], sample[i][6] ], c = color, alpha = alpha) return fig, ax def plot_lines_sample_n_alt_axes(self, n, ax=None, color='r', alpha=1.0): '''Plot lines from sample `n` using matplotlib on PEPT-style axes Given a **mpl_toolkits.mplot3d.Axes3D** axis `ax`, plots all lines from sample number sampleN on the PEPT-style coordinates convention: **x** is *parallel and horizontal* to the screens, **y** is *parallel and vertical* to the screens, **z** is *perpendicular* to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y) Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. n : int The number of the sample to be plotted. color : matplotlib color option (default 'r') alpha : matplotlib opacity option (default 1.0) Returns ------- fig, ax : matplotlib figure and axes objects ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() sample = self.sample_n(n) for i in range(0, len(sample)): ax.plot([ sample[i][3], sample[i][6] ], [ sample[i][1], sample[i][4] ], [ sample[i][2], sample[i][5] ], c = color, alpha = alpha) return fig, ax def all_lines_traces(self): '''Get a list of Plotly traces for each line. Creates a `plotly.graph_objects.Scatter3d` object for each line and returns them as a list. Can then be passed to the `plotly.graph_objects.figure.add_traces` function or a `PlotlyGrapher` instance using the `add_traces` method. Returns ------- list A list of `plotly.graph_objects.Scatter3d` objects. Note ---- Plotting all lines in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `lines_sample_n_traces` is recommended. ''' p1 = self._line_data[:, 1:4] p2 = self._line_data[:, 4:7] traces = [] for i in range(0, self._number_of_lines): traces.append(go.Scatter3d( x = [ p1[i][0], p2[i][0] ], y = [ p1[i][1], p2[i][1] ], z = [ p1[i][2], p2[i][2] ], mode = 'lines', opacity = 0.8, line = dict( width = 2, ) )) return traces def lines_sample_n_traces(self, n): '''Get a list of Plotly traces for each line in sample `n`. Creates a `plotly.graph_objects.Scatter3d` object for each line include in sample number `sampleN` and returns them as a list. Can then be passed to the `plotly.graph_objects.figure.add_traces` function or a `PlotlyGrapher` instance using the `add_traces` method. Parameters ---------- n : int The number of the sample to be plotted. Returns ------- list A list of `plotly.graph_objects.Scatter3d` objects. ''' sample = self.sample_n(n) traces = [] for i in range(0, len(sample)): traces.append(go.Scatter3d( x = [ sample[i][1], sample[i][4] ], y = [ sample[i][2], sample[i][5] ], z = [ sample[i][3], sample[i][6] ], mode = 'lines', opacity = 0.6, line = dict( width = 2, ) )) return traces def __len__(self): # Defined so that len(class_instance) returns the number of samples. return self.number_of_samples def __getitem__(self, key): # Defined so that samples can be accessed as class_instance[0] if self.number_of_samples == 0: raise IndexError("Tried to access sample {} (indexed from 0), when there are {} samples".format(key, self.number_of_samples)) if key >= self.number_of_samples: raise IndexError("Tried to access sample {} (indexed from 0), when there are {} samples".format(key, self.number_of_samples)) while key < 0: key += self.number_of_samples return self.sample_n(key + 1) def __iter__(self): # Defined so the class can be iterated as `for sample in class_instance: ...` return self def __next__(self): # sample_size = 0 => return all data if self._sample_size == 0: self._sample_size = -1 return self._line_data # Use -1 as a flag if self._sample_size == -1: self._sample_size = 0 raise StopIteration # sample_size > 0 => return slices if self._index != 0: self._index = self._index + self._sample_size - self.overlap else: self._index = self._index + self.sample_size if self._index > self.number_of_lines: self._index = 0 raise StopIteration return self._line_data[(self._index - self._sample_size):self._index]
Subclasses
Instance variables
var line_data
-
Get the lines stored in the class.
Returns
(, 7) numpy.ndarray A memory view of the lines stored in
line_data
.Source code
@property def line_data(self): '''Get the lines stored in the class. Returns ------- (, 7) numpy.ndarray A memory view of the lines stored in `line_data`. ''' return self._line_data
var number_of_lines
-
Get the number of lines stored in the class.
Returns
int
- The number of lines stored in
line_data
.
Source code
@property def number_of_lines(self): '''Get the number of lines stored in the class. Returns ------- int The number of lines stored in `line_data`. ''' return self._number_of_lines
var number_of_samples
-
Get number of samples, considering overlap.
If
sample_size == 0
, all data is returned as a single sample, and sonumber_of_samples
will be 1. Otherwise, it checks the number of samples every time it is called, takingoverlap
into consideration.Returns
int
- The number of samples, taking
overlap
into consideration.
Source code
@property def number_of_samples(self): '''Get number of samples, considering overlap. If `sample_size == 0`, all data is returned as a single sample, and so `number_of_samples` will be 1. Otherwise, it checks the number of samples every time it is called, taking `overlap` into consideration. Returns ------- int The number of samples, taking `overlap` into consideration. ''' # If self.sample_size == 0, all data is returned as a single sample if self._sample_size == 0: return 1 # If self.sample_size != 0, check there is at least one sample if self._number_of_lines >= self._sample_size: return (self._number_of_lines - self._sample_size) // (self.sample_size - self.overlap) + 1 else: return 0
var overlap
-
Get the overlap between every two samples returned by the class.
Returns
int
- The overlap (number of lines) between every two samples returned by the class.
Source code
@property def overlap(self): '''Get the overlap between every two samples returned by the class. Returns ------- int The overlap (number of lines) between every two samples returned by the class. ''' return self._overlap
var sample_size
-
Get the number of lines in one sample returned by the class.
Returns
int
- The sample size (number of lines) in one sample returned by the class.
Source code
@property def sample_size(self): '''Get the number of lines in one sample returned by the class. Returns ------- int The sample size (number of lines) in one sample returned by the class. ''' return self._sample_size
Methods
def all_lines_traces(self)
-
Get a list of Plotly traces for each line.
Creates a
plotly.graph_objects.Scatter3d
object for each line and returns them as a list. Can then be passed to theplotly.graph_objects.figure.add_traces
function or aPlotlyGrapher
instance using theadd_traces
method.Returns
list
- A list of
plotly.graph_objects.Scatter3d
objects.
Note
Plotting all lines in the case of large LoR arrays is very computationally intensive. For large arrays (> 10000), plotting individual samples using
lines_sample_n_traces
is recommended.Source code
def all_lines_traces(self): '''Get a list of Plotly traces for each line. Creates a `plotly.graph_objects.Scatter3d` object for each line and returns them as a list. Can then be passed to the `plotly.graph_objects.figure.add_traces` function or a `PlotlyGrapher` instance using the `add_traces` method. Returns ------- list A list of `plotly.graph_objects.Scatter3d` objects. Note ---- Plotting all lines in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `lines_sample_n_traces` is recommended. ''' p1 = self._line_data[:, 1:4] p2 = self._line_data[:, 4:7] traces = [] for i in range(0, self._number_of_lines): traces.append(go.Scatter3d( x = [ p1[i][0], p2[i][0] ], y = [ p1[i][1], p2[i][1] ], z = [ p1[i][2], p2[i][2] ], mode = 'lines', opacity = 0.8, line = dict( width = 2, ) )) return traces
def lines_sample_n_traces(self, n)
-
Get a list of Plotly traces for each line in sample
n
.Creates a
plotly.graph_objects.Scatter3d
object for each line include in sample numbersampleN
and returns them as a list. Can then be passed to theplotly.graph_objects.figure.add_traces
function or aPlotlyGrapher
instance using theadd_traces
method.Parameters
n
:int
- The number of the sample to be plotted.
Returns
list
- A list of
plotly.graph_objects.Scatter3d
objects.
Source code
def lines_sample_n_traces(self, n): '''Get a list of Plotly traces for each line in sample `n`. Creates a `plotly.graph_objects.Scatter3d` object for each line include in sample number `sampleN` and returns them as a list. Can then be passed to the `plotly.graph_objects.figure.add_traces` function or a `PlotlyGrapher` instance using the `add_traces` method. Parameters ---------- n : int The number of the sample to be plotted. Returns ------- list A list of `plotly.graph_objects.Scatter3d` objects. ''' sample = self.sample_n(n) traces = [] for i in range(0, len(sample)): traces.append(go.Scatter3d( x = [ sample[i][1], sample[i][4] ], y = [ sample[i][2], sample[i][5] ], z = [ sample[i][3], sample[i][6] ], mode = 'lines', opacity = 0.6, line = dict( width = 2, ) )) return traces
def plot_all_lines(self, ax=None, color='r', alpha=1.0)
-
Plot all lines using matplotlib
Given a mpl_toolkits.mplot3d.Axes3D axis
ax
, plots all lines on it.Parameters
ax
:mpl_toolkits.mplot3D.Axes3D
object
- The 3D matplotlib-based axis for plotting.
color
:matplotlib
color
option
(default'r'
)alpha
:matplotlib
opacity
option
(default1.0
)
Returns
fig
,ax
:matplotlib
figure
and
axes
objects
Note
Plotting all lines in the case of large LoR arrays is very computationally intensive. For large arrays (> 10000), plotting individual samples using
plot_lines_sample_n
is recommended.Source code
def plot_all_lines(self, ax = None, color='r', alpha=1.0 ): '''Plot all lines using matplotlib Given a **mpl_toolkits.mplot3d.Axes3D** axis `ax`, plots all lines on it. Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. color : matplotlib color option (default 'r') alpha : matplotlib opacity option (default 1.0) Returns ------- fig, ax : matplotlib figure and axes objects Note ---- Plotting all lines in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `plot_lines_sample_n` is recommended. ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() p1 = self._line_data[:, 1:4] p2 = self._line_data[:, 4:7] for i in range(0, self._number_of_lines): ax.plot([ p1[i][0], p2[i][0] ], [ p1[i][1], p2[i][1] ], [ p1[i][2], p2[i][2] ], c = color, alpha = alpha) return fig, ax
def plot_all_lines_alt_axes(self, ax, color='r', alpha=1.0)
-
Plot all lines using matplotlib on PEPT-style axes
Given a mpl_toolkits.mplot3d.Axes3D axis
ax
, plots all lines on the PEPT-style convention: x is parallel and horizontal to the screens, y is parallel and vertical to the screens, z is perpendicular to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y)Parameters
ax
:mpl_toolkits.mplot3D.Axes3D
object
- The 3D matplotlib-based axis for plotting.
color
:matplotlib
color
option
(default'r'
)alpha
:matplotlib
opacity
option
(default1.0
)
Returns
fig
,ax
:matplotlib
figure
and
axes
objects
Note
Plotting all lines in the case of large LoR arrays is very computationally intensive. For large arrays (> 10000), plotting individual samples using
plot_lines_sample_n_alt_axes
is recommended.Source code
def plot_all_lines_alt_axes(self, ax, color='r', alpha=1.0): '''Plot all lines using matplotlib on PEPT-style axes Given a **mpl_toolkits.mplot3d.Axes3D** axis `ax`, plots all lines on the PEPT-style convention: **x** is *parallel and horizontal* to the screens, **y** is *parallel and vertical* to the screens, **z** is *perpendicular* to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y) Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. color : matplotlib color option (default 'r') alpha : matplotlib opacity option (default 1.0) Returns ------- fig, ax : matplotlib figure and axes objects Note ---- Plotting all lines in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `plot_lines_sample_n_alt_axes` is recommended. ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() p1 = self._line_data[:, 1:4] p2 = self._line_data[:, 4:7] for i in range(0, self._number_of_lines): ax.plot([ p1[i][2], p2[i][2] ], [ p1[i][0], p2[i][0] ], [ p1[i][1], p2[i][1] ], c = color, alpha=alpha) return fig, ax
def plot_lines_sample_n(self, n, ax=None, color='r', alpha=1.0)
-
Plot lines from sample
n
using matplotlibGiven a mpl_toolkits.mplot3d.Axes3D axis
ax
, plots all lines from sample numbern
.Parameters
ax
:mpl_toolkits.mplot3D.Axes3D
object
- The 3D matplotlib-based axis for plotting.
sampleN
:int
- The number of the sample to be plotted.
color
:matplotlib
color
option
(default'r'
)alpha
:matplotlib
opacity
option
(default1.0
)
Returns
fig
,ax
:matplotlib
figure
and
axes
objects
Source code
def plot_lines_sample_n(self, n, ax = None, color = 'r', alpha = 1.0): '''Plot lines from sample `n` using matplotlib Given a **mpl_toolkits.mplot3d.Axes3D** axis `ax`, plots all lines from sample number `n`. Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. sampleN : int The number of the sample to be plotted. color : matplotlib color option (default 'r') alpha : matplotlib opacity option (default 1.0) Returns ------- fig, ax : matplotlib figure and axes objects ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() sample = self.sample_n(n) for i in range(0, len(sample)): ax.plot([ sample[i][1], sample[i][4] ], [ sample[i][2], sample[i][5] ], [ sample[i][3], sample[i][6] ], c = color, alpha = alpha) return fig, ax
def plot_lines_sample_n_alt_axes(self, n, ax=None, color='r', alpha=1.0)
-
Plot lines from sample
n
using matplotlib on PEPT-style axesGiven a mpl_toolkits.mplot3d.Axes3D axis
ax
, plots all lines from sample number sampleN on the PEPT-style coordinates convention: x is parallel and horizontal to the screens, y is parallel and vertical to the screens, z is perpendicular to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y)Parameters
ax
:mpl_toolkits.mplot3D.Axes3D
object
- The 3D matplotlib-based axis for plotting.
n
:int
- The number of the sample to be plotted.
color
:matplotlib
color
option
(default'r'
)alpha
:matplotlib
opacity
option
(default1.0
)
Returns
fig
,ax
:matplotlib
figure
and
axes
objects
Source code
def plot_lines_sample_n_alt_axes(self, n, ax=None, color='r', alpha=1.0): '''Plot lines from sample `n` using matplotlib on PEPT-style axes Given a **mpl_toolkits.mplot3d.Axes3D** axis `ax`, plots all lines from sample number sampleN on the PEPT-style coordinates convention: **x** is *parallel and horizontal* to the screens, **y** is *parallel and vertical* to the screens, **z** is *perpendicular* to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y) Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. n : int The number of the sample to be plotted. color : matplotlib color option (default 'r') alpha : matplotlib opacity option (default 1.0) Returns ------- fig, ax : matplotlib figure and axes objects ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() sample = self.sample_n(n) for i in range(0, len(sample)): ax.plot([ sample[i][3], sample[i][6] ], [ sample[i][1], sample[i][4] ], [ sample[i][2], sample[i][5] ], c = color, alpha = alpha) return fig, ax
def sample_n(self, n)
-
Get sample number n (indexed from 1, i.e.
n > 0
)Returns the lines from
line_data
included in sample numbern
. Samples are numbered starting from 1.Parameters
n
:int
- The number of the sample required. Note that
1 <= n <= number_of_samples
.
Returns
(, 7) numpy.ndarray A shallow copy of the lines from
line_data
included in sample number n.Raises
IndexError
- If
sample_size == 0
, all data is returned as one single sample. Raised ifn
is not 1. IndexError
- If
n > number_of_samples
orn <= 0
.
Source code
def sample_n(self, n): '''Get sample number n (indexed from 1, i.e. `n > 0`) Returns the lines from `line_data` included in sample number `n`. Samples are numbered starting from 1. Parameters ---------- n : int The number of the sample required. Note that `1 <= n <= number_of_samples`. Returns ------- (, 7) numpy.ndarray A shallow copy of the lines from `line_data` included in sample number n. Raises ------ IndexError If `sample_size == 0`, all data is returned as one single sample. Raised if `n` is not 1. IndexError If `n > number_of_samples` or `n <= 0`. ''' if self._sample_size == 0: if n == 1: return self._line_data else: raise IndexError("\n\n[ERROR]: Trying to access a non-existent sample (samples are indexed from 1): asked for sample number {}, when there is only 1 sample (sample_size == 0)\n".format(n)) elif (n > self.number_of_samples) or n <= 0: raise IndexError("\n\n[ERROR]: Trying to access a non-existent sample (samples are indexed from 1): asked for sample number {}, when there are {} samples\n".format(n, self.number_of_samples)) start_index = (n - 1) * (self._sample_size - self._overlap) return self._line_data[start_index:(start_index + self._sample_size)]
def to_csv(self, filepath, delimiter=' ', newline='\n')
-
Write
line_data
to a CSV fileWrite all LoRs stored in the class to a CSV file. Parameters ---------- filepath : filename or file handle If filepath is a path (rather than file handle), it is relative to where python is called. delimiter : str, optional The delimiter between values. The default is two spaces ' ', such that numbers in the format '123,456.78' are well-understood. newline : str, optional The sequence of characters at the end of every line. The default is a new line '
'
Source code
def to_csv(self, filepath, delimiter = ' ', newline = '\n'): '''Write `line_data` to a CSV file Write all LoRs stored in the class to a CSV file. Parameters ---------- filepath : filename or file handle If filepath is a path (rather than file handle), it is relative to where python is called. delimiter : str, optional The delimiter between values. The default is two spaces ' ', such that numbers in the format '123,456.78' are well-understood. newline : str, optional The sequence of characters at the end of every line. The default is a new line '\n' ''' np.savetxt(filepath, self._line_data, delimiter = delimiter, newline = newline)
class PointData (point_data, sample_size=0, overlap=0, verbose=False)
-
A class for generic PEPT data iteration, manipulation and visualisation.
This class is used to encapsulate points. Unlike
LineData
, it does not have any restriction on the maximum number of columns it can store. It can yield samples of thepoint_data
of an adaptivesample_size
andoverlap
, without requiring additional storage.Parameters
point_data
: (N
,M
)numpy.ndarray
- An (N, M >= 4) numpy array that stores points (or any generic 2D set of data). It expects that the first column is time, followed by cartesian (3D) coordinates of points in mm, followed by any extra information the user needs. A row is then [time, x, y, z, etc].
sample_size
:int
, optional- An
int`` that defines the number of points that should be returned when iterating over
point_data. A
sample_size` of 0 yields all the data as one single sample. (Default is 200) overlap
:int
, optional- An
int
that defines the overlap between two consecutive samples that are returned when iterating overpoint_data
. An overlap of 0 means consecutive samples, while an overlap of (sample_size
- 1) means incrementing the samples by one. A negative overlap means skipping values between samples. An error is raised ifoverlap
is larger than or equal tosample_size
. (Default is 0) verbose
:bool
, optional- An option that enables printing the time taken for the initialisation of an instance of the class. Useful when reading large files (10gb files for PEPT data is not unheard of). (Default is True)
Attributes
point_data
: (N
,M
)numpy.ndarray
- An (N, M >= 4) numpy array that stores the points as time, followed by
cartesian (3D) coordinates of the point in mm, followed by any extra
information. Each row is then
[time, x, y, z, etc]
. sample_size
:int
- An
int
that defines the number of lines that should be returned when iterating overpoint_data
. (Default is 200) overlap
:int
- An
int
that defines the overlap between two consecutive samples that are returned when iterating overpoint_data
. An overlap of 0 means consecutive samples, while an overlap of (sample_size
- 1) means incrementing the samples by one. A negative overlap means skipping values between samples. It is required to be smaller thansample_size
. (Default is 0) number_of_points
:int
- An
int
that corresponds to len(point_data
), or the number of points stored bypoint_data
. number_of_samples
:int
- An
int
that corresponds to the number of samples that can be accessed from the class, taking theoverlap
into consideration.
Raises
ValueError
- If
overlap
>=sample_size
. Overlap is required to be smaller thansample_size
, unlesssample_size
is 0. Note that it can also be negative. ValueError
- If
line_data
does not have (N, M) shape, where M >= 4.
Notes
The class saves
point_data
as a contiguous numpy array for efficient access in C extensions.Source code
class PointData: '''A class for generic PEPT data iteration, manipulation and visualisation. This class is used to encapsulate points. Unlike `LineData`, it does not have any restriction on the maximum number of columns it can store. It can yield samples of the `point_data` of an adaptive `sample_size` and `overlap`, without requiring additional storage. Parameters ---------- point_data : (N, M) numpy.ndarray An (N, M >= 4) numpy array that stores points (or any generic 2D set of data). It expects that the first column is time, followed by cartesian (3D) coordinates of points **in mm**, followed by any extra information the user needs. A row is then [time, x, y, z, etc]. sample_size : int, optional An `int`` that defines the number of points that should be returned when iterating over `point_data`. A `sample_size` of 0 yields all the data as one single sample. (Default is 200) overlap : int, optional An `int` that defines the overlap between two consecutive samples that are returned when iterating over `point_data`. An overlap of 0 means consecutive samples, while an overlap of (`sample_size` - 1) means incrementing the samples by one. A negative overlap means skipping values between samples. An error is raised if `overlap` is larger than or equal to `sample_size`. (Default is 0) verbose : bool, optional An option that enables printing the time taken for the initialisation of an instance of the class. Useful when reading large files (10gb files for PEPT data is not unheard of). (Default is True) Attributes ---------- point_data : (N, M) numpy.ndarray An (N, M >= 4) numpy array that stores the points as time, followed by cartesian (3D) coordinates of the point **in mm**, followed by any extra information. Each row is then `[time, x, y, z, etc]`. sample_size : int An `int` that defines the number of lines that should be returned when iterating over `point_data`. (Default is 200) overlap : int An `int` that defines the overlap between two consecutive samples that are returned when iterating over `point_data`. An overlap of 0 means consecutive samples, while an overlap of (`sample_size` - 1) means incrementing the samples by one. A negative overlap means skipping values between samples. It is required to be smaller than `sample_size`. (Default is 0) number_of_points : int An `int` that corresponds to len(`point_data`), or the number of points stored by `point_data`. number_of_samples : int An `int` that corresponds to the number of samples that can be accessed from the class, taking the `overlap` into consideration. Raises ------ ValueError If `overlap` >= `sample_size`. Overlap is required to be smaller than `sample_size`, unless `sample_size` is 0. Note that it can also be negative. ValueError If `line_data` does not have (N, M) shape, where M >= 4. Notes ----- The class saves `point_data` as a **contiguous** numpy array for efficient access in C extensions. ''' def __init__(self, point_data, sample_size = 0, overlap = 0, verbose = False): if verbose: start = time.time() if sample_size != 0 and overlap >= sample_size: raise ValueError('\n[ERROR]: overlap = {} must be smaller than sample_size = {}\n'.format(overlap, sample_size)) self._index = 0 self._sample_size = sample_size self._overlap = overlap self._point_data = np.asarray(point_data, order = 'C', dtype = float) if self._point_data.ndim != 2 or self._point_data.shape[1] < 4: raise ValueError('\n[ERROR]: point_data should have two dimensions (M, N), where N >= 4. Received {}\n'.format(self._point_data.shape)) self._number_of_points = len(self._point_data) if verbose: end = time.time() print("Initialising the PEPT data took {} seconds\n".format(end - start)) @property def point_data(self): '''Get the points stored in the class. Returns ------- (M, N) numpy.ndarray A memory view of the points stored in `point_data`. ''' return self._point_data @property def sample_size(self): '''Get the number of points in one sample returned by the class. Returns ------- int The sample size (number of lines) in one sample returned by the class. ''' return self._sample_size @sample_size.setter def sample_size(self, new_sample_size): '''Change `sample_size` without instantiating a new object It also resets the inner index of the class. Parameters ---------- new_sample_size : int The new sample size. It has to be larger than `overlap`, unless it is 0 (in which case all `point_data` will be returned as one sample). Raises ------ ValueError If `overlap` >= `new_sample_size`. Overlap has to be smaller than `sample_size`, unless `sample_size` is 0. Note that it can also be negative. ''' if new_sample_size != 0 and self._overlap >= new_sample_size: raise ValueError('\n[ERROR]: overlap = {} must be smaller than new_sample_size = {}\n'.format(self._overlap, new_sample_size)) self._index = 0 self._sample_size = new_sample_size @property def overlap(self): '''Get the overlap between every two samples returned by the class. Returns ------- int The overlap (number of points) between every two samples returned by the class. ''' return self._overlap @overlap.setter def overlap(self, new_overlap): '''Change `overlap` without instantiating a new object It also resets the inner index of the class. Parameters ---------- new_overlap : int The new overlap. It has to be smaller than `sample_size`, unless `sample_size` is 0 (in which case all `point_data` will be returned as one sample and so overlap does not play any role). Raises ------ ValueError If `new_overlap` >= `sample_size`. `new_overlap` has to be smaller than `sample_size`, unless `sample_size` is 0. Note that it can also be negative. ''' if self._sample_size != 0 and new_overlap >= self._sample_size: raise ValueError('\n[ERROR]: new_overlap = {} must be smaller than sample_size = {}\n'.format(new_overlap, self._sample_size)) self._index = 0 self._overlap = new_overlap @property def number_of_samples(self): '''Get number of samples, considering overlap. If `sample_size == 0`, all data is returned as a single sample, and so `number_of_samples` will be 1. Otherwise, it checks the number of samples every time it is called, taking `overlap` into consideration. Returns ------- int The number of samples, taking `overlap` into consideration. ''' # If self.sample_size == 0, all data is returned as a single sample if self._sample_size == 0: return 1 # If self.sample_size != 0, check there is at least one sample if self._number_of_points >= self._sample_size: return (self._number_of_points - self._sample_size) // (self.sample_size - self.overlap) + 1 else: return 0 @property def number_of_points(self): '''Get the number of points stored in the class. Returns ------- int The number of points stored in `point_data`. ''' return self._number_of_points def sample_n(self, n): '''Get sample number n (indexed from 1, i.e. `n > 0`) Returns the lines from `point_data` included in sample number `n`. Samples are numbered starting from 1. Parameters ---------- n : int The number of the sample required. Note that `1 <= n <= number_of_samples`. Returns ------- (M, N) numpy.ndarray A shallow copy of the points from `point_data` included in sample number n. Raises ------ IndexError If `sample_size == 0`, all data is returned as one single sample. Raised if `n` is not 1. IndexError If `n > number_of_samples` or `n <= 0`. ''' if self._sample_size == 0: if n == 1: return self._point_data else: raise IndexError("\n\n[ERROR]: Trying to access a non-existent sample (samples indexed from 1): asked for sample number {}, when there is only 1 sample (sample_size == 0)\n".format(n)) elif (n > self.number_of_samples) or n <= 0: raise IndexError("\n\n[ERROR]: Trying to access a non-existent sample (samples are indexed from 1): asked for sample number {}, when there are {} samples\n".format(n, self.number_of_samples)) start_index = (n - 1) * (self._sample_size - self._overlap) return self._point_data[start_index:(start_index + self._sample_size)] def to_csv(self, filepath, delimiter = ' ', newline = '\n'): '''Write `point_data` to a CSV file Write all points (and any extra data) stored in the class to a CSV file. Parameters ---------- filepath : filename or file handle If filepath is a path (rather than file handle), it is relative to where python is called. delimiter : str, optional The delimiter between values. The default is two spaces ' ', such that numbers in the format '123,456.78' are well-understood. newline : str, optional The sequence of characters at the end of every line. The default is a new line '\n' ''' np.savetxt(filepath, self._point_data, delimiter = delimiter, newline = newline) def plot_all_points(self, ax = None): '''Plot all points using matplotlib Given a **mpl_toolkits.mplot3d.Axes3D** axis, plots all points on it. Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. Returns ------- fig, ax : matplotlib figure and axes objects Note ---- Plotting all points in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `plot_points_sample_n` is recommended. ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() # Scatter x, y, z, [color] x = self._point_data[:, 1], y = self._point_data[:, 2], z = self._point_data[:, 3], color = self._point_data[:, -1], cmap = plt.cm.magma color_array = cmap(colour_data) ax.scatter(x,y,z,c=color_array[0]) return fig, ax def plot_all_points_alt_axes(self, ax = None ): '''Plot all points using matplotlib on PEPT-style axes Given a **mpl_toolkits.mplot3d.Axes3D** axis, plots all points on the PEPT-style convention: **x** is *parallel and horizontal* to the screens, **y** is *parallel and vertical* to the screens, **z** is *perpendicular* to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y) Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. Returns ------- fig, ax : matplotlib figure and axes objects Note ---- Plotting all points in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `plot_lines_sample_n_alt_axes` is recommended. ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() # Scatter x, y, z, [color] x = self._point_data[:, 1] y = self._point_data[:, 2] z = self._point_data[:, 3] color = self._point_data[:, -1] cmap = plt.cm.magma color_array = cmap(color) ax.scatter(z,x,y,c=color_array[0]) return fig, ax def plot_points_sample_n(self, n, ax=None): '''Plot points from sample `n` using matplotlib Given a **mpl_toolkits.mplot3d.Axes3D** axis, plots all points from sample number `n`. Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. n : int The number of the sample to be plotted. Returns ------- fig, ax : matplotlib figure and axes objects ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() # Scatter x, y, z, [color] sample = self.sample_n(n) x = sample[:, 1] y = sample[:, 2] z = sample[:, 3] color = sample[:, -1] cmap = plt.cm.magma color_array = cmap(color) ax.scatter(z,x,y,c=color_array[0]) return fig, ax def plot_points_sample_n_alt_axes(self, n, ax=None): '''Plot points from sample `n` using matplotlib on PEPT-style axes Given a **mpl_toolkits.mplot3d.Axes3D** axis, plots all points from sample number sampleN on the PEPT-style coordinates convention: **x** is *parallel and horizontal* to the screens, **y** is *parallel and vertical* to the screens, **z** is *perpendicular* to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y) Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. n : int The number of the sample to be plotted. Returns ------- fig, ax : matplotlib figure and axes objects ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() # Scatter x, y, z, [color] sample = self.sample_n(n) x = sample[:, 1] y = sample[:, 2] z = sample[:, 3] color = sample[:, -1] cmap = plt.cm.magma color_array = cmap(color) ax.scatter(z,x,y,c=color_array[0]) return fig, ax def all_points_trace(self, size = 2, color = None): '''Get a Plotly trace of all points. Creates a `plotly.graph_objects.Scatter3d` object. Can then be passed to the `plotly.graph_objects.figure.add_trace` function or a `PlotlyGrapher` instance using the `add_trace` method. Returns ------- plotly.graph_objects.Scatter3d A `plotly.graph_objects.Scatter3d` trace of all points. Note ---- Plotting all points in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `points_sample_n_traces` is recommended. ''' trace = go.Scatter3d( x = self._point_data[:, 1], y = self._point_data[:, 2], z = self._point_data[:, 3], mode = 'markers', marker = dict( size = size, color = color, opacity = 0.8 ) ) return trace def all_points_trace_colorbar(self, size = 2, colorbar_title = None): '''Get a Plotly trace of all points, colour-coding the last column of `point_data`. Creates a `plotly.graph_objects.Scatter3d` object. Can then be passed to the `plotly.graph_objects.figure.add_trace` function or a `PlotlyGrapher` instance using the `add_trace` method. Returns ------- plotly.graph_objects.Scatter3d A `plotly.graph_objects.Scatter3d` trace of all points. Note ---- Plotting all points in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `points_sample_n_traces` is recommended. ''' if colorbar_title != None: colorbar = dict(title = colorbar_title) else: colorbar = dict() trace = go.Scatter3d( x = self._point_data[:, 1], y = self._point_data[:, 2], z = self._point_data[:, 3], mode = 'markers', marker = dict( size = size, color = self._point_data[:, -1], colorscale = 'Magma', colorbar = colorbar, opacity = 0.8 ) ) return trace def points_sample_n_trace(self, n, size = 2, color = None): '''Get a Plotly trace for all points in sample `n`. Returns a `plotly.graph_objects.Scatter3d` trace containing all points included in sample number `n`. Can then be passed to the `plotly.graph_objects.figure.add_trace` function or a `PlotlyGrapher` instance using the `add_trace` method. Parameters ---------- n : int The number of the sample to be plotted. Returns ------- plotly.graph_object.Scatter3d A `plotly.graph_objects.Scatter3d` trace of all points in sample `n`. ''' sample = self.sample_n(n) trace = go.Scatter3d( x = sample[:, 1], y = sample[:, 2], z = sample[:, 3], mode = 'markers', marker = dict( size = size, color = color, opacity = 0.8 ) ) return trace def points_sample_n_trace_colorbar(self, n, size = 2, colorbar_title = None): '''Get a Plotly trace for all points in sample `n`, colour-coding the last column. Returns a `plotly.graph_objects.Scatter3d` trace containing all points included in sample number `n`. Can then be passed to the `plotly.graph_objects.figure.add_trace` function or a `PlotlyGrapher` instance using the `add_trace` method. Parameters ---------- n : int The number of the sample to be plotted. Returns ------- plotly.graph_object.Scatter3d A `plotly.graph_objects.Scatter3d` trace of all points in sample `n`. ''' if colorbar_title != None: colorbar = dict(title = colorbar_title) else: colorbar = dict() sample = self.sample_n(n) trace = go.Scatter3d( x = sample[:, 1], y = sample[:, 2], z = sample[:, 3], mode = 'markers', marker = dict( size = size, color = sample[:, -1], colorscale = 'Magma', colorbar = colorbar, opacity = 0.8 ) ) return trace def __len__(self): # Defined so that len(class_instance) returns the number of samples. return self.number_of_samples def __getitem__(self, key): # Defined so that samples can be accessed as class_instance[0] if self.number_of_samples == 0: raise IndexError("Tried to access sample {} (indexed from 0), when there are {} samples".format(key, self.number_of_samples)) if key >= self.number_of_samples: raise IndexError("Tried to access sample {} (indexed from 0), when there are {} samples".format(key, self.number_of_samples)) while key < 0: key += self.number_of_samples return self.sample_n(key + 1) def __iter__(self): # Defined so the class can be iterated as `for sample in class_instance: ...` return self def __next__(self): # sample_size = 0 => return all data if self._sample_size == 0: self._sample_size = -1 return self._point_data # Use -1 as a flag if self._sample_size == -1: self._sample_size = 0 raise StopIteration # sample_size > 0 => return slices if self._index != 0: self._index = self._index + self._sample_size - self.overlap else: self._index = self._index + self.sample_size if self._index > self.number_of_points: self._index = 0 raise StopIteration return self._point_data[(self._index - self._sample_size):self._index]
Subclasses
Instance variables
var number_of_points
-
Get the number of points stored in the class.
Returns
int
- The number of points stored in
point_data
.
Source code
@property def number_of_points(self): '''Get the number of points stored in the class. Returns ------- int The number of points stored in `point_data`. ''' return self._number_of_points
var number_of_samples
-
Get number of samples, considering overlap.
If
sample_size == 0
, all data is returned as a single sample, and sonumber_of_samples
will be 1. Otherwise, it checks the number of samples every time it is called, takingoverlap
into consideration.Returns
int
- The number of samples, taking
overlap
into consideration.
Source code
@property def number_of_samples(self): '''Get number of samples, considering overlap. If `sample_size == 0`, all data is returned as a single sample, and so `number_of_samples` will be 1. Otherwise, it checks the number of samples every time it is called, taking `overlap` into consideration. Returns ------- int The number of samples, taking `overlap` into consideration. ''' # If self.sample_size == 0, all data is returned as a single sample if self._sample_size == 0: return 1 # If self.sample_size != 0, check there is at least one sample if self._number_of_points >= self._sample_size: return (self._number_of_points - self._sample_size) // (self.sample_size - self.overlap) + 1 else: return 0
var overlap
-
Get the overlap between every two samples returned by the class.
Returns
int
- The overlap (number of points) between every two samples returned by the class.
Source code
@property def overlap(self): '''Get the overlap between every two samples returned by the class. Returns ------- int The overlap (number of points) between every two samples returned by the class. ''' return self._overlap
var point_data
-
Get the points stored in the class.
Returns
(M, N) numpy.ndarray A memory view of the points stored in
point_data
.Source code
@property def point_data(self): '''Get the points stored in the class. Returns ------- (M, N) numpy.ndarray A memory view of the points stored in `point_data`. ''' return self._point_data
var sample_size
-
Get the number of points in one sample returned by the class.
Returns
int
- The sample size (number of lines) in one sample returned by the class.
Source code
@property def sample_size(self): '''Get the number of points in one sample returned by the class. Returns ------- int The sample size (number of lines) in one sample returned by the class. ''' return self._sample_size
Methods
def all_points_trace(self, size=2, color=None)
-
Get a Plotly trace of all points.
Creates a
plotly.graph_objects.Scatter3d
object. Can then be passed to theplotly.graph_objects.figure.add_trace
function or aPlotlyGrapher
instance using theadd_trace
method.Returns
plotly.graph_objects.Scatter3d
- A
plotly.graph_objects.Scatter3d
trace of all points.
Note
Plotting all points in the case of large LoR arrays is very computationally intensive. For large arrays (> 10000), plotting individual samples using
points_sample_n_traces
is recommended.Source code
def all_points_trace(self, size = 2, color = None): '''Get a Plotly trace of all points. Creates a `plotly.graph_objects.Scatter3d` object. Can then be passed to the `plotly.graph_objects.figure.add_trace` function or a `PlotlyGrapher` instance using the `add_trace` method. Returns ------- plotly.graph_objects.Scatter3d A `plotly.graph_objects.Scatter3d` trace of all points. Note ---- Plotting all points in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `points_sample_n_traces` is recommended. ''' trace = go.Scatter3d( x = self._point_data[:, 1], y = self._point_data[:, 2], z = self._point_data[:, 3], mode = 'markers', marker = dict( size = size, color = color, opacity = 0.8 ) ) return trace
def all_points_trace_colorbar(self, size=2, colorbar_title=None)
-
Get a Plotly trace of all points, colour-coding the last column of
point_data
.Creates a
plotly.graph_objects.Scatter3d
object. Can then be passed to theplotly.graph_objects.figure.add_trace
function or aPlotlyGrapher
instance using theadd_trace
method.Returns
plotly.graph_objects.Scatter3d
- A
plotly.graph_objects.Scatter3d
trace of all points.
Note
Plotting all points in the case of large LoR arrays is very computationally intensive. For large arrays (> 10000), plotting individual samples using
points_sample_n_traces
is recommended.Source code
def all_points_trace_colorbar(self, size = 2, colorbar_title = None): '''Get a Plotly trace of all points, colour-coding the last column of `point_data`. Creates a `plotly.graph_objects.Scatter3d` object. Can then be passed to the `plotly.graph_objects.figure.add_trace` function or a `PlotlyGrapher` instance using the `add_trace` method. Returns ------- plotly.graph_objects.Scatter3d A `plotly.graph_objects.Scatter3d` trace of all points. Note ---- Plotting all points in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `points_sample_n_traces` is recommended. ''' if colorbar_title != None: colorbar = dict(title = colorbar_title) else: colorbar = dict() trace = go.Scatter3d( x = self._point_data[:, 1], y = self._point_data[:, 2], z = self._point_data[:, 3], mode = 'markers', marker = dict( size = size, color = self._point_data[:, -1], colorscale = 'Magma', colorbar = colorbar, opacity = 0.8 ) ) return trace
def plot_all_points(self, ax=None)
-
Plot all points using matplotlib
Given a mpl_toolkits.mplot3d.Axes3D axis, plots all points on it.
Parameters
ax
:mpl_toolkits.mplot3D.Axes3D
object
- The 3D matplotlib-based axis for plotting.
Returns
fig
,ax
:matplotlib
figure
and
axes
objects
Note
Plotting all points in the case of large LoR arrays is very computationally intensive. For large arrays (> 10000), plotting individual samples using
plot_points_sample_n
is recommended.Source code
def plot_all_points(self, ax = None): '''Plot all points using matplotlib Given a **mpl_toolkits.mplot3d.Axes3D** axis, plots all points on it. Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. Returns ------- fig, ax : matplotlib figure and axes objects Note ---- Plotting all points in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `plot_points_sample_n` is recommended. ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() # Scatter x, y, z, [color] x = self._point_data[:, 1], y = self._point_data[:, 2], z = self._point_data[:, 3], color = self._point_data[:, -1], cmap = plt.cm.magma color_array = cmap(colour_data) ax.scatter(x,y,z,c=color_array[0]) return fig, ax
def plot_all_points_alt_axes(self, ax=None)
-
Plot all points using matplotlib on PEPT-style axes
Given a mpl_toolkits.mplot3d.Axes3D axis, plots all points on the PEPT-style convention: x is parallel and horizontal to the screens, y is parallel and vertical to the screens, z is perpendicular to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y)
Parameters
ax
:mpl_toolkits.mplot3D.Axes3D
object
- The 3D matplotlib-based axis for plotting.
Returns
fig
,ax
:matplotlib
figure
and
axes
objects
Note
Plotting all points in the case of large LoR arrays is very computationally intensive. For large arrays (> 10000), plotting individual samples using
plot_lines_sample_n_alt_axes
is recommended.Source code
def plot_all_points_alt_axes(self, ax = None ): '''Plot all points using matplotlib on PEPT-style axes Given a **mpl_toolkits.mplot3d.Axes3D** axis, plots all points on the PEPT-style convention: **x** is *parallel and horizontal* to the screens, **y** is *parallel and vertical* to the screens, **z** is *perpendicular* to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y) Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. Returns ------- fig, ax : matplotlib figure and axes objects Note ---- Plotting all points in the case of large LoR arrays is *very* computationally intensive. For large arrays (> 10000), plotting individual samples using `plot_lines_sample_n_alt_axes` is recommended. ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() # Scatter x, y, z, [color] x = self._point_data[:, 1] y = self._point_data[:, 2] z = self._point_data[:, 3] color = self._point_data[:, -1] cmap = plt.cm.magma color_array = cmap(color) ax.scatter(z,x,y,c=color_array[0]) return fig, ax
def plot_points_sample_n(self, n, ax=None)
-
Plot points from sample
n
using matplotlibGiven a mpl_toolkits.mplot3d.Axes3D axis, plots all points from sample number
n
.Parameters
ax
:mpl_toolkits.mplot3D.Axes3D
object
- The 3D matplotlib-based axis for plotting.
n
:int
- The number of the sample to be plotted.
Returns
fig
,ax
:matplotlib
figure
and
axes
objects
Source code
def plot_points_sample_n(self, n, ax=None): '''Plot points from sample `n` using matplotlib Given a **mpl_toolkits.mplot3d.Axes3D** axis, plots all points from sample number `n`. Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. n : int The number of the sample to be plotted. Returns ------- fig, ax : matplotlib figure and axes objects ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() # Scatter x, y, z, [color] sample = self.sample_n(n) x = sample[:, 1] y = sample[:, 2] z = sample[:, 3] color = sample[:, -1] cmap = plt.cm.magma color_array = cmap(color) ax.scatter(z,x,y,c=color_array[0]) return fig, ax
def plot_points_sample_n_alt_axes(self, n, ax=None)
-
Plot points from sample
n
using matplotlib on PEPT-style axesGiven a mpl_toolkits.mplot3d.Axes3D axis, plots all points from sample number sampleN on the PEPT-style coordinates convention: x is parallel and horizontal to the screens, y is parallel and vertical to the screens, z is perpendicular to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y)
Parameters
ax
:mpl_toolkits.mplot3D.Axes3D
object
- The 3D matplotlib-based axis for plotting.
n
:int
- The number of the sample to be plotted.
Returns
fig
,ax
:matplotlib
figure
and
axes
objects
Source code
def plot_points_sample_n_alt_axes(self, n, ax=None): '''Plot points from sample `n` using matplotlib on PEPT-style axes Given a **mpl_toolkits.mplot3d.Axes3D** axis, plots all points from sample number sampleN on the PEPT-style coordinates convention: **x** is *parallel and horizontal* to the screens, **y** is *parallel and vertical* to the screens, **z** is *perpendicular* to the screens. The mapping relative to the Cartesian coordinates would then be: (x, y, z) -> (z, x, y) Parameters ---------- ax : mpl_toolkits.mplot3D.Axes3D object The 3D matplotlib-based axis for plotting. n : int The number of the sample to be plotted. Returns ------- fig, ax : matplotlib figure and axes objects ''' if ax == None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') else: fig = plt.gcf() # Scatter x, y, z, [color] sample = self.sample_n(n) x = sample[:, 1] y = sample[:, 2] z = sample[:, 3] color = sample[:, -1] cmap = plt.cm.magma color_array = cmap(color) ax.scatter(z,x,y,c=color_array[0]) return fig, ax
def points_sample_n_trace(self, n, size=2, color=None)
-
Get a Plotly trace for all points in sample
n
.Returns a
plotly.graph_objects.Scatter3d
trace containing all points included in sample numbern
. Can then be passed to theplotly.graph_objects.figure.add_trace
function or aPlotlyGrapher
instance using theadd_trace
method.Parameters
n
:int
- The number of the sample to be plotted.
Returns
plotly.graph_object.Scatter3d
- A
plotly.graph_objects.Scatter3d
trace of all points in samplen
.
Source code
def points_sample_n_trace(self, n, size = 2, color = None): '''Get a Plotly trace for all points in sample `n`. Returns a `plotly.graph_objects.Scatter3d` trace containing all points included in sample number `n`. Can then be passed to the `plotly.graph_objects.figure.add_trace` function or a `PlotlyGrapher` instance using the `add_trace` method. Parameters ---------- n : int The number of the sample to be plotted. Returns ------- plotly.graph_object.Scatter3d A `plotly.graph_objects.Scatter3d` trace of all points in sample `n`. ''' sample = self.sample_n(n) trace = go.Scatter3d( x = sample[:, 1], y = sample[:, 2], z = sample[:, 3], mode = 'markers', marker = dict( size = size, color = color, opacity = 0.8 ) ) return trace
def points_sample_n_trace_colorbar(self, n, size=2, colorbar_title=None)
-
Get a Plotly trace for all points in sample
n
, colour-coding the last column.Returns a
plotly.graph_objects.Scatter3d
trace containing all points included in sample numbern
. Can then be passed to theplotly.graph_objects.figure.add_trace
function or aPlotlyGrapher
instance using theadd_trace
method.Parameters
n
:int
- The number of the sample to be plotted.
Returns
plotly.graph_object.Scatter3d
- A
plotly.graph_objects.Scatter3d
trace of all points in samplen
.
Source code
def points_sample_n_trace_colorbar(self, n, size = 2, colorbar_title = None): '''Get a Plotly trace for all points in sample `n`, colour-coding the last column. Returns a `plotly.graph_objects.Scatter3d` trace containing all points included in sample number `n`. Can then be passed to the `plotly.graph_objects.figure.add_trace` function or a `PlotlyGrapher` instance using the `add_trace` method. Parameters ---------- n : int The number of the sample to be plotted. Returns ------- plotly.graph_object.Scatter3d A `plotly.graph_objects.Scatter3d` trace of all points in sample `n`. ''' if colorbar_title != None: colorbar = dict(title = colorbar_title) else: colorbar = dict() sample = self.sample_n(n) trace = go.Scatter3d( x = sample[:, 1], y = sample[:, 2], z = sample[:, 3], mode = 'markers', marker = dict( size = size, color = sample[:, -1], colorscale = 'Magma', colorbar = colorbar, opacity = 0.8 ) ) return trace
def sample_n(self, n)
-
Get sample number n (indexed from 1, i.e.
n > 0
)Returns the lines from
point_data
included in sample numbern
. Samples are numbered starting from 1.Parameters
n
:int
- The number of the sample required. Note that
1 <= n <= number_of_samples
.
Returns
(M, N) numpy.ndarray A shallow copy of the points from
point_data
included in sample number n.Raises
IndexError
- If
sample_size == 0
, all data is returned as one single sample. Raised ifn
is not 1. IndexError
- If
n > number_of_samples
orn <= 0
.
Source code
def sample_n(self, n): '''Get sample number n (indexed from 1, i.e. `n > 0`) Returns the lines from `point_data` included in sample number `n`. Samples are numbered starting from 1. Parameters ---------- n : int The number of the sample required. Note that `1 <= n <= number_of_samples`. Returns ------- (M, N) numpy.ndarray A shallow copy of the points from `point_data` included in sample number n. Raises ------ IndexError If `sample_size == 0`, all data is returned as one single sample. Raised if `n` is not 1. IndexError If `n > number_of_samples` or `n <= 0`. ''' if self._sample_size == 0: if n == 1: return self._point_data else: raise IndexError("\n\n[ERROR]: Trying to access a non-existent sample (samples indexed from 1): asked for sample number {}, when there is only 1 sample (sample_size == 0)\n".format(n)) elif (n > self.number_of_samples) or n <= 0: raise IndexError("\n\n[ERROR]: Trying to access a non-existent sample (samples are indexed from 1): asked for sample number {}, when there are {} samples\n".format(n, self.number_of_samples)) start_index = (n - 1) * (self._sample_size - self._overlap) return self._point_data[start_index:(start_index + self._sample_size)]
def to_csv(self, filepath, delimiter=' ', newline='\n')
-
Write
point_data
to a CSV fileWrite all points (and any extra data) stored in the class to a CSV file. Parameters ---------- filepath : filename or file handle If filepath is a path (rather than file handle), it is relative to where python is called. delimiter : str, optional The delimiter between values. The default is two spaces ' ', such that numbers in the format '123,456.78' are well-understood. newline : str, optional The sequence of characters at the end of every line. The default is a new line '
'
Source code
def to_csv(self, filepath, delimiter = ' ', newline = '\n'): '''Write `point_data` to a CSV file Write all points (and any extra data) stored in the class to a CSV file. Parameters ---------- filepath : filename or file handle If filepath is a path (rather than file handle), it is relative to where python is called. delimiter : str, optional The delimiter between values. The default is two spaces ' ', such that numbers in the format '123,456.78' are well-understood. newline : str, optional The sequence of characters at the end of every line. The default is a new line '\n' ''' np.savetxt(filepath, self._point_data, delimiter = delimiter, newline = newline)