pyIntensityFeatures 0.1.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 (30) hide show
  1. pyIntensityFeatures/__init__.py +30 -0
  2. pyIntensityFeatures/_main.py +500 -0
  3. pyIntensityFeatures/instruments/__init__.py +9 -0
  4. pyIntensityFeatures/instruments/satellites.py +137 -0
  5. pyIntensityFeatures/proc/__init__.py +10 -0
  6. pyIntensityFeatures/proc/boundaries.py +420 -0
  7. pyIntensityFeatures/proc/fitting.py +374 -0
  8. pyIntensityFeatures/proc/intensity.py +251 -0
  9. pyIntensityFeatures/tests/__init__.py +1 -0
  10. pyIntensityFeatures/tests/test_instruments_satellites.py +210 -0
  11. pyIntensityFeatures/tests/test_main.py +734 -0
  12. pyIntensityFeatures/tests/test_proc_boundaries.py +613 -0
  13. pyIntensityFeatures/tests/test_proc_fitting.py +218 -0
  14. pyIntensityFeatures/tests/test_proc_intensity.py +205 -0
  15. pyIntensityFeatures/tests/test_utils_checks.py +933 -0
  16. pyIntensityFeatures/tests/test_utils_coords.py +197 -0
  17. pyIntensityFeatures/tests/test_utils_distributions.py +236 -0
  18. pyIntensityFeatures/tests/test_utils_grids.py +189 -0
  19. pyIntensityFeatures/tests/test_utils_output.py +433 -0
  20. pyIntensityFeatures/utils/__init__.py +13 -0
  21. pyIntensityFeatures/utils/checks.py +420 -0
  22. pyIntensityFeatures/utils/coords.py +157 -0
  23. pyIntensityFeatures/utils/distributions.py +199 -0
  24. pyIntensityFeatures/utils/grids.py +113 -0
  25. pyIntensityFeatures/utils/output.py +276 -0
  26. pyintensityfeatures-0.1.0.dist-info/METADATA +360 -0
  27. pyintensityfeatures-0.1.0.dist-info/RECORD +30 -0
  28. pyintensityfeatures-0.1.0.dist-info/WHEEL +5 -0
  29. pyintensityfeatures-0.1.0.dist-info/licenses/LICENSE +28 -0
  30. pyintensityfeatures-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # Full license can be found in License.md
