stdpipe 0.4.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.
- stdpipe/__init__.py +9 -0
- stdpipe/artefacts.py +370 -0
- stdpipe/astrometry.py +2001 -0
- stdpipe/astrometry_quad.py +1643 -0
- stdpipe/astrometry_wcs.py +1141 -0
- stdpipe/catalogs.py +942 -0
- stdpipe/cutouts.py +570 -0
- stdpipe/data/legacysurvey_bricks.fits.gz +0 -0
- stdpipe/data/ps1skycells.txt +199708 -0
- stdpipe/db.py +130 -0
- stdpipe/fringe_removal.py +1086 -0
- stdpipe/lcs.py +440 -0
- stdpipe/photometry.py +2340 -0
- stdpipe/photometry_background.py +998 -0
- stdpipe/photometry_iraf.py +913 -0
- stdpipe/photometry_measure.py +2268 -0
- stdpipe/photometry_model.py +931 -0
- stdpipe/photometry_psf.py +1124 -0
- stdpipe/photometry_quality.py +185 -0
- stdpipe/pipeline.py +1303 -0
- stdpipe/plots.py +1582 -0
- stdpipe/psf.py +1542 -0
- stdpipe/realbogus.py +971 -0
- stdpipe/realbogus_features.py +1657 -0
- stdpipe/reproject.py +943 -0
- stdpipe/resolve.py +267 -0
- stdpipe/sfft.py +1077 -0
- stdpipe/simulation.py +2131 -0
- stdpipe/smoothing.py +488 -0
- stdpipe/subtraction.py +1324 -0
- stdpipe/templates.py +929 -0
- stdpipe/utils.py +365 -0
- stdpipe-0.4.0.dist-info/METADATA +171 -0
- stdpipe-0.4.0.dist-info/RECORD +37 -0
- stdpipe-0.4.0.dist-info/WHEEL +5 -0
- stdpipe-0.4.0.dist-info/licenses/LICENSE.md +21 -0
- stdpipe-0.4.0.dist-info/top_level.txt +1 -0
stdpipe/lcs.py
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import numpy as np
|
|
3
|
+
from astropy.stats import mad_std
|
|
4
|
+
from astropy.time import Time
|
|
5
|
+
|
|
6
|
+
from scipy.spatial import cKDTree
|
|
7
|
+
|
|
8
|
+
from . import astrometry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Fast-conversion registry for types that cannot be stored directly as
|
|
12
|
+
# ndarray chunks (e.g. astropy.time.Time).
|
|
13
|
+
#
|
|
14
|
+
# Each handler is a callable ``handler(val) -> (storage_values, finalizer)``:
|
|
15
|
+
# * ``storage_values`` is an ndarray (or scalar) of plain numbers
|
|
16
|
+
# (typically float64) that is stored in place of the original value.
|
|
17
|
+
# * ``finalizer`` is called once during column consolidation as
|
|
18
|
+
# ``finalizer(ndarray) -> typed_object`` to rebuild the original
|
|
19
|
+
# container from the accumulated storage values.
|
|
20
|
+
#
|
|
21
|
+
# To add support for another such type, append a (predicate, handler)
|
|
22
|
+
# tuple to ``_FAST_TYPE_HANDLERS``.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _time_handler(val):
|
|
26
|
+
"""Round-trip Time through MJD floats (~150 ms for 3.5 M rows)."""
|
|
27
|
+
scale, fmt = val.scale, val.format
|
|
28
|
+
|
|
29
|
+
def finalize(arr):
|
|
30
|
+
# Rows padded with NaN (missing values) become masked Time entries,
|
|
31
|
+
# as Time rejects non-finite plain doubles
|
|
32
|
+
if np.any(np.isnan(arr)):
|
|
33
|
+
arr = np.ma.masked_invalid(arr)
|
|
34
|
+
out = Time(arr, format='mjd', scale=scale)
|
|
35
|
+
if fmt != 'mjd':
|
|
36
|
+
out.format = fmt
|
|
37
|
+
return out
|
|
38
|
+
|
|
39
|
+
return val.mjd, finalize
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
_FAST_TYPE_HANDLERS = [
|
|
43
|
+
(lambda v: isinstance(v, Time), _time_handler),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _fast_storage_handler(val):
|
|
48
|
+
for predicate, handler in _FAST_TYPE_HANDLERS:
|
|
49
|
+
if predicate(val):
|
|
50
|
+
return handler
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _vector_length(val):
|
|
55
|
+
"""Length of a vector-like value, or None for scalars, strings and None."""
|
|
56
|
+
if val is None or isinstance(val, str):
|
|
57
|
+
return None
|
|
58
|
+
try:
|
|
59
|
+
return len(val)
|
|
60
|
+
except TypeError:
|
|
61
|
+
# Objects like scalar Time define __len__ but raise on scalars
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _PadChunk:
|
|
66
|
+
"""
|
|
67
|
+
Placeholder for rows without a value for a given key.
|
|
68
|
+
|
|
69
|
+
The actual fill value and dtype are decided lazily during column
|
|
70
|
+
consolidation, once the dtype of the real chunks is known: NaN for
|
|
71
|
+
floating-point columns (including fast-converted types like Time),
|
|
72
|
+
None (object dtype) otherwise.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
__slots__ = ('n',)
|
|
76
|
+
|
|
77
|
+
def __init__(self, n):
|
|
78
|
+
self.n = n
|
|
79
|
+
|
|
80
|
+
def __len__(self):
|
|
81
|
+
return self.n
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class LCs:
|
|
85
|
+
"""
|
|
86
|
+
Container for light-curve data vectors with spatial clustering utilities.
|
|
87
|
+
|
|
88
|
+
Stores user-provided per-detection vectors (e.g., ra/dec/flux/time) and
|
|
89
|
+
groups detections into spatial clusters using a KDTree radius search.
|
|
90
|
+
Clustering returns per-cluster centroids and member indices in `self.lcs`.
|
|
91
|
+
|
|
92
|
+
Notes
|
|
93
|
+
-----
|
|
94
|
+
- `add()` broadcasts scalars to the length of the vector inputs, which
|
|
95
|
+
must all share the same length. Keys omitted from a call, and rows
|
|
96
|
+
preceding the first appearance of a new key, are padded so that all
|
|
97
|
+
stored vectors stay aligned: with NaN for floating-point columns
|
|
98
|
+
(missing Time entries become masked), with None (object dtype)
|
|
99
|
+
otherwise.
|
|
100
|
+
- Data vectors are stored as per-key lists of ndarray chunks and
|
|
101
|
+
consolidated lazily into single arrays on first attribute access.
|
|
102
|
+
- `cluster()` refines centroids and can call an `analyze(self, ids)` callback
|
|
103
|
+
per cluster.
|
|
104
|
+
- Coordinate jitter is applied when building the KDTree to avoid degeneracy
|
|
105
|
+
from repeated positions.
|
|
106
|
+
- Clustering results are stored in `self.lcs` with keys:
|
|
107
|
+
- `x`, `y`, `z`: centroid unit-vector coordinates.
|
|
108
|
+
- `ra`, `dec`: centroid sky coordinates in degrees.
|
|
109
|
+
- `N`: number of points per cluster.
|
|
110
|
+
- `ids`: list of index arrays for member points in the container.
|
|
111
|
+
- `kd`: KDTree built from centroid vectors for fast queries.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(self):
|
|
115
|
+
# Storage for user-supplied data vectors: key -> list of ndarray chunks
|
|
116
|
+
self._params = {}
|
|
117
|
+
# Cache of consolidated (concatenated and finalized) columns
|
|
118
|
+
self._columns = {}
|
|
119
|
+
# Per-key finalizer callables for keys whose values were stored via
|
|
120
|
+
# the fast-conversion path (see ``_FAST_TYPE_HANDLERS``). Applied
|
|
121
|
+
# during consolidation to restore the original type.
|
|
122
|
+
self._finalizers = {}
|
|
123
|
+
# Total number of stored rows
|
|
124
|
+
self._length = 0
|
|
125
|
+
# Data version, and the (version, col_ra, col_dec) the KDTree was built from
|
|
126
|
+
self._version = 0
|
|
127
|
+
self._kd_key = None
|
|
128
|
+
|
|
129
|
+
self.lcs = None
|
|
130
|
+
self.kd = None
|
|
131
|
+
|
|
132
|
+
def __getattr__(self, name):
|
|
133
|
+
# Allows direct access to stored data vectors. Underscored names never
|
|
134
|
+
# resolve here, so the `self._params` access below cannot recurse when
|
|
135
|
+
# the instance dict is not yet populated (e.g. during unpickling).
|
|
136
|
+
if name.startswith('_'):
|
|
137
|
+
raise AttributeError(name)
|
|
138
|
+
|
|
139
|
+
if name in self._params:
|
|
140
|
+
return self._get_column(name)
|
|
141
|
+
|
|
142
|
+
raise AttributeError(
|
|
143
|
+
"'%s' object has no attribute '%s'" % (self.__class__.__name__, name)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def __dir__(self):
|
|
147
|
+
# For auto-completion of stored data vectors names
|
|
148
|
+
return (
|
|
149
|
+
list(self.__dict__.keys())
|
|
150
|
+
+ list(self.__class__.__dict__.keys())
|
|
151
|
+
+ list(self._params.keys())
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def _materialize_pads(self, name, chunks):
|
|
155
|
+
"""Convert _PadChunk placeholders to arrays of NaN or None."""
|
|
156
|
+
real = [_ for _ in chunks if not isinstance(_, _PadChunk)]
|
|
157
|
+
dtype = np.result_type(*real) if real else None
|
|
158
|
+
|
|
159
|
+
if (dtype is not None and dtype.kind in 'fc') or (
|
|
160
|
+
dtype is None and name in self._finalizers
|
|
161
|
+
):
|
|
162
|
+
# Floating-point columns (including fast-converted types whose
|
|
163
|
+
# storage is numeric) can hold NaN without dtype degradation
|
|
164
|
+
fill = np.nan
|
|
165
|
+
fill_dtype = dtype if dtype is not None else np.float64
|
|
166
|
+
else:
|
|
167
|
+
fill = None
|
|
168
|
+
fill_dtype = object
|
|
169
|
+
|
|
170
|
+
return [
|
|
171
|
+
np.full(len(_), fill, dtype=fill_dtype) if isinstance(_, _PadChunk) else _
|
|
172
|
+
for _ in chunks
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
def _get_column(self, name):
|
|
176
|
+
"""Return the consolidated (concatenated and finalized) vector for a key."""
|
|
177
|
+
if name not in self._columns:
|
|
178
|
+
chunks = self._params[name]
|
|
179
|
+
|
|
180
|
+
if any(isinstance(_, _PadChunk) for _ in chunks):
|
|
181
|
+
chunks = self._materialize_pads(name, chunks)
|
|
182
|
+
self._params[name] = chunks
|
|
183
|
+
|
|
184
|
+
if not chunks:
|
|
185
|
+
arr = np.array([])
|
|
186
|
+
elif len(chunks) == 1:
|
|
187
|
+
arr = np.asanyarray(chunks[0])
|
|
188
|
+
else:
|
|
189
|
+
arr = np.concatenate(chunks)
|
|
190
|
+
# Keep the single consolidated chunk so repeated access is cheap
|
|
191
|
+
self._params[name] = [arr]
|
|
192
|
+
|
|
193
|
+
finalizer = self._finalizers.get(name)
|
|
194
|
+
if finalizer is not None:
|
|
195
|
+
arr = finalizer(arr)
|
|
196
|
+
|
|
197
|
+
self._columns[name] = arr
|
|
198
|
+
|
|
199
|
+
return self._columns[name]
|
|
200
|
+
|
|
201
|
+
def add(self, **kwargs):
|
|
202
|
+
"""
|
|
203
|
+
Add per-detection vectors to the container.
|
|
204
|
+
|
|
205
|
+
Each keyword defines a stored vector. Scalars are broadcast to the
|
|
206
|
+
length of the vector inputs, which must all share the same length
|
|
207
|
+
(ValueError is raised otherwise). This method may be called repeatedly
|
|
208
|
+
to append new chunks of measurements (e.g., per-image batches) to the
|
|
209
|
+
existing vectors. Previously stored keys omitted from a call, as well
|
|
210
|
+
as rows preceding the first appearance of a new key, are padded so
|
|
211
|
+
that all stored vectors stay aligned - with NaN for floating-point
|
|
212
|
+
columns (missing Time entries become masked), with None otherwise.
|
|
213
|
+
|
|
214
|
+
Examples
|
|
215
|
+
--------
|
|
216
|
+
>>> lcs = LCs()
|
|
217
|
+
>>> lcs.add(ra=[1, 2], dec=[3, 4], flux=10.0)
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
# All vector inputs must agree on the chunk length
|
|
221
|
+
vec_lengths = {}
|
|
222
|
+
for key, val in kwargs.items():
|
|
223
|
+
n = _vector_length(val)
|
|
224
|
+
if n is not None:
|
|
225
|
+
vec_lengths[key] = n
|
|
226
|
+
|
|
227
|
+
if len(set(vec_lengths.values())) > 1:
|
|
228
|
+
raise ValueError(
|
|
229
|
+
'Mismatched vector lengths in add(): '
|
|
230
|
+
+ ', '.join('%s has %d' % _ for _ in vec_lengths.items())
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
length = next(iter(vec_lengths.values()), 0)
|
|
234
|
+
|
|
235
|
+
for key in set(kwargs) | set(self._params):
|
|
236
|
+
if key not in self._params:
|
|
237
|
+
# New key: backfill rows stored before it first appeared
|
|
238
|
+
self._params[key] = (
|
|
239
|
+
[_PadChunk(self._length)] if self._length else []
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
val = kwargs.get(key)
|
|
243
|
+
|
|
244
|
+
# Types like astropy.time.Time cannot be stored as plain ndarray
|
|
245
|
+
# chunks; convert them to numeric storage values and remember the
|
|
246
|
+
# finalizer that restores the original type during consolidation
|
|
247
|
+
handler = _fast_storage_handler(val)
|
|
248
|
+
if handler is not None:
|
|
249
|
+
val, finalizer = handler(val)
|
|
250
|
+
self._finalizers.setdefault(key, finalizer)
|
|
251
|
+
|
|
252
|
+
if _vector_length(val) is not None:
|
|
253
|
+
chunk = np.asanyarray(val)
|
|
254
|
+
elif val is not None:
|
|
255
|
+
# Broadcast scalar to the common vector length
|
|
256
|
+
chunk = np.full(length, val)
|
|
257
|
+
else:
|
|
258
|
+
# Key omitted from this call, or explicit None
|
|
259
|
+
chunk = _PadChunk(length)
|
|
260
|
+
|
|
261
|
+
if len(chunk):
|
|
262
|
+
self._params[key].append(chunk)
|
|
263
|
+
|
|
264
|
+
self._length += length
|
|
265
|
+
# Invalidate consolidated columns and KDTree built from previous data
|
|
266
|
+
self._columns = {}
|
|
267
|
+
self._version += 1
|
|
268
|
+
|
|
269
|
+
def cluster(
|
|
270
|
+
self,
|
|
271
|
+
sr=1 / 3600,
|
|
272
|
+
min_length=None,
|
|
273
|
+
col_ra='ra',
|
|
274
|
+
col_dec='dec',
|
|
275
|
+
verbose=True,
|
|
276
|
+
analyze=None,
|
|
277
|
+
N=1000,
|
|
278
|
+
max_refine_iter=1,
|
|
279
|
+
rng=0,
|
|
280
|
+
):
|
|
281
|
+
"""
|
|
282
|
+
Spatially cluster the data vectors using ra/dec values stored in `col_ra` and `col_dec`.
|
|
283
|
+
|
|
284
|
+
Parameters
|
|
285
|
+
----------
|
|
286
|
+
sr : float, optional
|
|
287
|
+
Clustering radius in degrees.
|
|
288
|
+
min_length : int or None, optional
|
|
289
|
+
Minimum number of points required to keep a cluster.
|
|
290
|
+
col_ra : str, optional
|
|
291
|
+
Name of the RA column in stored vectors.
|
|
292
|
+
col_dec : str, optional
|
|
293
|
+
Name of the Dec column in stored vectors.
|
|
294
|
+
verbose : bool or callable, optional
|
|
295
|
+
Logging control, can be a print-like function.
|
|
296
|
+
analyze : callable or None, optional
|
|
297
|
+
Optional callback `analyze(self, ids)` called per accepted cluster.
|
|
298
|
+
Any returned mapping entries are appended into `self.lcs` under their
|
|
299
|
+
respective keys (one entry per cluster). The callback may also
|
|
300
|
+
return None to skip reporting for a given cluster.
|
|
301
|
+
N : int, optional
|
|
302
|
+
Progress update interval in points.
|
|
303
|
+
max_refine_iter : int, optional
|
|
304
|
+
Maximum number of centroid refinement iterations (default 1).
|
|
305
|
+
rng : int, numpy.random.Generator, or None, optional
|
|
306
|
+
Seed or generator for the coordinate jitter used to break KDTree
|
|
307
|
+
degeneracies from repeated positions. The default fixed seed makes
|
|
308
|
+
clustering deterministic; pass None for non-deterministic jitter.
|
|
309
|
+
|
|
310
|
+
Notes
|
|
311
|
+
-----
|
|
312
|
+
Clustering is greedy in storage order: every not-yet-masked point seeds
|
|
313
|
+
a radius search, and all points within the refined cluster radius are
|
|
314
|
+
excluded from seeding afterwards. Such points may still be claimed as
|
|
315
|
+
members of later clusters, so nearby clusters can share points. For
|
|
316
|
+
point distributions wider than `sr`, the exact set of clusters may
|
|
317
|
+
depend on the ordering of the stored points.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
log = (verbose if callable(verbose) else print) if verbose else lambda *args, **kwargs: None
|
|
321
|
+
|
|
322
|
+
if min_length is None:
|
|
323
|
+
min_length = 0
|
|
324
|
+
|
|
325
|
+
# Clustering radius as 3-D chord length corresponding to the angular radius
|
|
326
|
+
sr0 = 2 * np.sin(np.deg2rad(sr) / 2)
|
|
327
|
+
|
|
328
|
+
for col in (col_ra, col_dec):
|
|
329
|
+
if col not in self._params:
|
|
330
|
+
raise KeyError("Column '%s' is not stored in the container" % col)
|
|
331
|
+
|
|
332
|
+
kd_key = (self._version, col_ra, col_dec)
|
|
333
|
+
if self._kd_key != kd_key:
|
|
334
|
+
log('Building positional KDTree')
|
|
335
|
+
|
|
336
|
+
self._xarr, self._yarr, self._zarr = astrometry.radectoxyz(
|
|
337
|
+
self._get_column(col_ra), self._get_column(col_dec)
|
|
338
|
+
)
|
|
339
|
+
# Add some additional jitter to coordinates, or KDTree may hang on repeating positions
|
|
340
|
+
gen = np.random.default_rng(rng)
|
|
341
|
+
self._xarr = gen.normal(self._xarr, 0.01 / 206265)
|
|
342
|
+
self._yarr = gen.normal(self._yarr, 0.01 / 206265)
|
|
343
|
+
self._zarr = gen.normal(self._zarr, 0.01 / 206265)
|
|
344
|
+
self.kd = cKDTree(np.array([self._xarr, self._yarr, self._zarr]).T)
|
|
345
|
+
self._kd_key = kd_key
|
|
346
|
+
|
|
347
|
+
def refine_pos(x, y, z):
|
|
348
|
+
"""Returns mean position for a list of individual positions"""
|
|
349
|
+
x1, y1, z1 = [np.mean(_) for _ in [x, y, z]]
|
|
350
|
+
|
|
351
|
+
# Normalize back to unit sphere
|
|
352
|
+
r = np.sqrt(x1 * x1 + y1 * y1 + z1 * z1)
|
|
353
|
+
x1, y1, z1 = [_ / r for _ in [x1, y1, z1]]
|
|
354
|
+
|
|
355
|
+
return x1, y1, z1
|
|
356
|
+
|
|
357
|
+
xarr = self._xarr
|
|
358
|
+
yarr = self._yarr
|
|
359
|
+
zarr = self._zarr
|
|
360
|
+
kd = self.kd
|
|
361
|
+
|
|
362
|
+
vmask = np.zeros(len(xarr), bool)
|
|
363
|
+
|
|
364
|
+
self.lcs = {'x': [], 'y': [], 'z': [], 'N': [], 'ids': []}
|
|
365
|
+
lcs_x = self.lcs['x']
|
|
366
|
+
lcs_y = self.lcs['y']
|
|
367
|
+
lcs_z = self.lcs['z']
|
|
368
|
+
lcs_n = self.lcs['N']
|
|
369
|
+
lcs_ids = self.lcs['ids']
|
|
370
|
+
|
|
371
|
+
log(
|
|
372
|
+
'Starting spatial clustering of %d points with %.1f arcsec radius'
|
|
373
|
+
% (len(vmask), sr * 3600)
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
def process_seed(i):
|
|
377
|
+
# Select points around seed position
|
|
378
|
+
ids = kd.query_ball_point([xarr[i], yarr[i], zarr[i]], sr0)
|
|
379
|
+
|
|
380
|
+
if len(ids) < min_length:
|
|
381
|
+
vmask[ids] = True
|
|
382
|
+
return
|
|
383
|
+
|
|
384
|
+
x1, y1, z1 = refine_pos(xarr[ids], yarr[ids], zarr[ids])
|
|
385
|
+
ids = kd.query_ball_point([x1, y1, z1], sr0)
|
|
386
|
+
|
|
387
|
+
for _ in range(max_refine_iter - 1):
|
|
388
|
+
x2, y2, z2 = refine_pos(xarr[ids], yarr[ids], zarr[ids])
|
|
389
|
+
ids2 = kd.query_ball_point([x2, y2, z2], sr0)
|
|
390
|
+
if set(ids2) == set(ids):
|
|
391
|
+
x1, y1, z1 = x2, y2, z2
|
|
392
|
+
break
|
|
393
|
+
ids = ids2
|
|
394
|
+
x1, y1, z1 = x2, y2, z2
|
|
395
|
+
|
|
396
|
+
vmask[ids] = True # Mask all points around mean position
|
|
397
|
+
|
|
398
|
+
if len(ids) >= min_length:
|
|
399
|
+
# Actual processing of points
|
|
400
|
+
lcs_x.append(x1)
|
|
401
|
+
lcs_y.append(y1)
|
|
402
|
+
lcs_z.append(z1)
|
|
403
|
+
lcs_n.append(len(ids))
|
|
404
|
+
lcs_ids.append(ids)
|
|
405
|
+
|
|
406
|
+
if analyze is not None and callable(analyze):
|
|
407
|
+
ares = analyze(self, ids)
|
|
408
|
+
|
|
409
|
+
if ares:
|
|
410
|
+
for _, __ in ares.items():
|
|
411
|
+
if _ not in self.lcs:
|
|
412
|
+
self.lcs[_] = []
|
|
413
|
+
self.lcs[_].append(__)
|
|
414
|
+
|
|
415
|
+
for i in range(len(vmask)):
|
|
416
|
+
if not vmask[i]:
|
|
417
|
+
process_seed(i)
|
|
418
|
+
|
|
419
|
+
if i % N == 0:
|
|
420
|
+
if verbose is True:
|
|
421
|
+
# Interactive in-place progress for plain print mode
|
|
422
|
+
sys.stdout.write("\r %d points - %d lcs" % (i, len(self.lcs['x'])))
|
|
423
|
+
sys.stdout.flush()
|
|
424
|
+
elif callable(verbose):
|
|
425
|
+
log("%d points - %d lcs" % (i, len(self.lcs['x'])))
|
|
426
|
+
|
|
427
|
+
if verbose is True:
|
|
428
|
+
sys.stdout.write("\n")
|
|
429
|
+
sys.stdout.flush()
|
|
430
|
+
|
|
431
|
+
for _ in self.lcs.keys():
|
|
432
|
+
if isinstance(self.lcs[_], list) and _ not in ['ids']:
|
|
433
|
+
self.lcs[_] = np.array(self.lcs[_])
|
|
434
|
+
|
|
435
|
+
self.lcs['ra'], self.lcs['dec'] = astrometry.xyztoradec(
|
|
436
|
+
[self.lcs['x'], self.lcs['y'], self.lcs['z']]
|
|
437
|
+
)
|
|
438
|
+
self.lcs['kd'] = cKDTree(np.array([self.lcs['x'], self.lcs['y'], self.lcs['z']]).T)
|
|
439
|
+
|
|
440
|
+
log('%d spatial clusters isolated' % len(self.lcs['ra']))
|