vectorose 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vectorose/__init__.py +15 -0
- vectorose/io.py +400 -0
- vectorose/mock_data.py +458 -0
- vectorose/plotting.py +2549 -0
- vectorose/polar_data.py +315 -0
- vectorose/sphere_base.py +693 -0
- vectorose/stats.py +921 -0
- vectorose/tregenza_sphere.py +935 -0
- vectorose/triangle_sphere.py +177 -0
- vectorose/util.py +839 -0
- vectorose-0.2.1.dist-info/LICENSE +22 -0
- vectorose-0.2.1.dist-info/METADATA +295 -0
- vectorose-0.2.1.dist-info/RECORD +14 -0
- vectorose-0.2.1.dist-info/WHEEL +4 -0
vectorose/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""VectoRose: A new tool for visualising and analysing directional data."""
|
|
2
|
+
|
|
3
|
+
from vectorose import polar_data
|
|
4
|
+
from vectorose import plotting
|
|
5
|
+
from vectorose import io
|
|
6
|
+
from vectorose import triangle_sphere
|
|
7
|
+
from vectorose import tregenza_sphere
|
|
8
|
+
from vectorose import util
|
|
9
|
+
from vectorose import stats
|
|
10
|
+
|
|
11
|
+
from vectorose.sphere_base import SphereBase
|
|
12
|
+
|
|
13
|
+
from importlib_metadata import version
|
|
14
|
+
|
|
15
|
+
__version__ = version("vectorose")
|
vectorose/io.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# Copyright (c) 2023-, Benjamin Rudski, Joseph Deering
|
|
2
|
+
#
|
|
3
|
+
# This code is licensed under the MIT License. See the `LICENSE` file for
|
|
4
|
+
# more details about copying.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Functions for import and export.
|
|
8
|
+
|
|
9
|
+
This module provides the ability to load vector fields from file and save
|
|
10
|
+
vector fields and vector rose histogram data.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import enum
|
|
14
|
+
import os
|
|
15
|
+
from typing import Any, Dict, Optional, Sequence, Type, Union
|
|
16
|
+
|
|
17
|
+
import matplotlib.animation
|
|
18
|
+
import numpy as np
|
|
19
|
+
import pandas as pd
|
|
20
|
+
|
|
21
|
+
DEFAULT_LOCATION_COLUMNS = (0, 1, 2)
|
|
22
|
+
"""Default column numbers for the location coordinates in the order
|
|
23
|
+
``(x, y, z)``."""
|
|
24
|
+
|
|
25
|
+
DEFAULT_COMPONENT_COLUMNS = (-3, -2, -1)
|
|
26
|
+
"""Default column numbers for the vector components in the order
|
|
27
|
+
``(vx, vy, vz)``."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class VectorFileType(enum.Enum):
|
|
31
|
+
"""File types for numeric data.
|
|
32
|
+
|
|
33
|
+
Numeric data may be imported and exported in a number of different
|
|
34
|
+
formats. This enumerated type allows the user to specify which file
|
|
35
|
+
type they would like to use to load or store numeric data, such as
|
|
36
|
+
vector lists and binning arrays. The associated strings for each member
|
|
37
|
+
are the file extension **without a dot**.
|
|
38
|
+
|
|
39
|
+
Members
|
|
40
|
+
-------
|
|
41
|
+
CSV
|
|
42
|
+
Comma-separated value file, in which the columns will be separated
|
|
43
|
+
by a tab "\\t". File extension: ``*.csv``.
|
|
44
|
+
NPY
|
|
45
|
+
NumPy array, which can easily be loaded into NumPy. File extension:
|
|
46
|
+
``*.npy``.
|
|
47
|
+
EXCEL
|
|
48
|
+
Microsoft Excel spreadsheet (compatible with Excel 2007 or later).
|
|
49
|
+
File extension: ``*.xlsx``.
|
|
50
|
+
|
|
51
|
+
Warnings
|
|
52
|
+
--------
|
|
53
|
+
When constructing a filename using the members of this type, a dot
|
|
54
|
+
``(.)`` must be added.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
CSV = "csv"
|
|
58
|
+
NPY = "npy"
|
|
59
|
+
EXCEL = "xlsx"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ImageFileType(enum.Enum):
|
|
63
|
+
"""Image File Types.
|
|
64
|
+
|
|
65
|
+
File types for images. These include both raster formats (``*.png`` and
|
|
66
|
+
``*.tiff``) and vector formats (``*.svg`` and ``*.pdf``). The members
|
|
67
|
+
of this enumerated type have as value the string extensions for the
|
|
68
|
+
respective file types **without** the dot.
|
|
69
|
+
|
|
70
|
+
Members
|
|
71
|
+
-------
|
|
72
|
+
PNG
|
|
73
|
+
Portable Network Graphics (png) image (raster).
|
|
74
|
+
TIFF
|
|
75
|
+
Tagged Image File Format (tiff) image (raster).
|
|
76
|
+
SVG
|
|
77
|
+
Scalable Vector Graphic (svg) image (vector).
|
|
78
|
+
PDF
|
|
79
|
+
Portable Document Format (pdf) file (vector).
|
|
80
|
+
|
|
81
|
+
Warnings
|
|
82
|
+
--------
|
|
83
|
+
When constructing a filename using the members of this type, a dot
|
|
84
|
+
``(.)`` must be added.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
PNG = "png"
|
|
88
|
+
TIFF = "tiff"
|
|
89
|
+
SVG = "svg"
|
|
90
|
+
PDF = "pdf"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class VideoFileType(enum.Enum):
|
|
94
|
+
"""Video File Types.
|
|
95
|
+
|
|
96
|
+
File types for videos and animations. The raw values for the members of
|
|
97
|
+
this enumerated type correspond to the respective file extensions
|
|
98
|
+
**without** a period.
|
|
99
|
+
|
|
100
|
+
Members
|
|
101
|
+
-------
|
|
102
|
+
MP4
|
|
103
|
+
Moving Picture Experts Group (MPEG) 4 video format.
|
|
104
|
+
GIF
|
|
105
|
+
Graphics Interchange Format animated image (regardless of whether
|
|
106
|
+
you pronounce if G-IF or J-IF).
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
Warnings
|
|
110
|
+
--------
|
|
111
|
+
When constructing a filename using the members of this type, a dot
|
|
112
|
+
``(.)`` must be added.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
MP4 = "mp4"
|
|
116
|
+
GIF = "gif"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def __infer_filetype_from_filename(
|
|
120
|
+
filename: str, file_type_enum: Type[enum.Enum]
|
|
121
|
+
) -> Optional[enum.Enum]:
|
|
122
|
+
"""Infer a file type from a filename.
|
|
123
|
+
|
|
124
|
+
This function tries to infer a file type, of the provided enumerated
|
|
125
|
+
type ``file_type_enum`` from a provided filename by checking the
|
|
126
|
+
extension. If no valid extension is found, ``None`` is returned.
|
|
127
|
+
Otherwise, the determined file type is returned.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
filename
|
|
132
|
+
String containing the filename.
|
|
133
|
+
file_type_enum
|
|
134
|
+
Enumerated type representing the desired file type. This enumerated
|
|
135
|
+
type should have string values representing various file
|
|
136
|
+
extensions. These values **should not** contain a dot.
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
file_type_enum or None:
|
|
141
|
+
Member of ``file_type_enum`` if a valid file type is found.
|
|
142
|
+
Otherwise, ``None``.
|
|
143
|
+
|
|
144
|
+
See Also
|
|
145
|
+
--------
|
|
146
|
+
ImageFileType: Sample enumerated types to pass in for image files.
|
|
147
|
+
VectorFileType: Sample enumerated types for vector data files.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
# Separate out the file extension
|
|
151
|
+
basename, extension = os.path.splitext(filename)
|
|
152
|
+
|
|
153
|
+
# Remove the dot from the extension.
|
|
154
|
+
cleaned_extension = extension.lstrip(".")
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
# Try to get the file type based on the extension.
|
|
158
|
+
file_type = file_type_enum(cleaned_extension)
|
|
159
|
+
except ValueError:
|
|
160
|
+
# Otherwise, no filetype found.
|
|
161
|
+
file_type = None
|
|
162
|
+
|
|
163
|
+
return file_type
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def __infer_vector_filetype_from_filename(
|
|
167
|
+
filename: str,
|
|
168
|
+
) -> Optional[VectorFileType]:
|
|
169
|
+
"""Infer a vector field file type from a filename.
|
|
170
|
+
|
|
171
|
+
This function tries to infer a :class:`VectorFileType` from a provided
|
|
172
|
+
filename by checking the extension. If no valid extension is found,
|
|
173
|
+
:class:`None` is returned. Otherwise, the determined vector type is
|
|
174
|
+
returned.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
filename
|
|
179
|
+
String containing the filename.
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
VectorFileType or None:
|
|
184
|
+
Vector file type corresponding to the filename if a valid filetype
|
|
185
|
+
is found. Otherwise, :class:`None`.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
vector_file_type = __infer_filetype_from_filename(
|
|
189
|
+
filename=filename, file_type_enum=VectorFileType
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return vector_file_type
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def __infer_video_filetype_from_filename(
|
|
196
|
+
filename: str,
|
|
197
|
+
) -> Optional[VideoFileType]:
|
|
198
|
+
"""Infer a video file type from a filename.
|
|
199
|
+
|
|
200
|
+
This function tries to infer a :class:`VideoFileType` from a provided
|
|
201
|
+
filename by checking the extension. If no valid extension is found,
|
|
202
|
+
:class:`None` is returned. Otherwise, the determined vector type is
|
|
203
|
+
returned.
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
filename
|
|
208
|
+
String containing the filename.
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
VideoFileType or None:
|
|
213
|
+
Video file type corresponding to the filename if a valid filetype
|
|
214
|
+
is found. Otherwise, :class:`None`.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
video_file_type = __infer_filetype_from_filename(
|
|
218
|
+
filename=filename, file_type_enum=VideoFileType
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return video_file_type
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def import_vector_field(
|
|
225
|
+
filepath: str,
|
|
226
|
+
default_file_type: VectorFileType = VectorFileType.NPY,
|
|
227
|
+
contains_headers: bool = False,
|
|
228
|
+
sheet: Optional[Union[str, int]] = None,
|
|
229
|
+
location_columns: Optional[Sequence[int]] = DEFAULT_LOCATION_COLUMNS,
|
|
230
|
+
component_columns: Sequence[int] = DEFAULT_COMPONENT_COLUMNS,
|
|
231
|
+
component_axis: int = -1,
|
|
232
|
+
separator: str = "\t",
|
|
233
|
+
) -> Optional[np.ndarray]:
|
|
234
|
+
"""Import a vector field.
|
|
235
|
+
|
|
236
|
+
Load a vector field from a file into a NumPy array. For available
|
|
237
|
+
file formats, see :class:`VectorFileType`. The file type is inferred
|
|
238
|
+
from the filename. If it cannot be inferred, the ``default_file_type``
|
|
239
|
+
is tried. If the vector field is not valid, then :class:`None` is
|
|
240
|
+
returned.
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
filepath
|
|
245
|
+
File path to the vector field file.
|
|
246
|
+
default_file_type
|
|
247
|
+
File type to attempt if the type cannot be inferred from the
|
|
248
|
+
filename.
|
|
249
|
+
contains_headers
|
|
250
|
+
Indicate whether the file contains headers. This option is only
|
|
251
|
+
considered if the vectors are in a CSV or Excel file.
|
|
252
|
+
sheet
|
|
253
|
+
Name or index of the sheet to consider if the vectors are in an
|
|
254
|
+
Excel file.
|
|
255
|
+
location_columns
|
|
256
|
+
Column indices for the vector *spatial coordinates* in the order
|
|
257
|
+
``(x, y, z)``. If this is set to :class:`None`, the vectors are
|
|
258
|
+
assumed to be located at the origin. By default, the first three
|
|
259
|
+
columns are assumed to refer to ``(x, y, z)``, respectively.
|
|
260
|
+
component_columns
|
|
261
|
+
Column indices referring to the vector *components* in the order
|
|
262
|
+
``(vx, vy, vz)``. By default, the last three columns
|
|
263
|
+
``(-3, -2, -1)`` are assumed to be the ``(vx, vy, vz)``.
|
|
264
|
+
component_axis
|
|
265
|
+
Axis along which the components are defined, in the case of a NumPy
|
|
266
|
+
array which has more than 2 dimensions.
|
|
267
|
+
separator
|
|
268
|
+
Column separator to use if the vector field is a CSV file.
|
|
269
|
+
|
|
270
|
+
Returns
|
|
271
|
+
-------
|
|
272
|
+
numpy.ndarray or None
|
|
273
|
+
NumPy array containing the vectors. The array has shape
|
|
274
|
+
``(n, 3)`` or ``(n, 6)``, depending on whether the locations
|
|
275
|
+
are included. The columns correspond to ``(x,y,z)`` coordinates
|
|
276
|
+
of the location (if available), followed by ``(vx, vy, vz)``
|
|
277
|
+
components. If the filetype cannot be properly inferred,
|
|
278
|
+
a value of ``None`` is returned instead.
|
|
279
|
+
|
|
280
|
+
Raises
|
|
281
|
+
------
|
|
282
|
+
OSError
|
|
283
|
+
The requested file does not exist.
|
|
284
|
+
ValueError
|
|
285
|
+
The requested file cannot be parsed.
|
|
286
|
+
|
|
287
|
+
Notes
|
|
288
|
+
-----
|
|
289
|
+
If the vector field file passed contains an array with a dimension > 2,
|
|
290
|
+
then the components are assumed to be along the axis passed in the
|
|
291
|
+
argument `component_axis` and the array will be flattened to 2D.
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
# First, infer the file type from the filename
|
|
295
|
+
filetype = __infer_vector_filetype_from_filename(filepath)
|
|
296
|
+
|
|
297
|
+
# If inference fails, try the default file type.
|
|
298
|
+
if filetype is None:
|
|
299
|
+
filetype = default_file_type
|
|
300
|
+
|
|
301
|
+
if filetype is VectorFileType.NPY:
|
|
302
|
+
vector_field: np.ndarray = np.load(filepath)
|
|
303
|
+
|
|
304
|
+
# Check the dimension of the vector field array
|
|
305
|
+
if vector_field.ndim > 2:
|
|
306
|
+
# Get the vector dimension
|
|
307
|
+
d = vector_field.shape[component_axis]
|
|
308
|
+
|
|
309
|
+
# Flatten the array
|
|
310
|
+
vector_field = np.moveaxis(vector_field, component_axis, -1).reshape(
|
|
311
|
+
-1, d
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Remove any rows containing NaN
|
|
315
|
+
vector_field = vector_field[~np.any(np.isnan(vector_field), axis=1)]
|
|
316
|
+
|
|
317
|
+
# Use Pandas in the other cases
|
|
318
|
+
else:
|
|
319
|
+
header_row: Optional[int] = 0 if contains_headers else None
|
|
320
|
+
# Reading function depends on whether CSV or Excel
|
|
321
|
+
if filetype is VectorFileType.CSV:
|
|
322
|
+
vector_field_dataframe = pd.read_csv(
|
|
323
|
+
filepath, header=header_row, sep=separator
|
|
324
|
+
)
|
|
325
|
+
elif filetype is VectorFileType.EXCEL:
|
|
326
|
+
vector_field_dataframe = pd.read_excel(
|
|
327
|
+
filepath, sheet_name=sheet, header=header_row
|
|
328
|
+
)
|
|
329
|
+
else:
|
|
330
|
+
return None
|
|
331
|
+
|
|
332
|
+
# Remove NaN values
|
|
333
|
+
vector_field = vector_field_dataframe.dropna().to_numpy()
|
|
334
|
+
|
|
335
|
+
n, d = vector_field.shape
|
|
336
|
+
|
|
337
|
+
# Now, for the column parsing
|
|
338
|
+
if location_columns is None or d < 6:
|
|
339
|
+
# No location, only consider the components
|
|
340
|
+
clean_vector_field = vector_field[:, component_columns]
|
|
341
|
+
else:
|
|
342
|
+
# Consider both the location and the components.
|
|
343
|
+
column_indices = list(location_columns) + list(component_columns)
|
|
344
|
+
|
|
345
|
+
# Squeeze is necessary to not break type safety.
|
|
346
|
+
clean_vector_field = vector_field[:, column_indices]
|
|
347
|
+
|
|
348
|
+
# Convert the vector field to have high-precision floating point type
|
|
349
|
+
clean_vector_field = clean_vector_field.astype(float)
|
|
350
|
+
|
|
351
|
+
return clean_vector_field
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def export_mpl_animation(
|
|
355
|
+
animation: matplotlib.animation.Animation,
|
|
356
|
+
filename: str,
|
|
357
|
+
file_type: VideoFileType = VideoFileType.MP4,
|
|
358
|
+
dpi: Optional[int] = 150,
|
|
359
|
+
fps: Optional[int] = None,
|
|
360
|
+
**export_kwargs: Dict[str, Any],
|
|
361
|
+
):
|
|
362
|
+
"""Export a Matplotlib animation.
|
|
363
|
+
|
|
364
|
+
Export a provided Matplotlib animation as a video.
|
|
365
|
+
|
|
366
|
+
Parameters
|
|
367
|
+
----------
|
|
368
|
+
animation
|
|
369
|
+
The :class:`matplotlib.animation.Animation` animation to export.
|
|
370
|
+
filename
|
|
371
|
+
The destination for the video export. This filename will be used to
|
|
372
|
+
infer the export file type.
|
|
373
|
+
file_type
|
|
374
|
+
The default filetype to consider if unable to resolve the file type
|
|
375
|
+
from the file name.
|
|
376
|
+
dpi
|
|
377
|
+
Resolution of the exported video in dots-per-inch (DPI).
|
|
378
|
+
fps
|
|
379
|
+
Desired frame rate in the exported video.
|
|
380
|
+
export_kwargs
|
|
381
|
+
Additional keyword arguments
|
|
382
|
+
to :meth:`matplotlib.animation.Animation.save`.
|
|
383
|
+
|
|
384
|
+
See Also
|
|
385
|
+
--------
|
|
386
|
+
matplotlib.animation.Animation.save:
|
|
387
|
+
The function called to perform the actual export.
|
|
388
|
+
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
# Infer the file type from the filename
|
|
392
|
+
inferred_type = __infer_video_filetype_from_filename(filename)
|
|
393
|
+
|
|
394
|
+
if inferred_type is None:
|
|
395
|
+
filename = f"{filename}.{file_type.value}"
|
|
396
|
+
|
|
397
|
+
if fps == 0:
|
|
398
|
+
fps = None
|
|
399
|
+
|
|
400
|
+
animation.save(filename=filename, fps=fps, dpi=dpi, **export_kwargs)
|