4
+ #
5
+ # DISTRIBUTION STATEMENT A: Approved for public release. Distribution is
6
+ # unlimited.
7
+ # ----------------------------------------------------------------------------
8
+ """Package to identify intensity features in imager data."""
9
+
10
+ import logging
11
+
12
+ try:
13
+ from importlib import metadata
14
+ except ImportError:
15
+ import importlib_metadata as metadata
16
+
17
+ # Define a logger object to allow easier log handling
18
+ logging.raiseExceptions = False
19
+ logger = logging.getLogger('pyIntensityFeatures_logger')
20
+
21
+ # Import the package modules and top-level classes
22
+ from pyIntensityFeatures import _main # noqa F401
23
+ from pyIntensityFeatures import instruments # noqa F401
24
+ from pyIntensityFeatures import proc # noqa F401
25
+ from pyIntensityFeatures import utils # noqa F401
26
+
27
+ from pyIntensityFeatures._main import AuroralBounds # noqa F401
28
+
29
+ # Define the global variables
30
+ __version__ = metadata.version('pyIntensityFeatures')
@@ -0,0 +1,500 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # DISTRIBUTION STATEMENT A: Approved for public release. Distribution is
5
+ # unlimited.
6
+ # -----------------------------------------------------------------------------
7
+ """Main classes and functions for the package."""
8
+
9
+ import datetime as dt
10
+ from functools import partial
11
+ import numpy as np
12
+ import pandas as pds
13
+ import xarray as xr
14
+
15
+ from pyIntensityFeatures import logger
16
+ from pyIntensityFeatures import proc
17
+ from pyIntensityFeatures.instruments import satellites
18
+ from pyIntensityFeatures import utils
19
+
20
+
21
+ class AuroralBounds(object):
22
+ """Manage intensity data and obtain auroral luminosity boundaries.
23
+
24
+ Parameters
25
+ ----------
26
+ inst_data : object
27
+ Instrument data of any type (e.g., pysat.Instrument, xr.Dataset,
28
+ pds.DataFrame, dict of arrays) with appropriate observations.
29
+ time_var : str or int
30
+ Time key, attribute, variable name, or index
31
+ glon_var : str
32
+ Geographic longitude key, attribute, variable name, or index
33
+ glat_var : str
34
+ Geographic latitude key, attribute, variable name, or index
35
+ intensity_var : str
36
+ Observed intensity key, attribute, variable name, or index
37
+ alt : float or int
38
+ Altitude in km at which the aurora is observed
39
+ transpose : bool or dict
40
+ Flag that indicates whether or not the 2D data accessed by `glon_var`,
41
+ `glat_var`, and `intensity_var` needs to be transposed. If a dict is
42
+ supplied, the keys should correspond the the different variable names
43
+ and allows different flags for each set of data. (default=False)
44
+ opt_coords : dict or NoneType
45
+ Dict of coordinates to include in `boundaries` or None to only have
46
+ the required coordinates (default=None)
47
+ hemipshere : int
48
+ Integer denoting hemisphere, 1 for North and -1 for South (default=1)
49
+ stime : object or NoneType
50
+ Time object or None to use earliest time from `inst_data`
51
+ (default=None)
52
+ etime : object or NoneType
53
+ Time object or None to use latest time from `inst_data` (default=None)
54
+ slice_func : function
55
+ Appropriate function to retrieve an auroral image slice
56
+ (default=pyIntensityFeatures.instruments.satellites.get_auroral_slice)
57
+ slice_kwargs : dict or NoneType
58
+ Kwargs to be used by `slice_func` (default=None)
59
+ clean_func : function or NoneType
60
+ Appropriate function to clean the intensity data (default=None)
61
+ clean_kwargs : dict or NoneType
62
+ Kwargs to be used by `clean_func` (default=None)
63
+
64
+ Attributes
65
+ ----------
66
+ inst_data
67
+ time_var
68
+ glon_var
69
+ glat_var
70
+ intensity_var
71
+ alt
72
+ opt_coords
73
+ hemipshere
74
+ stime
75
+ etime
76
+ slice_func
77
+ clean_func
78
+ boundaries : xr.Dataset or NoneType
79
+ Dataset with Auroral Luminosity Boundaries or NoneType if not run
80
+
81
+ Methods
82
+ -------
83
+ set_boundaries
84
+ Set the `boundaries` attribute using the assigned data.
85
+ update_times
86
+ Update `stime` and `etime` attributes to reflect current data in
87
+ `inst_data`, if available.
88
+
89
+ """
90
+
91
+ def __init__(self, inst_data, time_var, glon_var, glat_var, intensity_var,
92
+ alt, transpose=False, opt_coords=None, hemisphere=1,
93
+ stime=None, etime=None,
94
+ slice_func=satellites.get_auroral_slice, slice_kwargs=None,
95
+ clean_func=None, clean_kwargs=None):
96
+ """Set up the class attributes."""
97
+
98
+ # Set the data object and altitude
99
+ self.inst_data = inst_data
100
+ self.alt = alt
101
+
102
+ # Set the data access variables
103
+ self.time_var = time_var
104
+ self.glon_var = glon_var
105
+ self.glat_var = glat_var
106
+ self.intensity_var = intensity_var
107
+
108
+ # Set the transpose flag for the intensity/location data
109
+ if hasattr(transpose, 'keys'):
110
+ self.transpose = transpose
111
+ trans_default = False # Set the default for unspecified variables
112
+ else:
113
+ # All variables will be set to the same flag value
114
+ self.transpose = {}
115
+ trans_default = transpose
116
+
117
+ for tkey in [self.glon_var, self.glat_var, self.intensity_var]:
118
+ if tkey not in self.transpose.keys():
119
+ # Update the unset variables
120
+ self.transpose[tkey] = trans_default
121
+
122
+ # Set the hemisphere and coordinates
123
+ self.hemisphere = hemisphere
124
+
125
+ if opt_coords is None:
126
+ self.opt_coords = {'hemisphere': self.hemisphere}
127
+ else:
128
+ self.opt_coords = opt_coords
129
+ self.opt_coords['hemisphere'] = self.hemisphere
130
+
131
+ # Set the starting and ending times
132
+ self.stime = stime
133
+ self.etime = etime
134
+
135
+ # Set the instrument-specific functions
136
+ if slice_kwargs is None:
137
+ slice_kwargs = {}
138
+
139
+ if slice_func is None:
140
+ logger.info('slice function not provided, data must be pre-sliced.')
141
+ self.slice_func = None
142
+ else:
143
+ self.slice_func = partial(slice_func, **slice_kwargs)
144
+
145
+ if clean_kwargs is None:
146
+ clean_kwargs = {}
147
+
148
+ if clean_func is None:
149
+ self.clean_func = None
150
+ else:
151
+ self.clean_func = partial(clean_func, **clean_kwargs)
152
+
153
+ # Initalize the boundary object
154
+ self.boundaries = None
155
+
156
+ return
157
+
158
+ def __eq__(self, other):
159
+ """Perform equality check.
160
+
161
+ Parameters
162
+ ----------
163
+ other : any
164
+ Other object to compare for equality
165
+
166
+ Returns
167
+ -------
168
+ bool
169
+ True if objects are identical, False if they are not.
170
+
171
+ """
172
+ # Check if other is the same class
173
+ if not isinstance(other, self.__class__):
174
+ logger.info("wrong class")
175
+ return False
176
+
177
+ # Check to see if other has the same and equal attributes
178
+ for attr in self.__dict__.keys():
179
+ attr_flag = False
180
+ if hasattr(other, attr):
181
+ self_val = getattr(self, attr)
182
+ other_val = getattr(other, attr)
183
+
184
+ if attr.find('inst_data') == 0:
185
+ # This is the Instrument data, different comparisons are
186
+ # needed for known supported data types
187
+ if isinstance(self_val, xr.Dataset) or isinstance(
188
+ self_val, pds.DataFrame):
189
+ attr_flag = self_val.equals(other_val)
190
+ else:
191
+ attr_flag = np.all(self_val == other_val)
192
+ elif attr.find('func') < 0:
193
+ # This is an integer, boolean or string
194
+ attr_flag = self_val == other_val
195
+ else:
196
+ # This is a partial function, ensure it has all the
197
+ # necessary attributes
198
+ attr_flag = str(self_val) == str(other_val)
199
+ else:
200
+ logger.info("object is missing attribute: {:}".format(attr))
201
+ return attr_flag
202
+
203
+ if not attr_flag:
204
+ # Exit upon first inequality
205
+ logger.info("attribute {:} differs".format(attr))
206
+ return attr_flag
207
+
208
+ # Confirm that other object doesn't have extra terms
209
+ for attr in other.__dict__.keys():
210
+ if attr not in self.__dict__.keys():
211
+ logger.info("object contains extra attribute: {:}".format(attr))
212
+ return False
213
+
214
+ return True
215
+
216
+ def __repr__(self):
217
+ """Print basic class properties."""
218
+ out_str = "".join(["pyIntensityFeatures.AuroralBounds(",
219
+ ", ".join([repr(self.inst_data),
220
+ repr(self.time_var),
221
+ repr(self.glon_var), repr(self.glat_var),
222
+ repr(self.intensity_var),
223
+ repr(self.alt)]),
224
+ ", transpose=", repr(self.transpose),
225
+ ", opt_coords=", repr(self.opt_coords),
226
+ ", hemisphere=", repr(self.hemisphere), ", stime=",
227
+ repr(self.stime), ", etime=", repr(self.etime),
228
+ ", slice_func=", repr(self.slice_func),
229
+ ", clean_func=", repr(self.clean_func), ")"])
230
+ return out_str
231
+
232
+ def __str__(self):
233
+ """Descriptively print the class properties."""
234
+
235
+ out_str = "\n".join(["Auroral Boundary object",
236
+ "=======================",
237
+ "Instrument Data: {:}".format(self.inst_data),
238
+ "\nData Variables",
239
+ "--------------",
240
+ "Time: {:s}".format(self.time_var),
241
+ "Geo Lon: {:s}".format(self.glon_var),
242
+ "Geo Lat: {:s}".format(self.glat_var),
243
+ "Intensity: {:s}".format(self.intensity_var),
244
+ "Transpose: {:}".format(self.transpose),
245
+ "\nCoordinate Attributes",
246
+ "---------------------",
247
+ "Hemisphere: {:d}".format(self.hemisphere),
248
+ "Altitude: {:.2f} km".format(self.alt),
249
+ "Optional coords: {:}".format(self.opt_coords),
250
+ "Start time: {:}".format(self.stime),
251
+ "End time: {:}".format(self.etime),
252
+ "\nInstrument Functions",
253
+ "--------------------",
254
+ "Slicing: {:}".format(self.slice_func),
255
+ "Cleaning: {:}".format(self.clean_func)])
256
+
257
+ return out_str
258
+
259
+ def _get_variable(self, var):
260
+ """Get the data variable data as an array from the data object.
261
+
262
+ Parameters
263
+ ----------
264
+ var : str
265
+ Data variable name
266
+
267
+ Returns
268
+ -------
269
+ ret_data :array-like
270
+ numpy array of the desired data.
271
+
272
+ Notes
273
+ -----
274
+ Uses the `transpose` attribute to determine whether or not returned
275
+ data will be transposed.
276
+
277
+ """
278
+
279
+ if hasattr(self.inst_data, str(var)):
280
+ # Retrieve the time attribute and cast as a numpy array
281
+ ret_data = np.array(getattr(self.inst_data, str(var)))
282
+ else:
283
+ try:
284
+ # Retrieve the data variable as a key
285
+ ret_data = np.array(self.inst_data[var])
286
+ except (TypeError, KeyError, ValueError, IndexError):
287
+ logger.warning("".join(["unable to retrieve ", repr(var), " ",
288
+ "from `inst_data`, data may be empty"]))
289
+ ret_data = None
290
+
291
+ # Transpose the data if desired
292
+ if var in self.transpose.keys() and ret_data is not None:
293
+ if self.transpose[var]:
294
+ ret_data = ret_data.transpose()
295
+
296
+ return ret_data
297
+
298
+ @property
299
+ def alt(self):
300
+ """Altitude in km at which the intensity data is located."""
301
+ return self._alt
302
+
303
+ @alt.setter
304
+ def alt(self, new_alt=-1):
305
+ self._alt = new_alt
306
+
307
+ @property
308
+ def hemisphere(self):
309
+ """Hemisphere for data processing (1 for North, -1 for South)."""
310
+ return self._hemisphere
311
+
312
+ @hemisphere.setter
313
+ def hemisphere(self, new_hemisphere=1):
314
+ # Also update the `opt_coords` attribute dict, if it exists and
315
+ # contains hemisphere information.
316
+ if hasattr(self, 'opt_coords'):
317
+ opt_dict = getattr(self, 'opt_coords')
318
+
319
+ if 'hemisphere' in opt_dict.keys():
320
+ opt_dict['hemisphere'] = new_hemisphere
321
+ setattr(self, 'opt_coords', opt_dict)
322
+
323
+ self._hemisphere = new_hemisphere
324
+
325
+ @property
326
+ def stime(self):
327
+ """Starting time for loaded data."""
328
+ return self._stime
329
+
330
+ @stime.setter
331
+ def stime(self, new_stime=None):
332
+ if new_stime is None:
333
+ self._stime = self._get_variable(self.time_var)
334
+
335
+ if self._stime is not None:
336
+ self._stime = utils.coords.as_datetime(
337
+ self._stime.flatten().min())
338
+ else:
339
+ self._stime = utils.coords.as_datetime(new_stime)
340
+
341
+ @property
342
+ def etime(self):
343
+ """Ending time for loaded data."""
344
+ return self._etime
345
+
346
+ @etime.setter
347
+ def etime(self, new_etime=None):
348
+ if new_etime is None:
349
+ self._etime = self._get_variable(self.time_var)
350
+
351
+ if self._etime is not None:
352
+ self._etime = utils.coords.as_datetime(
353
+ self._etime.flatten().max())
354
+ else:
355
+ self._etime = utils.coords.as_datetime(new_etime)
356
+
357
+ def set_boundaries(self, min_mlat_base=59.0, mag_method='ALLOWTRACE',
358
+ mlat_inc=1.0, mlt_inc=0.5, un_threshold=1.25,
359
+ dayglow_threshold=300.0, strict_fit=False,
360
+ lt_out_bin=5.0, max_iqr=1.5):
361
+ """Set `boundaries` with auroral boundaries from intensity data.
362
+
363
+ Parameters
364
+ ----------
365
+ min_mlat_base : float
366
+ Base minimum co-latitude for intensity profiles. (default=59.0)
367
+ method : str
368
+ Method for converting between geographic and magnetic coordinates.
369
+ (default='ALLOWTRACE')
370
+ mlat_inc : float
371
+ Magnetic latitude increment for gridding intensity. (default=1.0)
372
+ mlt_inc : float
373
+ Magnetic local time increment for gridding intensity. (default=0.5)
374
+ un_threshold : float
375
+ Maximum acceptable uncertainty value in degrees (default=1.25)
376
+ dayglow_threshold : float
377
+ Minimum allowable background intensity value in Rayleighs
378
+ (default=300.0)
379
+ strict_fit : bool
380
+ Enforce positive values for the x-offsets in quadratic-Gaussian fits
381
+ (default=False)
382
+ lt_out_bin : float
383
+ Size of local time bin in hours over which outliers in the data
384
+ will be identified (default=5.0)
385
+ max_iqr : float
386
+ Maximum multiplier for the interquartile range (IQR) used to
387
+ identify outliers above or below the upper or lower quartile
388
+ (default=1.5)
389
+
390
+ See Also
391
+ --------
392
+ utils.coords.convert_geo_to_mag
393
+
394
+ Notes
395
+ -----
396
+ Luminosity boundary identification finding and verification based on,
397
+ but not identical to, the method developed by Longden, et al. (2010).
398
+
399
+ References
400
+ ----------
401
+ Longden, N. S., et al. (2010) Estimating the location of the open-closed
402
+ magnetic field line boundary from auroral images, 28 (9), p 1659-1678,
403
+ doi:10.5194/angeo-28-1659-2010.
404
+
405
+ """
406
+ # Get the time, location, and intensity data
407
+ time_data = self._get_variable(self.time_var)
408
+ glat_data = self._get_variable(self.glat_var)
409
+ glon_data = self._get_variable(self.glon_var)
410
+ intensity_data = self._get_variable(self.intensity_var)
411
+
412
+ for name, var in [(self.time_var, time_data),
413
+ (self.glat_var, glat_data),
414
+ (self.glon_var, glon_data),
415
+ (self.intensity_var, intensity_data)]:
416
+ if var is None:
417
+ logger.info("Missing {:} data, cannot set boundaries".format(
418
+ name))
419
+ return
420
+
421
+ # Initalize the dicts for holding coordinate and boundary data
422
+ attr_dict = {'min_mlat_base': min_mlat_base, 'mag_method': mag_method,
423
+ 'mlat_inc': mlat_inc, 'mlt_inc': mlt_inc,
424
+ 'un_threshold': un_threshold, 'max_iqr': max_iqr,
425
+ 'strict_fit': int(strict_fit), 'lt_out_bin': lt_out_bin}
426
+ coord_dict, data_dict = utils.output.init_boundary_dicts(
427
+ opt_coords=self.opt_coords)
428
+ max_coeff = 0
429
+
430
+ # Clean the intensity data
431
+ if self.clean_func is not None:
432
+ clean_mask = self.clean_func(self.inst_data)
433
+ else:
434
+ clean_mask = None
435
+
436
+ # Cycle through the desired date range
437
+ ctime = utils.coords.as_datetime(self.stime)
438
+ while ctime < utils.coords.as_datetime(self.etime):
439
+ # Get the next auroral intensity slice
440
+ if self.slice_func is not None:
441
+ # Use provided function to select desired data
442
+ intensity, glat, glon, sweep_times = self.slice_func(
443
+ time_data, glat_data, glon_data, intensity_data,
444
+ clean_mask=clean_mask, start_time=ctime,
445
+ hemisphere=self.hemisphere)
446
+ else:
447
+ # No slicing function provided, assume data is pre-sliced
448
+ intensity = intensity_data
449
+ glat = glat_data
450
+ glon = glon_data
451
+ sweep_times = [time_data[0], time_data[-1]]
452
+
453
+ # Get the next auroral slice and boundaries
454
+ (sweep_end, sweep_data,
455
+ max_coeff) = proc.intensity.find_intensity_boundaries(
456
+ intensity, glat, glon, sweep_times, self.alt,
457
+ min_mlat_base, max_coeff, method=mag_method,
458
+ mlat_inc=mlat_inc, mlt_inc=mlt_inc, un_threshold=un_threshold,
459
+ dayglow_threshold=dayglow_threshold, strict_fit=strict_fit)
460
+
461
+ logger.info("Auroral slice at {:} {:s} data".format(
462
+ sweep_end, "without" if sweep_data is None else "with"))
463
+
464
+ if sweep_end < ctime:
465
+ # We've reached the end of the data, but it's not triggering
466
+ # the escape condition
467
+ break
468
+
469
+ # Save the output for this sweep
470
+ utils.output.update_boundary_dicts(sweep_data, coord_dict,
471
+ data_dict)
472
+
473
+ # Cycle the time to start looking for the next slice
474
+ ctime = utils.coords.as_datetime(sweep_end) + dt.timedelta(
475
+ seconds=1)
476
+
477
+ # Reshape the data and cast as xarray
478
+ self.boundaries = utils.output.convert_boundary_dict(
479
+ coord_dict, data_dict, max_coeff, attr_dict=attr_dict)
480
+
481
+ # If there is data, update the boundaries, otherwise inform the user
482
+ if len(self.boundaries) > 0:
483
+ # Remove boundary outliers from each slice
484
+ logger.info("Removing boundary outliers from {:} to {:}.".format(
485
+ self.stime, self.etime))
486
+ self.boundaries = utils.checks.evaluate_boundary_in_mlt(
487
+ self.boundaries, "eq_bounds", "po_bounds", "mlt",
488
+ "sweep_start", lt_bin=lt_out_bin, max_iqr=max_iqr)
489
+ else:
490
+ logger.info("No boundary data found from {:} to {:}.".format(
491
+ self.stime, self.etime))
492
+
493
+ return
494
+
495
+ def update_times(self):
496
+ """Update `stime` and `etime` based on potentially updated data."""
497
+ # Setting to NoneType triggers the "get value from data" feature
498
+ self.stime = None
499
+ self.etime = None
500
+ return
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # DISTRIBUTION STATEMENT A: Approved for public release. Distribution is
5
+ # unlimited.
6
+ # -----------------------------------------------------------------------------
7
+ """pyIntensityFeatures instrument modules."""
8
+
9
+ from pyIntensityFeatures.instruments import satellites # noqa F401
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # DISTRIBUTION STATEMENT A: Approved for public release. Distribution is
5
+ # unlimited.
6
+ # -----------------------------------------------------------------------------
7
+ """Satellite instrument support functions."""
8
+
9
+ import numpy as np
10
+
11
+
12
+ def get_auroral_slice(time_data, glat_data, glon_data, int_data,
13
+ clean_mask=None, start_time=None, hemisphere=1,
14
+ min_colat=45):
15
+ """Retrieve an auroral image slice that spans the desired lat range.
16
+
17
+ Parameters
18
+ ----------
19
+ time_data : array-like
20
+ 1D array of time indexes
21
+ glat_data : array-like
22
+ 2D array of geographic latitudes
23
+ glon_data : array-like
24
+ 2D array of geographic longitudes
25
+ int_data : array-like
26
+ 2D array of intensity values
27
+ clean_mask : array-like or NoneType
28
+ None to create a mask of finite values or 2D mask array specifying
29
+ good values (default=None)
30
+ start_time : dt.datetime or NoneType
31
+ Start time to search from or None to start from beginning of
32
+ available data (default=None)
33
+ hemisphere : int
34
+ Hemisphere to consider, where 1 is North and -1 is South (default=1)
35
+ min_colat : int or float
36
+ Absolute value of the most equatorward latitude to include (default=45)
37
+
38
+ Returns
39
+ -------
40
+ intensity : array-like
41
+ Array with dimensions of time x sweep-locations containing the
42
+ rectified intensity at the auroral daytime pierce point
43
+ glat : array-like
44
+ Array with dimensions of time x sweep-locations containing the
45
+ geodetic latitude
46
+ glon : array-like
47
+ Array with dimensions of time x sweep-locations containing the
48
+ geographic longitude
49
+ sweep_times : array-like
50
+ Array with the starting and ending times of the auroral sweep. Gives
51
+ the start and end times searched if no sweep is found.
52
+
53
+ Raises
54
+ ------
55
+ ValueError
56
+ If imager data does not have the same shape
57
+
58
+ """
59
+ # Set the start time
60
+ if start_time is None:
61
+ start_time = time_data[0]
62
+ itime = 0
63
+ else:
64
+ start_time = np.datetime64(start_time)
65
+ time_data = np.asarray(time_data).astype(np.datetime64)
66
+ itime = int(abs(start_time - time_data).argmin())
67
+
68
+ if time_data[itime] < start_time:
69
+ itime += 1
70
+
71
+ # Create the data mask, if desired
72
+ if clean_mask is None:
73
+ clean_mask = np.isfinite(int_data)
74
+
75
+ # Ensure the data arrays are shaped as expected
76
+ if np.asarray(time_data).shape[0] != int_data.shape[0]:
77
+ raise ValueError('first dimension of intensity data differs from time')
78
+
79
+ if int_data.shape != glat_data.shape or int_data.shape != glon_data.shape:
80
+ raise ValueError('intensity and location input shapes differ')
81
+
82
+ if clean_mask.shape != int_data.shape:
83
+ raise ValueError('clean mask shape differs from intensity data')
84
+
85
+ # Find the start of an auroral sweep
86
+ while itime < len(time_data):
87
+ if (glat_data[itime] * hemisphere >= min_colat).any():
88
+ if clean_mask[itime].any():
89
+ # If there is no clean data at this time, keep looking
90
+ break
91
+
92
+ # Cycle to the next time
93
+ itime += 1
94
+
95
+ if itime < len(time_data):
96
+ # Prepare the output
97
+ time_inds = list()
98
+ sweep_times = list()
99
+
100
+ # Found an auroral sweep in the correct hemisphere
101
+ while itime < len(time_data) and np.any(
102
+ glat_data[itime] * hemisphere >= min_colat):
103
+ if clean_mask[itime].any():
104
+ time_inds.append(itime)
105
+
106
+ if len(sweep_times) < 2:
107
+ sweep_times.append(time_data[itime])
108
+ else:
109
+ sweep_times[-1] = time_data[itime]
110
+
111
+ # Cycle the time index
112
+ itime += 1
113
+
114
+ # If the sweep ended without capturing sufficient data, set the stop
115
+ # time
116
+ if len(sweep_times) < 2:
117
+ if itime >= len(time_data):
118
+ itime = -1
119
+
120
+ if len(sweep_times) == 0:
121
+ # If no times were found, insert the start time
122
+ sweep_times.append(start_time)
123
+
124
+ sweep_times.append(time_data[itime])
125
+
126
+ # The sweep has ended, select the desired time indices
127
+ intensity = np.asarray(int_data[time_inds])
128
+ glat = np.asarray(glat_data[time_inds])
129
+ glon = np.asarray(glon_data[time_inds])
130
+ sweep_times = np.asarray(sweep_times)
131
+ else:
132
+ intensity = np.asarray([])
133
+ glat = np.asarray([])
134
+ glon = np.asarray([])
135
+ sweep_times = np.asarray([start_time, time_data[-1]])
136
+
137
+ return intensity, glat, glon, sweep_times
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # DISTRIBUTION STATEMENT A: Approved for public release. Distribution is
5
+ # unlimited.
6
+ # -----------------------------------------------------------------------------
7
+ """pyIntensityFeatures processing modules."""
8
+
9
+ from pyIntensityFeatures.proc import boundaries # noqa F401
10
+ from pyIntensityFeatures.proc import intensity # noqa F401