graxpert 3.2.0a0.dev4__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.
- graxpert/AstroImageRepository.py +114 -0
- graxpert/__init__.py +1 -0
- graxpert/ai_model_handling.py +215 -0
- graxpert/app_state.py +10 -0
- graxpert/application/__init__.py +0 -0
- graxpert/application/app.py +741 -0
- graxpert/application/app_events.py +96 -0
- graxpert/application/eventbus.py +24 -0
- graxpert/astroimage.py +308 -0
- graxpert/background_extraction.py +290 -0
- graxpert/background_flood_selection.py +240 -0
- graxpert/background_grid_selection.py +64 -0
- graxpert/cmdline_tools.py +547 -0
- graxpert/commands.py +200 -0
- graxpert/deconvolution.py +176 -0
- graxpert/denoising.py +178 -0
- graxpert/grid_utils.py +38 -0
- graxpert/img/GraXpert_LOGO_Hauptvariante.png +0 -0
- graxpert/img/Icon.png +0 -0
- graxpert/img/LDN1235_original.jpg +0 -0
- graxpert/img/LDN1235_processed.jpg +0 -0
- graxpert/img/__init__.py +0 -0
- graxpert/img/gfx_numbers.png +0 -0
- graxpert/img/gfx_numbers.svg +737 -0
- graxpert/img/graXpert_Startbadge_Umbriel.png +0 -0
- graxpert/img/hourglass.png +0 -0
- graxpert/locales/__init__.py +0 -0
- graxpert/locales/de_DE/LC_MESSAGES/base.mo +0 -0
- graxpert/localization.py +39 -0
- graxpert/main.py +441 -0
- graxpert/mp_logging.py +97 -0
- graxpert/parallel_processing.py +3 -0
- graxpert/preferences.py +139 -0
- graxpert/radialbasisinterpolation.py +215 -0
- graxpert/resource_utils.py +35 -0
- graxpert/s3_secrets.py +16 -0
- graxpert/skyall.py +152 -0
- graxpert/stretch.py +151 -0
- graxpert/theme/__init__.py +0 -0
- graxpert/theme/graxpert-dark-blue.json +367 -0
- graxpert/ui/__init__.py +1 -0
- graxpert/ui/application_frame.py +115 -0
- graxpert/ui/canvas.py +558 -0
- graxpert/ui/left_menu.py +437 -0
- graxpert/ui/loadingframe.py +142 -0
- graxpert/ui/right_menu.py +315 -0
- graxpert/ui/statusbar.py +102 -0
- graxpert/ui/styling.py +23 -0
- graxpert/ui/tooltip.py +194 -0
- graxpert/ui/ui_events.py +19 -0
- graxpert/ui/widgets.py +297 -0
- graxpert/ui_scaling.py +13 -0
- graxpert/version.py +2 -0
- graxpert-3.2.0a0.dev4.dist-info/METADATA +219 -0
- graxpert-3.2.0a0.dev4.dist-info/RECORD +58 -0
- graxpert-3.2.0a0.dev4.dist-info/WHEEL +5 -0
- graxpert-3.2.0a0.dev4.dist-info/entry_points.txt +2 -0
- graxpert-3.2.0a0.dev4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import multiprocessing
|
|
2
|
+
|
|
3
|
+
multiprocessing.freeze_support()
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from concurrent.futures import wait
|
|
7
|
+
from multiprocessing import shared_memory
|
|
8
|
+
|
|
9
|
+
import cv2
|
|
10
|
+
import numpy as np
|
|
11
|
+
from astropy.stats import sigma_clipped_stats
|
|
12
|
+
from pykrige.ok import OrdinaryKriging
|
|
13
|
+
from scipy import interpolate, linalg
|
|
14
|
+
|
|
15
|
+
from graxpert.ai_model_handling import get_execution_providers_ordered
|
|
16
|
+
from graxpert.mp_logging import get_logging_queue, worker_configurer
|
|
17
|
+
from graxpert.parallel_processing import executor
|
|
18
|
+
from graxpert.radialbasisinterpolation import RadialBasisInterpolation
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def gaussian_kernel(sigma=1.0, truncate=4.0): # follow simulate skimage.filters.gaussian defaults
|
|
22
|
+
ksize = round(sigma * truncate) - 1 if round(sigma * truncate) % 2 == 0 else round(sigma * truncate)
|
|
23
|
+
return (ksize, ksize)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def extract_background(in_imarray, background_points, interpolation_type, smoothing, downscale_factor, sample_size, RBF_kernel, spline_order, corr_type, ai_path, progress=None, ai_gpu_acceleration=True):
|
|
27
|
+
num_colors = in_imarray.shape[-1]
|
|
28
|
+
|
|
29
|
+
shm_imarray = None
|
|
30
|
+
shm_background = None
|
|
31
|
+
|
|
32
|
+
if interpolation_type == "AI":
|
|
33
|
+
imarray = np.ndarray(in_imarray.shape, dtype=np.float32)
|
|
34
|
+
background = np.ndarray(in_imarray.shape, dtype=np.float32)
|
|
35
|
+
np.copyto(imarray, in_imarray)
|
|
36
|
+
|
|
37
|
+
# Shrink and pad to avoid artifacts on borders
|
|
38
|
+
padding = 8
|
|
39
|
+
imarray_shrink = cv2.resize(imarray, dsize=(256 - 2 * padding, 256 - 2 * padding), interpolation=cv2.INTER_LINEAR)
|
|
40
|
+
|
|
41
|
+
if len(imarray_shrink.shape) == 2:
|
|
42
|
+
imarray_shrink = np.expand_dims(imarray_shrink, -1)
|
|
43
|
+
|
|
44
|
+
imarray_shrink = np.pad(imarray_shrink, ((padding, padding), (padding, padding), (0, 0)), mode="edge")
|
|
45
|
+
|
|
46
|
+
median = []
|
|
47
|
+
mad = []
|
|
48
|
+
|
|
49
|
+
if progress is not None:
|
|
50
|
+
progress.update(8)
|
|
51
|
+
|
|
52
|
+
for c in range(num_colors):
|
|
53
|
+
median.append(np.median(imarray_shrink[:, :, c]))
|
|
54
|
+
mad.append(np.median(np.abs(imarray_shrink[:, :, c] - median[c])))
|
|
55
|
+
|
|
56
|
+
if progress is not None:
|
|
57
|
+
progress.update(8)
|
|
58
|
+
|
|
59
|
+
imarray_shrink = (imarray_shrink - median) / mad * 0.04
|
|
60
|
+
imarray_shrink = np.clip(imarray_shrink, -1.0, 1.0)
|
|
61
|
+
|
|
62
|
+
if progress is not None:
|
|
63
|
+
progress.update(8)
|
|
64
|
+
|
|
65
|
+
if num_colors == 1:
|
|
66
|
+
imarray_shrink = np.array([imarray_shrink[:, :, 0], imarray_shrink[:, :, 0], imarray_shrink[:, :, 0]])
|
|
67
|
+
imarray_shrink = np.moveaxis(imarray_shrink, 0, -1)
|
|
68
|
+
|
|
69
|
+
if progress is not None:
|
|
70
|
+
progress.update(8)
|
|
71
|
+
|
|
72
|
+
providers = get_execution_providers_ordered(ai_gpu_acceleration)
|
|
73
|
+
import onnxruntime as ort # Must be after get_execution_providers_ordered
|
|
74
|
+
session = ort.InferenceSession(ai_path, providers=providers)
|
|
75
|
+
|
|
76
|
+
logging.info(f"Providers : {providers}")
|
|
77
|
+
logging.info(f"Used providers : {session.get_providers()}")
|
|
78
|
+
|
|
79
|
+
background = session.run(None, {"gen_input_image": np.expand_dims(imarray_shrink, axis=0)})[0][0]
|
|
80
|
+
|
|
81
|
+
background = background / 0.04 * mad + median
|
|
82
|
+
|
|
83
|
+
if progress is not None:
|
|
84
|
+
progress.update(8)
|
|
85
|
+
|
|
86
|
+
if smoothing != 0:
|
|
87
|
+
sigma = smoothing * 20
|
|
88
|
+
background = cv2.GaussianBlur(background, ksize=gaussian_kernel(sigma), sigmaX=sigma, sigmaY=sigma)
|
|
89
|
+
|
|
90
|
+
if progress is not None:
|
|
91
|
+
progress.update(8)
|
|
92
|
+
|
|
93
|
+
if num_colors == 1:
|
|
94
|
+
background = np.array([background[:, :, 0]])
|
|
95
|
+
background = np.moveaxis(background, 0, -1)
|
|
96
|
+
|
|
97
|
+
if progress is not None:
|
|
98
|
+
progress.update(8)
|
|
99
|
+
|
|
100
|
+
# Slice to unpadded size of shrinked image, then resize to original size
|
|
101
|
+
if padding != 0:
|
|
102
|
+
background = background[padding:-padding, padding:-padding, :]
|
|
103
|
+
|
|
104
|
+
if progress is not None:
|
|
105
|
+
progress.update(8)
|
|
106
|
+
|
|
107
|
+
sigma = 3.0
|
|
108
|
+
background = cv2.GaussianBlur(background, ksize=gaussian_kernel(sigma), sigmaX=sigma, sigmaY=sigma)
|
|
109
|
+
background = cv2.resize(background, dsize=(in_imarray.shape[1], in_imarray.shape[0]), interpolation=cv2.INTER_LINEAR)
|
|
110
|
+
|
|
111
|
+
if len(background.shape) == 2:
|
|
112
|
+
background = np.expand_dims(background, -1)
|
|
113
|
+
|
|
114
|
+
if progress is not None:
|
|
115
|
+
progress.update(8)
|
|
116
|
+
|
|
117
|
+
else:
|
|
118
|
+
shm_imarray = shared_memory.SharedMemory(create=True, size=in_imarray.nbytes)
|
|
119
|
+
shm_background = shared_memory.SharedMemory(create=True, size=in_imarray.nbytes)
|
|
120
|
+
imarray = np.ndarray(in_imarray.shape, dtype=np.float32, buffer=shm_imarray.buf)
|
|
121
|
+
background = np.ndarray(in_imarray.shape, dtype=np.float32, buffer=shm_background.buf)
|
|
122
|
+
np.copyto(imarray, in_imarray)
|
|
123
|
+
|
|
124
|
+
x_sub = np.array(background_points[:, 0], dtype=int)
|
|
125
|
+
y_sub = np.array(background_points[:, 1], dtype=int)
|
|
126
|
+
|
|
127
|
+
if progress is not None:
|
|
128
|
+
progress.update(24)
|
|
129
|
+
|
|
130
|
+
futures = []
|
|
131
|
+
logging_queue = get_logging_queue()
|
|
132
|
+
for c in range(num_colors):
|
|
133
|
+
futures.insert(
|
|
134
|
+
c,
|
|
135
|
+
executor.submit(
|
|
136
|
+
interpol,
|
|
137
|
+
shm_imarray.name,
|
|
138
|
+
shm_background.name,
|
|
139
|
+
c,
|
|
140
|
+
x_sub,
|
|
141
|
+
y_sub,
|
|
142
|
+
in_imarray.shape,
|
|
143
|
+
interpolation_type,
|
|
144
|
+
smoothing,
|
|
145
|
+
downscale_factor,
|
|
146
|
+
sample_size,
|
|
147
|
+
RBF_kernel,
|
|
148
|
+
spline_order,
|
|
149
|
+
imarray.dtype,
|
|
150
|
+
logging_queue,
|
|
151
|
+
worker_configurer,
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
wait(futures)
|
|
155
|
+
|
|
156
|
+
if progress is not None:
|
|
157
|
+
progress.update(48)
|
|
158
|
+
|
|
159
|
+
# Correction
|
|
160
|
+
if corr_type == "Subtraction":
|
|
161
|
+
mean = np.mean(background)
|
|
162
|
+
imarray[:, :, :] = imarray[:, :, :] - background[:, :, :] + mean
|
|
163
|
+
elif corr_type == "Division":
|
|
164
|
+
for c in range(num_colors):
|
|
165
|
+
mean = np.mean(imarray[:, :, c])
|
|
166
|
+
imarray[:, :, c] = imarray[:, :, c] / background[:, :, c] * mean
|
|
167
|
+
|
|
168
|
+
if progress is not None:
|
|
169
|
+
progress.update(8)
|
|
170
|
+
|
|
171
|
+
# clip image
|
|
172
|
+
imarray[:, :, :] = imarray.clip(min=0.0, max=1.0)
|
|
173
|
+
|
|
174
|
+
in_imarray[:] = imarray[:]
|
|
175
|
+
|
|
176
|
+
if progress is not None:
|
|
177
|
+
progress.update(8)
|
|
178
|
+
|
|
179
|
+
if shm_imarray is not None:
|
|
180
|
+
shm_imarray.close()
|
|
181
|
+
shm_imarray.unlink()
|
|
182
|
+
if shm_background is not None:
|
|
183
|
+
background = np.copy(background)
|
|
184
|
+
shm_background.close()
|
|
185
|
+
shm_background.unlink()
|
|
186
|
+
|
|
187
|
+
return background
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def calc_mode_dataset(data, x_sub, y_sub, halfsize):
|
|
191
|
+
|
|
192
|
+
n = x_sub.shape[0]
|
|
193
|
+
data_padded = np.pad(array=data, pad_width=(halfsize,), mode="reflect")
|
|
194
|
+
subsample = np.zeros(n)
|
|
195
|
+
|
|
196
|
+
for i in range(n):
|
|
197
|
+
data_footprint = data_padded[y_sub[i] : y_sub[i] + 2 * halfsize, x_sub[i] : x_sub[i] + 2 * halfsize]
|
|
198
|
+
subsample[i] = sigma_clipped_stats(data=data_footprint, cenfunc="median", stdfunc="std", grow=4)[1]
|
|
199
|
+
|
|
200
|
+
return subsample
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def interpol(shm_imarray_name, shm_background_name, c, x_sub, y_sub, shape, kind, smoothing, downscale_factor, sample_size, RBF_kernel, spline_order, dtype, logging_queue, logging_configurer):
|
|
204
|
+
|
|
205
|
+
logging_configurer(logging_queue)
|
|
206
|
+
logging.info("background_extraction.interpol started")
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
existing_shm_imarray = shared_memory.SharedMemory(name=shm_imarray_name)
|
|
210
|
+
existing_shm_background = shared_memory.SharedMemory(name=shm_background_name)
|
|
211
|
+
imarray = np.ndarray(shape, dtype, buffer=existing_shm_imarray.buf) # [:,:,channel_idx]
|
|
212
|
+
imarray = imarray[:, :, c]
|
|
213
|
+
background = np.ndarray(shape, dtype, buffer=existing_shm_background.buf)
|
|
214
|
+
shape = imarray.shape
|
|
215
|
+
|
|
216
|
+
subsample = calc_mode_dataset(imarray, x_sub, y_sub, sample_size)
|
|
217
|
+
|
|
218
|
+
if downscale_factor != 1:
|
|
219
|
+
x_sub = x_sub / shape[1]
|
|
220
|
+
y_sub = y_sub / shape[0]
|
|
221
|
+
|
|
222
|
+
shape_scaled = (shape[0] // downscale_factor, shape[1] // downscale_factor)
|
|
223
|
+
|
|
224
|
+
x_sub = x_sub * shape_scaled[1]
|
|
225
|
+
y_sub = y_sub * shape_scaled[0]
|
|
226
|
+
|
|
227
|
+
else:
|
|
228
|
+
shape_scaled = shape
|
|
229
|
+
|
|
230
|
+
if kind == "RBF":
|
|
231
|
+
points_stacked = np.stack([x_sub, y_sub], -1)
|
|
232
|
+
interp = RadialBasisInterpolation(points_stacked, subsample, kernel=RBF_kernel, smooth=smoothing * linalg.norm(subsample) / np.sqrt(len(subsample)))
|
|
233
|
+
|
|
234
|
+
# Create background from interpolation
|
|
235
|
+
x_new = np.arange(0, shape_scaled[1], 1)
|
|
236
|
+
y_new = np.arange(0, shape_scaled[0], 1)
|
|
237
|
+
|
|
238
|
+
xx, yy = np.meshgrid(x_new, y_new)
|
|
239
|
+
points_new_stacked = np.stack([xx.ravel(), yy.ravel()], -1)
|
|
240
|
+
|
|
241
|
+
result = interp(points_new_stacked).reshape(shape_scaled)
|
|
242
|
+
|
|
243
|
+
elif kind == "Splines":
|
|
244
|
+
interp = interpolate.bisplrep(y_sub, x_sub, subsample, w=np.ones(len(x_sub)) / np.std(subsample), s=smoothing * len(x_sub), kx=spline_order, ky=spline_order)
|
|
245
|
+
|
|
246
|
+
# Create background from interpolation
|
|
247
|
+
x_new = np.arange(0, shape_scaled[1], 1)
|
|
248
|
+
y_new = np.arange(0, shape_scaled[0], 1)
|
|
249
|
+
result = interpolate.bisplev(y_new, x_new, interp)
|
|
250
|
+
|
|
251
|
+
elif kind == "Kriging":
|
|
252
|
+
OK = OrdinaryKriging(
|
|
253
|
+
x=x_sub,
|
|
254
|
+
y=y_sub,
|
|
255
|
+
z=subsample,
|
|
256
|
+
variogram_model="spherical",
|
|
257
|
+
verbose=False,
|
|
258
|
+
enable_plotting=False,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Create background from interpolation
|
|
262
|
+
x_new = np.arange(0, shape_scaled[1], 1).astype("float64")
|
|
263
|
+
y_new = np.arange(0, shape_scaled[0], 1).astype("float64")
|
|
264
|
+
|
|
265
|
+
result = np.zeros(shape_scaled, dtype=np.float32)
|
|
266
|
+
|
|
267
|
+
num_it = shape_scaled[0] // 50
|
|
268
|
+
|
|
269
|
+
for i in range(num_it):
|
|
270
|
+
result_i, var = OK.execute("grid", xpoints=x_new, ypoints=y_new[i * 50 : (i + 1) * 50], backend="vectorized")
|
|
271
|
+
result[i * 50 : (i + 1) * 50, :] = result_i
|
|
272
|
+
|
|
273
|
+
result_i, var = OK.execute("grid", xpoints=x_new, ypoints=y_new[num_it * 50 :], backend="vectorized")
|
|
274
|
+
result[num_it * 50 :, :] = result_i
|
|
275
|
+
|
|
276
|
+
else:
|
|
277
|
+
logging.warning("Interpolation method not recognized")
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
if downscale_factor != 1:
|
|
281
|
+
result = cv2.resize(src=result, dsize=(shape[1], shape[0]), interpolation=cv2.INTER_LINEAR)
|
|
282
|
+
|
|
283
|
+
background[:, :, c] = result
|
|
284
|
+
except Exception as e:
|
|
285
|
+
logging.exception("Error occured during background_extraction.interpol")
|
|
286
|
+
|
|
287
|
+
existing_shm_imarray.close()
|
|
288
|
+
existing_shm_background.close()
|
|
289
|
+
|
|
290
|
+
logging.info("background_extraction.interpol finished")
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from concurrent.futures.process import _ResultItem
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from scipy.spatial import KDTree
|
|
6
|
+
from skimage.color import rgb2gray
|
|
7
|
+
|
|
8
|
+
from graxpert.astroimage import AstroImage
|
|
9
|
+
from graxpert.grid_utils import find_darkest_quadrant
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def idx_to_coords(idx, dist):
|
|
13
|
+
return int(idx[0] * dist + dist / 2), int(idx[1] * dist + dist / 2)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def candidate_visited(candidate_idx, found_row_segments):
|
|
17
|
+
|
|
18
|
+
for segment in found_row_segments:
|
|
19
|
+
if (
|
|
20
|
+
candidate_idx[1] == segment["y"]
|
|
21
|
+
and segment["xl"] <= candidate_idx[0]
|
|
22
|
+
and candidate_idx[0] <= segment["xr"]
|
|
23
|
+
):
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def candidate_valid(
|
|
30
|
+
candidate_idx,
|
|
31
|
+
width,
|
|
32
|
+
height,
|
|
33
|
+
dist,
|
|
34
|
+
selected_median,
|
|
35
|
+
tol,
|
|
36
|
+
mad,
|
|
37
|
+
data_mono_padded,
|
|
38
|
+
halfsize,
|
|
39
|
+
):
|
|
40
|
+
x_pt, y_pt = idx_to_coords(candidate_idx, dist)
|
|
41
|
+
|
|
42
|
+
if x_pt < 0 or x_pt >= width or y_pt < 0 or y_pt >= height:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
pt, local_median = find_darkest_quadrant(x_pt, y_pt, data_mono_padded, halfsize)
|
|
46
|
+
|
|
47
|
+
if (selected_median - tol * mad / 10) <= local_median and local_median <= (selected_median + tol * mad / 10):
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def overlap(p1, p2, sample_size):
|
|
54
|
+
|
|
55
|
+
if abs(p1[0] - p2[0]) <= sample_size * 2 and abs(p1[1] - p2[1]) <= sample_size * 2:
|
|
56
|
+
return True
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def background_flood_selection(
|
|
61
|
+
selected_point,
|
|
62
|
+
current_background_points,
|
|
63
|
+
tol,
|
|
64
|
+
bg_pts,
|
|
65
|
+
sample_size,
|
|
66
|
+
image: AstroImage,
|
|
67
|
+
):
|
|
68
|
+
# Convert to mono
|
|
69
|
+
data_mono = np.copy(image.img_display)
|
|
70
|
+
if data_mono.shape[-1] == 3:
|
|
71
|
+
data_mono = rgb2gray(data_mono)
|
|
72
|
+
|
|
73
|
+
global_median = np.median(data_mono)
|
|
74
|
+
|
|
75
|
+
grid_pts = []
|
|
76
|
+
dist = data_mono.shape[1] / bg_pts
|
|
77
|
+
|
|
78
|
+
# Create grid
|
|
79
|
+
x_start = int(0.5 * dist)
|
|
80
|
+
y_start = int(0.5 * (data_mono.shape[0] % dist))
|
|
81
|
+
x = x_start
|
|
82
|
+
y = y_start
|
|
83
|
+
|
|
84
|
+
while y < data_mono.shape[0]:
|
|
85
|
+
x = x_start
|
|
86
|
+
while x < data_mono.shape[1]:
|
|
87
|
+
grid_pts.append([y, x])
|
|
88
|
+
x = int(x + dist)
|
|
89
|
+
y = int(y + dist)
|
|
90
|
+
|
|
91
|
+
# Calculate median around each grid point
|
|
92
|
+
local_median = np.zeros(len(grid_pts))
|
|
93
|
+
halfsize = sample_size
|
|
94
|
+
data_mono_padded = np.pad(array=data_mono, pad_width=(halfsize,), mode="reflect")
|
|
95
|
+
|
|
96
|
+
r = range(len(grid_pts))
|
|
97
|
+
for i in r:
|
|
98
|
+
x_pt = grid_pts[i][0]
|
|
99
|
+
y_pt = grid_pts[i][1]
|
|
100
|
+
|
|
101
|
+
pt, median = find_darkest_quadrant(x_pt, y_pt, data_mono_padded, halfsize)
|
|
102
|
+
|
|
103
|
+
grid_pts[i][0] = pt[0]
|
|
104
|
+
grid_pts[i][1] = pt[1]
|
|
105
|
+
local_median[i] = median
|
|
106
|
+
|
|
107
|
+
# Calculate median average deviation
|
|
108
|
+
mad = np.median(np.abs(local_median - global_median))
|
|
109
|
+
|
|
110
|
+
pt, candidate_median = find_darkest_quadrant(
|
|
111
|
+
int(selected_point[0]), int(selected_point[1]), data_mono_padded, sample_size
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
width = image.width
|
|
115
|
+
height = image.height
|
|
116
|
+
|
|
117
|
+
# distance between grid points
|
|
118
|
+
dist = width / bg_pts
|
|
119
|
+
|
|
120
|
+
# first candidate row index
|
|
121
|
+
x_candidate_idx = int(((selected_point[0] - x_start) / dist))
|
|
122
|
+
y_candidate_idx = int(((selected_point[1] - y_start) / dist))
|
|
123
|
+
|
|
124
|
+
# stack that contains candidate bg_point indices
|
|
125
|
+
candidate_idxs = [
|
|
126
|
+
[x_candidate_idx + 1, y_candidate_idx + 1],
|
|
127
|
+
[x_candidate_idx, y_candidate_idx + 1],
|
|
128
|
+
[x_candidate_idx + 1, y_candidate_idx],
|
|
129
|
+
[x_candidate_idx, y_candidate_idx],
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
# list of valid row segments
|
|
133
|
+
found_row_segments = []
|
|
134
|
+
|
|
135
|
+
# step 1: compute row segments with valid bg_point indices
|
|
136
|
+
# valid means: bg_point is in tolerance and in image bounds
|
|
137
|
+
# row segments are computed by scanning each grid line to left and right, starting from the manually selected point as reference
|
|
138
|
+
# candidate bg_point indices are stored on a stack
|
|
139
|
+
|
|
140
|
+
while candidate_idxs:
|
|
141
|
+
|
|
142
|
+
candidate_idx = candidate_idxs.pop()
|
|
143
|
+
|
|
144
|
+
if candidate_visited(candidate_idx, found_row_segments):
|
|
145
|
+
logging.debug("candidate_valid")
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
if not candidate_valid(
|
|
149
|
+
candidate_idx,
|
|
150
|
+
width,
|
|
151
|
+
height,
|
|
152
|
+
dist,
|
|
153
|
+
candidate_median,
|
|
154
|
+
tol,
|
|
155
|
+
mad,
|
|
156
|
+
data_mono_padded,
|
|
157
|
+
halfsize,
|
|
158
|
+
):
|
|
159
|
+
logging.debug("candidate_valid")
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
row_segment = {
|
|
163
|
+
"xl": candidate_idx[0],
|
|
164
|
+
"xr": candidate_idx[0],
|
|
165
|
+
"y": candidate_idx[1],
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
next_candidate_idx = [candidate_idx[0] - 1, candidate_idx[1]]
|
|
169
|
+
while candidate_valid(
|
|
170
|
+
next_candidate_idx,
|
|
171
|
+
width,
|
|
172
|
+
height,
|
|
173
|
+
dist,
|
|
174
|
+
candidate_median,
|
|
175
|
+
tol,
|
|
176
|
+
mad,
|
|
177
|
+
data_mono_padded,
|
|
178
|
+
halfsize,
|
|
179
|
+
):
|
|
180
|
+
row_segment["xl"] = next_candidate_idx[0]
|
|
181
|
+
candidate_idxs.append([next_candidate_idx[0], next_candidate_idx[1] - 1])
|
|
182
|
+
candidate_idxs.append([next_candidate_idx[0], next_candidate_idx[1] + 1])
|
|
183
|
+
next_candidate_idx = [next_candidate_idx[0] - 1, next_candidate_idx[1]]
|
|
184
|
+
|
|
185
|
+
next_candidate_idx = [candidate_idx[0] + 1, candidate_idx[1]]
|
|
186
|
+
while candidate_valid(
|
|
187
|
+
next_candidate_idx,
|
|
188
|
+
width,
|
|
189
|
+
height,
|
|
190
|
+
dist,
|
|
191
|
+
candidate_median,
|
|
192
|
+
tol,
|
|
193
|
+
mad,
|
|
194
|
+
data_mono_padded,
|
|
195
|
+
halfsize,
|
|
196
|
+
):
|
|
197
|
+
row_segment["xr"] = next_candidate_idx[0]
|
|
198
|
+
candidate_idxs.append([next_candidate_idx[0], next_candidate_idx[1] - 1])
|
|
199
|
+
candidate_idxs.append([next_candidate_idx[0], next_candidate_idx[1] + 1])
|
|
200
|
+
next_candidate_idx = [next_candidate_idx[0] + 1, next_candidate_idx[1]]
|
|
201
|
+
|
|
202
|
+
found_row_segments.append(row_segment)
|
|
203
|
+
|
|
204
|
+
# step 2: compute actual points from found row segments
|
|
205
|
+
|
|
206
|
+
found_points = [selected_point]
|
|
207
|
+
|
|
208
|
+
for segment in found_row_segments:
|
|
209
|
+
y_idx = segment["y"]
|
|
210
|
+
for x_idx in range(segment["xl"], segment["xr"] + 1):
|
|
211
|
+
x, y = idx_to_coords([x_idx, y_idx], dist)
|
|
212
|
+
pt, median = find_darkest_quadrant(x, y, data_mono_padded, sample_size)
|
|
213
|
+
found_points.append([pt[0], pt[1], 1])
|
|
214
|
+
|
|
215
|
+
# step 3: check for and eliminate duplicates
|
|
216
|
+
|
|
217
|
+
if current_background_points is None or len(current_background_points) == 0:
|
|
218
|
+
result = []
|
|
219
|
+
for p in found_points:
|
|
220
|
+
result.append(np.array(p, dtype=int))
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
confirmed_points = []
|
|
224
|
+
|
|
225
|
+
background_tree = KDTree(current_background_points)
|
|
226
|
+
|
|
227
|
+
for f in found_points:
|
|
228
|
+
f_neighbors = background_tree.query_ball_point(f, dist * 2)
|
|
229
|
+
overlaps = False
|
|
230
|
+
for n_idx in f_neighbors:
|
|
231
|
+
if overlap(current_background_points[n_idx], f, sample_size):
|
|
232
|
+
overlaps = True
|
|
233
|
+
if not overlaps:
|
|
234
|
+
confirmed_points.append(f)
|
|
235
|
+
|
|
236
|
+
result = []
|
|
237
|
+
for p in confirmed_points:
|
|
238
|
+
result.append(np.array(p, dtype=int))
|
|
239
|
+
|
|
240
|
+
return result
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from skimage import color
|
|
3
|
+
|
|
4
|
+
import graxpert.skyall
|
|
5
|
+
import graxpert.stretch
|
|
6
|
+
from graxpert.grid_utils import find_darkest_quadrant
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def background_grid_selection(data, num_pts_per_row, tol, sample_size):
|
|
10
|
+
|
|
11
|
+
# Convert to mono
|
|
12
|
+
data_mono = np.copy(data)
|
|
13
|
+
if(data_mono.shape[-1] == 3):
|
|
14
|
+
data_mono = color.rgb2gray(data_mono)
|
|
15
|
+
else:
|
|
16
|
+
data_mono = data_mono[:,:,0]
|
|
17
|
+
|
|
18
|
+
global_median = np.median(data_mono)
|
|
19
|
+
|
|
20
|
+
background_pts = []
|
|
21
|
+
dist = data_mono.shape[1] / num_pts_per_row
|
|
22
|
+
|
|
23
|
+
# Create grid
|
|
24
|
+
x_start = int(0.5 * dist)
|
|
25
|
+
y_start = int(0.5 * (data_mono.shape[0] % dist))
|
|
26
|
+
x = x_start
|
|
27
|
+
y = y_start
|
|
28
|
+
|
|
29
|
+
while(y < data_mono.shape[0]):
|
|
30
|
+
x = x_start
|
|
31
|
+
while(x < data_mono.shape[1]):
|
|
32
|
+
background_pts.append([x,y,1])
|
|
33
|
+
x = int(x + dist)
|
|
34
|
+
y = int(y+dist)
|
|
35
|
+
|
|
36
|
+
# Calculate median around each grid point
|
|
37
|
+
local_median = np.zeros(len(background_pts))
|
|
38
|
+
halfsize = sample_size
|
|
39
|
+
data_mono_padded = np.pad(array=data_mono, pad_width=(halfsize,), mode="reflect")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
for i in range(len(background_pts)):
|
|
43
|
+
x_pt = background_pts[i][0]
|
|
44
|
+
y_pt = background_pts[i][1]
|
|
45
|
+
|
|
46
|
+
pt, median = find_darkest_quadrant(x_pt, y_pt, data_mono_padded, halfsize)
|
|
47
|
+
|
|
48
|
+
background_pts[i][0] = pt[0]
|
|
49
|
+
background_pts[i][1] = pt[1]
|
|
50
|
+
local_median[i] = median
|
|
51
|
+
|
|
52
|
+
# Calculate median average deviation and remove points not within tolerance
|
|
53
|
+
mad = np.median(np.abs(local_median - global_median))
|
|
54
|
+
|
|
55
|
+
background_pts_sliced = []
|
|
56
|
+
for i in range(len(background_pts)):
|
|
57
|
+
if(local_median[i] < global_median + tol*mad):
|
|
58
|
+
background_pts_sliced.append(background_pts[i])
|
|
59
|
+
|
|
60
|
+
result = []
|
|
61
|
+
for p in background_pts_sliced:
|
|
62
|
+
result.append(np.array(p, dtype=int))
|
|
63
|
+
|
|
64
|
+
return result
|