DASPy-toolbox 1.0.0__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.
Files changed (49) hide show
  1. DASPy_toolbox-1.0.0.dist-info/LICENSE.txt +1 -0
  2. DASPy_toolbox-1.0.0.dist-info/METADATA +85 -0
  3. DASPy_toolbox-1.0.0.dist-info/RECORD +49 -0
  4. DASPy_toolbox-1.0.0.dist-info/WHEEL +5 -0
  5. DASPy_toolbox-1.0.0.dist-info/entry_points.txt +2 -0
  6. DASPy_toolbox-1.0.0.dist-info/top_level.txt +1 -0
  7. daspy/__init__.py +4 -0
  8. daspy/advanced_tools/__init__.py +0 -0
  9. daspy/advanced_tools/channel.py +354 -0
  10. daspy/advanced_tools/decomposition.py +165 -0
  11. daspy/advanced_tools/denoising.py +276 -0
  12. daspy/advanced_tools/fdct.py +789 -0
  13. daspy/advanced_tools/strain2vel.py +245 -0
  14. daspy/basic_tools/__init__.py +0 -0
  15. daspy/basic_tools/filter.py +257 -0
  16. daspy/basic_tools/freqattributes.py +117 -0
  17. daspy/basic_tools/preprocessing.py +238 -0
  18. daspy/basic_tools/visualization.py +186 -0
  19. daspy/core/__init__.py +4 -0
  20. daspy/core/collection.py +279 -0
  21. daspy/core/dasdatetime.py +72 -0
  22. daspy/core/example.pkl +0 -0
  23. daspy/core/make_example.py +32 -0
  24. daspy/core/read.py +544 -0
  25. daspy/core/section.py +1319 -0
  26. daspy/core/write.py +282 -0
  27. daspy/seismic_detection/__init__.py +1 -0
  28. daspy/seismic_detection/calc_travel_time.py +23 -0
  29. daspy/seismic_detection/core.py +119 -0
  30. daspy/seismic_detection/detection.py +12 -0
  31. daspy/seismic_detection/gamma/__init__.py +13 -0
  32. daspy/seismic_detection/gamma/_base.py +549 -0
  33. daspy/seismic_detection/gamma/_bayesian_mixture.py +875 -0
  34. daspy/seismic_detection/gamma/_gaussian_mixture.py +866 -0
  35. daspy/seismic_detection/gamma/app.py +192 -0
  36. daspy/seismic_detection/gamma/seismic_ops.py +478 -0
  37. daspy/seismic_detection/gamma/utils.py +512 -0
  38. daspy/seismic_detection/location.py +266 -0
  39. daspy/seismic_detection/magnitude.py +43 -0
  40. daspy/seismic_detection/phase_picking.py +67 -0
  41. daspy/structure_imaging/__init__.py +0 -0
  42. daspy/structure_imaging/ambient_noise.py +4 -0
  43. daspy/structure_imaging/dispersion.py +27 -0
  44. daspy/structure_imaging/fault_zone.py +59 -0
  45. daspy/structure_imaging/inversion.py +6 -0
  46. daspy/traffic_monitoring/JamDetection.py +6 -0
  47. daspy/traffic_monitoring/SpeedMeasurement.py +6 -0
  48. daspy/traffic_monitoring/VehicleDetection.py +6 -0
  49. daspy/traffic_monitoring/__init__.py +0 -0
@@ -0,0 +1,266 @@
1
+ # Purpose: Locate earthquake location with body wave travel time
2
+ # Author: Minzhe Hu
3
+ # Date: 2024.6.5
4
+ # Email: hmz2018@mail.ustc.edu.cn
5
+ import os
6
+ import multiprocessing
7
+ import numpy as np
8
+ from pathlib import Path
9
+ from numpy.lib.stride_tricks import as_strided
10
+ from tqdm import tqdm
11
+ from obspy.taup.tau_model import TauModel
12
+ from obspy.taup import TauPyModel
13
+ from obspy.taup.taup_create import build_taup_model
14
+ from obspy.geodetics import locations2degrees
15
+
16
+
17
+
18
+ def calc_travel_time(model, elo, ela, edep, slo, sla, sdep):
19
+ deg = locations2degrees(ela, elo, sla, slo)
20
+ arrival = model.get_travel_times(source_depth_in_km=edep,
21
+ receiver_depth_in_km=sdep,
22
+ distance_in_degree=deg,
23
+ phase_list=['p','P','s','S'])
24
+
25
+ return arrival[0].time, arrival[1].time
26
+
27
+ def sliding_window_sum(matrix, half_win):
28
+ shape = matrix.shape
29
+ app = np.zeros((len(matrix), half_win))
30
+ matrix = np.hstack((app, matrix, app))
31
+ strides = (matrix.strides[1], matrix.strides[0], matrix.strides[1])
32
+ windowed = as_strided(matrix[0:-2*half_win],
33
+ shape= (shape[1], shape[0], 2*half_win+1),
34
+ strides=strides)
35
+ return np.sum(windowed, axis=(1,2))
36
+
37
+ def calc_max(pick_map, delay, mode='PS'):
38
+ nch, nt = pick_map.shape
39
+ if mode == 'P':
40
+ delay = delay[:, 0]
41
+ min_sp = delay.min()
42
+ max_sp = delay.max()
43
+ hotmap = np.zeros((nch, nt+max_sp-min_sp))
44
+ for ch in range(nch):
45
+ hotmap[ch, max_sp-delay[ch]:max_sp-delay[ch]+nt] = pick_map[ch]
46
+ elif mode == 'S':
47
+ delay = delay[:, 1]
48
+ min_sp = delay.min()
49
+ max_sp = delay.max()
50
+ hotmap = np.zeros((nch, nt+max_sp-min_sp))
51
+ for ch in range(nch):
52
+ hotmap[ch, max_sp-delay[ch]:max_sp-delay[ch]+nt] = pick_map[ch]
53
+ else:
54
+ min_sp = delay.min()
55
+ max_sp = delay.max()
56
+ hotmap = np.zeros((nch, nt+max_sp-min_sp))
57
+ for ch in range(nch):
58
+ hotmap[ch, max_sp-delay[ch,0]:max_sp-delay[ch,0]+nt] += pick_map[ch]
59
+ hotmap[ch, max_sp-delay[ch,1]:max_sp-delay[ch,1]+nt] += pick_map[ch]
60
+
61
+ intensity = sliding_window_sum(hotmap, 5)
62
+ return np.argmax(intensity)-max_sp, np.max(intensity)
63
+
64
+
65
+ class Gird_Searcher(object):
66
+ def __init__(self, geometry, grid_lon, grid_lat, grid_dep,
67
+ model=None, filepath='./'):
68
+ if isinstance(model, TauPyModel):
69
+ self.model = model
70
+ model = model.model
71
+ elif isinstance(model, TauModel):
72
+ self.model = TauPyModel()
73
+ self.model.model = model
74
+ else:
75
+ model = TauModel.from_file(model)
76
+ self.model = TauPyModel()
77
+ self.model.model = model
78
+ # model.serialize(os.path.join(filepath, 'model.npz'))
79
+
80
+ if len(geometry[0]) == 2:
81
+ geometry = np.insert(geometry, 2, values=0, axis=1).shape
82
+
83
+ min_depth = min(min(grid_dep), geometry[:,2].min())
84
+ if min_depth < 0 :
85
+ self.depth_correction = -geometry[:,2].min()
86
+ grid_dep += self.depth_correction
87
+ geometry[:,2] += self.depth_correction
88
+ else:
89
+ self.depth_correction = 0
90
+ np.save(os.path.join(filepath, 'geometry.npy'), geometry)
91
+ self.geometry = geometry
92
+ self.grid_lon = grid_lon
93
+ self.grid_lat = grid_lat
94
+ self.grid_dep = grid_dep
95
+ self.nx, self.ny, self.nz = len(grid_lon), len(grid_lat), len(grid_dep)
96
+ self.filepath = filepath
97
+
98
+ def fit_map(self, pick_map, fs, n=5, mode='PS'):
99
+ self.mode = mode
100
+ self.intensity = np.zeros((self.nx, self.ny, self.nz))
101
+ self.rel_time = np.zeros((self.nx, self.ny, self.nz))
102
+ if n:
103
+ self.n = n
104
+ self.idx_lon = np.round(np.linspace(0, self.nx-1, n)).astype(int)
105
+ self.idx_lat = np.round(np.linspace(0, self.ny-1, n)).astype(int)
106
+ self.idx_dep = np.round(np.linspace(0, self.nz-1, n)).astype(int)
107
+ rd = 1
108
+ while len(self.idx_lon) + len(self.idx_lat) + len(self.idx_lon) > 9:
109
+ print(f'Grid seaching... Round {rd}')
110
+ self.calc_grid(pick_map, fs)
111
+ self._update_index()
112
+ rd += 1
113
+ print(f'Grid seaching... Round {rd}')
114
+ self.calc_grid(pick_map, fs)
115
+ else:
116
+ self.idx_lon = range(self.nx)
117
+ self.idx_lat = range(self.ny)
118
+ self.idx_dep = range(self.nz)
119
+ self.calc_grid(pick_map, fs)
120
+ idx = np.unravel_index(np.argmax(self.intensity), self.intensity.shape)
121
+ self.loc_idx = idx
122
+ return *self.ijk2lonlatdep(*idx), self.rel_time[idx]
123
+
124
+ def calc_grid(self, pick_map, fs):
125
+ for i in self.idx_lon:
126
+ elo = self.grid_lon[i]
127
+ for j in self.idx_lat:
128
+ ela = self.grid_lat[j]
129
+ for k in self.idx_dep:
130
+ edep = self.grid_dep[k]
131
+ if self.intensity[i, j, k] != 0:
132
+ continue
133
+ filename = os.path.join(self.filepath, f'{i}_{j}_{k}.npy')
134
+ if not os.path.exists(filename):
135
+ print(f'Calculating travel time for grid {i}, {j}, {k}.')
136
+ pyfile = Path(__file__).parent / 'calc_travel_time.py'
137
+ os.system(f'python {pyfile} {self.filepath} {elo} {ela} {edep} {i}_{j}_{k}.npy')
138
+
139
+ delay = np.load(filename)
140
+ delay = np.round(delay * fs).astype(int)
141
+ dsp, itst = calc_max(pick_map, delay, mode=self.mode)
142
+ self.intensity[i, j, k] = itst
143
+ self.rel_time[i, j, k] = dsp / fs
144
+
145
+ def _update_index(self):
146
+ idx = np.unravel_index(np.argmax(self.intensity), self.intensity.shape)
147
+ ii = list(self.idx_lon).index(idx[0])
148
+ s, e = self.idx_lon[max(ii-1, 0)], self.idx_lon[min(ii+1, len(self.idx_lon)-1)]
149
+ self.idx_lon = self._new_idx(s, e)
150
+ jj = list(self.idx_lat).index(idx[1])
151
+ s, e = self.idx_lat[max(jj-1, 0)], self.idx_lat[min(jj+1, len(self.idx_lat)-1)]
152
+ self.idx_lat = self._new_idx(s, e)
153
+ kk = list(self.idx_dep).index(idx[2])
154
+ s, e = self.idx_dep[max(kk-1, 0)], self.idx_dep[min(kk+1, len(self.idx_dep)-1)]
155
+ self.idx_dep = self._new_idx(s, e)
156
+ return self
157
+
158
+ def _new_idx(self, s, e):
159
+ n = e - s
160
+ if n >= 4:
161
+ return np.round(np.linspace(s, e, self.n)).astype(int)
162
+ else:
163
+ return list(range(s, e+1))
164
+
165
+ def lonlatdep2ijk(self, lon, lat, dep):
166
+ i = np.argmin(abs(self.grid_lon - lon))
167
+ j = np.argmin(abs(self.grid_lat - lat))
168
+ k = np.argmin(abs((self.grid_dep-self.depth_correction) - dep))
169
+ return i, j, k
170
+
171
+ def ijk2lonlatdep(self, i, j, k):
172
+ return self.grid_lon[i], self.grid_lat[j], self.grid_dep[k]-self.depth_correction
173
+
174
+
175
+
176
+ def model_initialization(fname):
177
+
178
+ """
179
+ Load a custom model. It will create a '.npz' file if imput file is '.nd' or
180
+ '.tvel'.
181
+ """
182
+ suffix = os.path.splitext(fname)[0]
183
+ if not suffix[1] == '.npz':
184
+ build_taup_model(fname, output_folder=suffix[0] + '.npz')
185
+ fname = suffix[0] + '.npz'
186
+ print(fname + ' created')
187
+
188
+ model = TauPyModel()
189
+ model.model = TauModel.from_file(fname)
190
+ return model
191
+
192
+
193
+ def xy2deg(y1, x1, y2, x2):
194
+ return np.sqrt((x1 - x2)**2 + (y1 - y2)**2) / 6371
195
+
196
+
197
+ def time_difference(elo, ela, edep, geometry, t_obs,
198
+ phase, model, coordsys='lonlat'):
199
+ """
200
+ Culculate the difference of observed arrival time and theoretical arrival
201
+ time list of 1 grid point.
202
+ """
203
+ if coordsys == 'lonlat':
204
+ coord2deg = locations2degrees
205
+ elif coordsys == 'xy':
206
+ coord2deg = xy2deg
207
+ else:
208
+ raise ValueError("Coordsys must be one of 'lonlat', 'xy'")
209
+ stations = len(geometry)
210
+ dt = t_obs
211
+ for i in range(stations):
212
+ deg = coord2deg(ela, elo, geometry[i, 1], geometry[i, 0])
213
+ if phase[i] in ['p', 'P', 0]:
214
+ phs = ['p', 'P']
215
+ elif phase[i] in ['s', 'S', 1]:
216
+ phs = ['s', 'S']
217
+ else:
218
+ raise ValueError("Phase must be 'P' or 'S'")
219
+ arrivals = model.get_travel_times(source_depth_in_km=edep,
220
+ receiver_depth_in_km=geometry[i, 2],
221
+ distance_in_degree=deg,
222
+ phase_list=phs)
223
+ dt[i] -= arrivals[0].time
224
+ return dt
225
+
226
+
227
+ def grid_search(lonlst, latlst, deplst, geometry, t_obs, phase, weight=None,
228
+ model=TauPyModel(model='iasp91'), coordsys='lonlat',
229
+ time_correction=True, norm_ord=1, parallel=True):
230
+ """
231
+ Grid search for 1 event
232
+ """
233
+ X, Y, Z = np.meshgrid(lonlst, latlst, deplst)
234
+ grid = np.array([X.flatten(), Y.flatten(), Z.flatten()]).transpose()
235
+ stations = len(geometry)
236
+ if len(geometry[0]) == 2:
237
+ geometry = np.concatenate((geometry, np.zeros((stations, 1))), axis=1)
238
+ if parallel:
239
+ cores = multiprocessing.cpu_count() - 1
240
+ pool = multiprocessing.Pool(processes=cores)
241
+ tasks = [[elo, ela, edep, geometry, t_obs, phase, model, coordsys] for
242
+ (elo, ela, edep) in grid]
243
+ dt = pool.starmap(time_difference, tasks)
244
+ else:
245
+ dt = np.zeros((len(grid), stations))
246
+ for i in tqdm(range(len(grid))):
247
+ dt[i] = time_difference(grid[i, 0], grid[i, 1], grid[i, 2],
248
+ geometry, t_obs, phase, model, coordsys)
249
+ if not weight:
250
+ weight = np.ones(len(geometry))
251
+ if time_correction:
252
+ if norm_ord == 1:
253
+ dtau = np.median(dt, axis=0)
254
+ elif norm_ord == 2:
255
+ dtau = np.mean(dt, axis=0)
256
+ else:
257
+ raise ValueError("Order of the norm must be 'P' or 'S'")
258
+ residual = np.linalg.norm(dt - dtau, ord=norm_ord, axis=0)
259
+ pos = np.argmin(residual)
260
+ elo, ela, edep = grid[pos]
261
+ return [elo, ela, edep, residual[pos], dtau[pos]]
262
+ else:
263
+ residual = np.linalg.norm(dt, ord=norm_ord, axis=0)
264
+ pos = np.argmin(residual)
265
+ elo, ela, edep = grid[pos]
266
+ return [elo, ela, edep, residual[pos]]
@@ -0,0 +1,43 @@
1
+ # Purpose: Magnitude estimation
2
+ # Author: Minzhe Hu
3
+ # Date: 2023.8.26
4
+ # Email: hmz2018@mail.ustc.edu.cn
5
+ import numpy as np
6
+
7
+
8
+ def _phase2ab(phase):
9
+ if phase in ['p', 'P', 0]:
10
+ a = 0.437
11
+ b = 1.269
12
+ elif phase in ['s', 'S', 1]:
13
+ a = 0.690
14
+ b = 1.588
15
+ else:
16
+ raise ValueError("Phase must be 'P' or 'S'")
17
+ return a, b
18
+
19
+
20
+ def fit_K(M, E, D, phase, ord=2):
21
+ '''
22
+ Fit Ki for n channels with m events.
23
+ :param M: 1D array. magnitude for each event
24
+ :param E: m*n array or float. peak amplitude of stain rate
25
+ :param D: m*n array or float. hypocentral distance
26
+ :param phase: only for p or s wave
27
+ '''
28
+ a, b = _phase2ab(phase)
29
+ M = np.tile(M, (len(D[0]), 1)).T
30
+ K = np.sum((np.log10(E) - a * M - b * D), 0)
31
+ return K
32
+
33
+
34
+ def magnitude(E, D, K, phase):
35
+ '''
36
+ Estimate magnitude for one or multiple events.
37
+ :param E: 1-D array or float. peak amplitude of stain rate
38
+ :param D: 1-D array or float. hypocentral distance
39
+ :param K: 1-D array or float. site calibration terms
40
+ :param phase: only for p or s wave
41
+ '''
42
+ a, b = _phase2ab(phase)
43
+ return (np.log10(E) - b * np.log10(D) - K) / a
@@ -0,0 +1,67 @@
1
+ # Purpose: Pick phases for events
2
+ # Author: Minzhe Hu
3
+ # Date: 2024.8.30
4
+ # Email: hmz2018@mail.ustc.edu.cn
5
+ import numpy as np
6
+ from scipy.stats import kurtosis, skew
7
+ from obspy.signal.trigger import classic_sta_lta, trigger_onset
8
+ from daspy.basic_tools.preprocessing import normalization
9
+
10
+
11
+ def sta_lta_map(data, fs, sw=0.5, lw=5):
12
+ cft = np.zeros_like(data)
13
+ for ch, d in enumerate(data):
14
+ cft[ch] = classic_sta_lta(d, nsta=round(sw * fs), nlta=round(lw * fs))
15
+
16
+ return cft
17
+
18
+ def kurto_map(data, fs, win=3, diff=True, norm=True):
19
+ nch, nt = data.shape
20
+ w = round(win * fs)
21
+ kts = np.zeros((nch, nt-w))
22
+ for t in range(w, nt):
23
+ kts[:, t - w] = kurtosis(data[:, t - w:t], axis=1)
24
+
25
+ pre_nt = w
26
+ if diff:
27
+ kts = np.abs(np.diff(kts, axis=1))
28
+ pre_nt += 1
29
+ if norm:
30
+ kts = normalization(kts)
31
+
32
+ kts = np.hstack((np.zeros((nch, pre_nt)), kts))
33
+ return kts
34
+
35
+ def skew_map(data, fs, win=3, diff=True, norm=True):
36
+ nch, nt = data.shape
37
+ w = round(win * fs)
38
+ kts = np.zeros((nch, nt-w))
39
+ for t in range(w, nt):
40
+ kts[:, t - w] = skew(data[:, t - w:t], axis=1)
41
+
42
+ pre_nt = w
43
+ if diff:
44
+ kts = np.abs(np.diff(kts, axis=1))
45
+ pre_nt += 1
46
+ if norm:
47
+ kts = normalization(kts)
48
+
49
+ kts = np.hstack((np.zeros((nch, pre_nt)), kts))
50
+ return kts
51
+
52
+
53
+ def map_picking(hot_map, thres1=5, thres2=5, choose_max=True, min_dsp=None):
54
+ pick = []
55
+ for ch, arr in enumerate(hot_map):
56
+ onsets = trigger_onset(arr, thres1=thres1, thres2=thres2)
57
+ if len(onsets) != 0:
58
+ for s, e in onsets:
59
+ if min_dsp is not None:
60
+ if e - s < min_dsp:
61
+ continue
62
+ if choose_max:
63
+ pick.append([ch, np.argmax(arr[s:e+1]) + s])
64
+ else:
65
+ pick.append([ch, s])
66
+
67
+ return np.array(pick)
File without changes
@@ -0,0 +1,4 @@
1
+ # Purpose:
2
+ # Author:
3
+ # Date:
4
+ # Email:
@@ -0,0 +1,27 @@
1
+ # Purpose: Some preprocess methods
2
+ # Author: Minzhe Hu, Hanwen Zou
3
+ # Date: 2023.8.27
4
+ # Email: hmz2018@mail.ustc.edu.cn
5
+ import numpy as np
6
+ from scipy.special import hankel1
7
+ from daspy.basic_tools.freqattributes import spectrum
8
+
9
+
10
+ def fj_method(data, dx, fs, nfft='default', f=None, v=np.arange(100, 3000, 5),
11
+ verbose=False):
12
+ x = np.arange(1, len(data) + 1) * dx
13
+ spec, ff = spectrum(data, fs, tpad=nfft)
14
+ if f is None:
15
+ f = ff
16
+
17
+ # F-J method
18
+ dispen = np.zeros((len(f), len(v)), complex)
19
+ for i, fi in enumerate(f):
20
+ for j, vj in enumerate(v):
21
+ k = 2 * np.pi * fi / vj
22
+ bssh1 = hankel1(0, k * x)
23
+ c = spec[:, round(fi / (ff[1] - ff[0]))]
24
+ dispen[i, j] = np.sum(c * bssh1 * x)
25
+ if verbose:
26
+ return dispen, f, v
27
+ return dispen
@@ -0,0 +1,59 @@
1
+ # Purpose: Finding the source of the fault scattered waves from the waveforms recorded by DAS
2
+ # Author: Minzhe Hu
3
+ # Date: 2023.8.27
4
+ # Email: hmz2018@mail.ustc.edu.cn
5
+ import multiprocessing
6
+ import numpy as np
7
+ from math import ceil
8
+ from daspy.advanced_tools.decomposition import fk_filter
9
+
10
+
11
+ def csb_stacking(data, lent, slope):
12
+ """
13
+ Use coherence- and symmetry-based method to stack scattered wave from 1
14
+ channel location.
15
+ """
16
+ nx, nt = data.shape
17
+ nv = len(slope)
18
+ lenx = int((nx - 1) / 2)
19
+ t_ex = lent + ceil(lenx * max(slope))
20
+ data = np.hstack((data, np.zeros((nx, t_ex))))
21
+
22
+ intensity = np.zeros(nt)
23
+ for i in range(nt):
24
+ left = np.zeros((nv, lent))
25
+ right = np.zeros((nv, lent))
26
+ for j, s in enumerate(slope):
27
+ for k in range(lenx + 1):
28
+ start = i + round(k * s)
29
+ left[j] += data[lenx - k, start:start + lent]
30
+ right[j] += data[lenx + k, start:start + lent]
31
+ intensity[i] = np.sum(abs(left * right), axis=1).max()
32
+ return intensity
33
+
34
+
35
+ def fault_detection(raw_data, dx, fs, winx=200, wint=1, vmin=200, vmax=700,
36
+ nv=30, fk=True):
37
+ """
38
+ Obtain the spatiotemporal distribution of scattering intensity and velocity
39
+ from the waveform.
40
+ """
41
+ data = raw_data.copy()
42
+ if fk:
43
+ data = fk_filter(data, dx, fs, vmin=vmin, vmax=vmax)
44
+
45
+ nx, nt = data.shape
46
+ lenx = round(winx / dx)
47
+ lent = round(wint * fs)
48
+ data = np.r_[np.zeros((lenx, nt)), data, np.zeros((lenx, nt))]
49
+
50
+ cores = multiprocessing.cpu_count() - 1
51
+ pool = multiprocessing.Pool(processes=cores)
52
+
53
+ slope = 1 / np.linspace(vmin, vmax, nv) * dx * fs
54
+ tasks = []
55
+ for i in range(nx):
56
+ tasks.append((data[i:i + 2 * lenx + 1], lent, slope))
57
+
58
+ intensity = np.array(pool.starmap(csb_stacking, tasks))
59
+ return intensity
@@ -0,0 +1,6 @@
1
+ # Purpose:
2
+ # Usage:
3
+ # Author:
4
+ # Date:
5
+ # Email:
6
+
@@ -0,0 +1,6 @@
1
+ # Purpose:
2
+ # Usage:
3
+ # Author:
4
+ # Date:
5
+ # Email:
6
+
@@ -0,0 +1,6 @@
1
+ # Purpose:
2
+ # Usage:
3
+ # Author:
4
+ # Date:
5
+ # Email:
6
+
@@ -0,0 +1,6 @@
1
+ # Purpose:
2
+ # Usage:
3
+ # Author:
4
+ # Date:
5
+ # Email:
6
+
File without changes