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.
Files changed (58) hide show
  1. graxpert/AstroImageRepository.py +114 -0
  2. graxpert/__init__.py +1 -0
  3. graxpert/ai_model_handling.py +215 -0
  4. graxpert/app_state.py +10 -0
  5. graxpert/application/__init__.py +0 -0
  6. graxpert/application/app.py +741 -0
  7. graxpert/application/app_events.py +96 -0
  8. graxpert/application/eventbus.py +24 -0
  9. graxpert/astroimage.py +308 -0
  10. graxpert/background_extraction.py +290 -0
  11. graxpert/background_flood_selection.py +240 -0
  12. graxpert/background_grid_selection.py +64 -0
  13. graxpert/cmdline_tools.py +547 -0
  14. graxpert/commands.py +200 -0
  15. graxpert/deconvolution.py +176 -0
  16. graxpert/denoising.py +178 -0
  17. graxpert/grid_utils.py +38 -0
  18. graxpert/img/GraXpert_LOGO_Hauptvariante.png +0 -0
  19. graxpert/img/Icon.png +0 -0
  20. graxpert/img/LDN1235_original.jpg +0 -0
  21. graxpert/img/LDN1235_processed.jpg +0 -0
  22. graxpert/img/__init__.py +0 -0
  23. graxpert/img/gfx_numbers.png +0 -0
  24. graxpert/img/gfx_numbers.svg +737 -0
  25. graxpert/img/graXpert_Startbadge_Umbriel.png +0 -0
  26. graxpert/img/hourglass.png +0 -0
  27. graxpert/locales/__init__.py +0 -0
  28. graxpert/locales/de_DE/LC_MESSAGES/base.mo +0 -0
  29. graxpert/localization.py +39 -0
  30. graxpert/main.py +441 -0
  31. graxpert/mp_logging.py +97 -0
  32. graxpert/parallel_processing.py +3 -0
  33. graxpert/preferences.py +139 -0
  34. graxpert/radialbasisinterpolation.py +215 -0
  35. graxpert/resource_utils.py +35 -0
  36. graxpert/s3_secrets.py +16 -0
  37. graxpert/skyall.py +152 -0
  38. graxpert/stretch.py +151 -0
  39. graxpert/theme/__init__.py +0 -0
  40. graxpert/theme/graxpert-dark-blue.json +367 -0
  41. graxpert/ui/__init__.py +1 -0
  42. graxpert/ui/application_frame.py +115 -0
  43. graxpert/ui/canvas.py +558 -0
  44. graxpert/ui/left_menu.py +437 -0
  45. graxpert/ui/loadingframe.py +142 -0
  46. graxpert/ui/right_menu.py +315 -0
  47. graxpert/ui/statusbar.py +102 -0
  48. graxpert/ui/styling.py +23 -0
  49. graxpert/ui/tooltip.py +194 -0
  50. graxpert/ui/ui_events.py +19 -0
  51. graxpert/ui/widgets.py +297 -0
  52. graxpert/ui_scaling.py +13 -0
  53. graxpert/version.py +2 -0
  54. graxpert-3.2.0a0.dev4.dist-info/METADATA +219 -0
  55. graxpert-3.2.0a0.dev4.dist-info/RECORD +58 -0
  56. graxpert-3.2.0a0.dev4.dist-info/WHEEL +5 -0
  57. graxpert-3.2.0a0.dev4.dist-info/entry_points.txt +2 -0
  58. graxpert-3.2.0a0.dev4.dist-info/top_level.txt +1 -0
graxpert/commands.py ADDED
@@ -0,0 +1,200 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from copy import deepcopy
5
+ from typing import Dict
6
+
7
+ import numpy as np
8
+
9
+ from graxpert.app_state import INITIAL_STATE, AppState
10
+ from graxpert.background_grid_selection import background_grid_selection
11
+ from graxpert.background_flood_selection import background_flood_selection
12
+
13
+
14
+ class ICommandHandler(ABC):
15
+ @abstractmethod
16
+ def execute(self, app_state: AppState, cmd_args: Dict) -> AppState:
17
+ pass
18
+
19
+ @abstractmethod
20
+ def undo(
21
+ self, cur_state: AppState, prev_state: AppState, cmd_args: Dict
22
+ ) -> AppState:
23
+ pass
24
+
25
+ @abstractmethod
26
+ def redo(
27
+ self, cur_state: AppState, next_state: AppState, cmd_args: Dict
28
+ ) -> AppState:
29
+ pass
30
+
31
+ @abstractmethod
32
+ def progress(self) -> float:
33
+ pass
34
+
35
+
36
+ class Command:
37
+
38
+ def __init__(
39
+ self, handler: ICommandHandler, prev: Command = None, **kwargs
40
+ ) -> None:
41
+ self.prev: Command = prev
42
+ self.next: Command = None
43
+ self.handler: ICommandHandler = handler
44
+ self.app_state: AppState = None
45
+ self.cmd_args = kwargs
46
+
47
+ def execute(self) -> AppState:
48
+ if self.prev is None:
49
+ app_state = INITIAL_STATE
50
+ else:
51
+ app_state = self.prev.app_state
52
+ self.app_state = self.handler.execute(app_state, self.cmd_args)
53
+ if self.prev is not None:
54
+ self.prev.next = self
55
+ return self.app_state
56
+
57
+ def undo(self) -> Command:
58
+ if self.prev is None:
59
+ prev_state = INITIAL_STATE
60
+ return self
61
+ else:
62
+ prev_state = self.prev.app_state
63
+ self.prev.app_state = self.handler.undo(self.app_state, prev_state, self.cmd_args)
64
+ return self.prev
65
+
66
+
67
+ def redo(self) -> Command:
68
+ assert self.next is not None
69
+ next_state = self.next.app_state
70
+ self.next.app_state = self.handler.redo(self.app_state, next_state, self.cmd_args)
71
+ return self.next
72
+
73
+
74
+ class InitHandler(ICommandHandler):
75
+
76
+ def execute(self, app_state: AppState, cmd_args: Dict) -> AppState:
77
+ state = INITIAL_STATE
78
+ state.background_points = cmd_args["background_points"]
79
+ return state
80
+
81
+ def undo(
82
+ self, cur_state: AppState, prev_state: AppState, cmd_args: Dict
83
+ ) -> AppState:
84
+ state = INITIAL_STATE
85
+ state.background_points = cmd_args["background_points"]
86
+ return state
87
+
88
+ def redo(
89
+ self, cur_state: AppState, next_state: AppState, cmd_args: Dict
90
+ ) -> AppState:
91
+ return deepcopy(next_state)
92
+
93
+ def progress(self) -> float:
94
+ return 1.0
95
+
96
+ class PointHandler(ICommandHandler):
97
+ def undo(
98
+ self, cur_state: AppState, prev_state: AppState, cmd_args: Dict
99
+ ) -> AppState:
100
+ app_state_copy = deepcopy(cur_state)
101
+ prev_background_points = deepcopy(prev_state.background_points)
102
+ app_state_copy.background_points = prev_background_points
103
+ return app_state_copy
104
+
105
+ def redo(
106
+ self, cur_state: AppState, next_state: AppState, cmd_args: Dict
107
+ ) -> AppState:
108
+ app_state_copy = deepcopy(cur_state)
109
+ next_background_points = deepcopy(next_state.background_points)
110
+ app_state_copy.background_points = next_background_points
111
+ return app_state_copy
112
+
113
+
114
+ class AddPointHandler(PointHandler):
115
+ def execute(self, app_state: AppState, cmd_args: Dict) -> AppState:
116
+ app_state_copy = deepcopy(app_state)
117
+ point = cmd_args["point"]
118
+ app_state_copy.background_points.append(point)
119
+ return app_state_copy
120
+
121
+ def progress(self) -> float:
122
+ return 1.0
123
+
124
+
125
+ class AddPointsHandler(PointHandler):
126
+ def execute(self, app_state: AppState, cmd_args: Dict) -> AppState:
127
+ app_state_copy = deepcopy(app_state)
128
+ point = cmd_args["point"]
129
+ background_points = app_state_copy.background_points
130
+ tol = cmd_args["tol"]
131
+ bg_pts = cmd_args["bg_pts"]
132
+ sample_size = cmd_args["sample_size"]
133
+ image = cmd_args["image"]
134
+ new_points = background_flood_selection(point, background_points, tol, bg_pts, sample_size, image)
135
+ app_state_copy.background_points.extend(new_points)
136
+ return app_state_copy
137
+
138
+ def progress(self) -> float:
139
+ return 1.0
140
+
141
+
142
+ class RemovePointHandler(PointHandler):
143
+ def execute(self, app_state: AppState, cmd_args: Dict) -> AppState:
144
+ app_state_copy = deepcopy(app_state)
145
+ idx = cmd_args["idx"]
146
+ app_state_copy.background_points.pop(idx)
147
+ return app_state_copy
148
+
149
+ def progress(self) -> float:
150
+ return 1.0
151
+
152
+
153
+ class MovePointHandler(PointHandler):
154
+ def execute(self, app_state: AppState, cmd_args: Dict) -> AppState:
155
+ app_state_copy = deepcopy(app_state)
156
+ idx = cmd_args["idx"]
157
+ new_point = cmd_args["new_point"]
158
+
159
+ if len(new_point) == 0:
160
+ app_state_copy.background_points.pop(idx)
161
+ else:
162
+ app_state_copy.background_points[idx] = new_point
163
+
164
+ return app_state_copy
165
+
166
+ def progress(self) -> float:
167
+ return 1.0
168
+
169
+
170
+ class SelectPointsHandler(PointHandler):
171
+ def execute(self, app_state: AppState, cmd_args: Dict) -> AppState:
172
+ app_state_copy = deepcopy(app_state)
173
+ data = cmd_args["data"]
174
+ num_pts = cmd_args["num_pts"]
175
+ tol = cmd_args["tol"]
176
+ sample_size = cmd_args["sample_size"]
177
+ automatic_points = background_grid_selection(data, num_pts, tol, sample_size)
178
+ app_state_copy.background_points = automatic_points
179
+ return app_state_copy
180
+
181
+ def progress(self) -> float:
182
+ return 1.0
183
+
184
+ class ResetPointsHandler(PointHandler):
185
+ def execute(self, app_state: AppState, cmd_args: Dict) -> AppState:
186
+ app_state_copy = deepcopy(app_state)
187
+ app_state_copy.background_points.clear()
188
+ return app_state_copy
189
+
190
+ def progress(self) -> float:
191
+ return 1.0
192
+
193
+
194
+ INIT_HANDLER = InitHandler()
195
+ ADD_POINT_HANDLER = AddPointHandler()
196
+ ADD_POINTS_HANDLER = AddPointsHandler()
197
+ RM_POINT_HANDLER = RemovePointHandler()
198
+ MOVE_POINT_HANDLER = MovePointHandler()
199
+ SEL_POINTS_HANDLER = SelectPointsHandler()
200
+ RESET_POINTS_HANDLER = ResetPointsHandler()
@@ -0,0 +1,176 @@
1
+ import copy
2
+ import logging
3
+
4
+ import numpy as np
5
+
6
+ from graxpert.ai_model_handling import get_execution_providers_ordered
7
+ from graxpert.application.app_events import AppEvents
8
+ from graxpert.application.eventbus import eventbus
9
+
10
+
11
+ def deconvolve(image, ai_path, strength, psfsize, batch_size=4, window_size=512, stride=448, progress=None, ai_gpu_acceleration=True):
12
+ logging.info("Starting deconvolution")
13
+ if "stars" in ai_path:
14
+ type = "Stellar"
15
+ elif "obj" in ai_path:
16
+ type = "Obj"
17
+ strength = 0.95 * strength # TODO : strenght of exactly 1.0 brings no results, to fix
18
+
19
+ if type == "Stellar":
20
+ psfsize = np.clip((psfsize / 2.355 - 1.5) / 3.0, 0.05, 0.95) # Stellar
21
+ else:
22
+ if "1.0.0" in ai_path:
23
+ psfsize = np.clip((psfsize / 2.355 - 1.0) / 5.0, 0.05, 0.95) # Object v1.0.0
24
+ else:
25
+ psfsize = np.clip((psfsize / 2.355 - 0.5) / 5.5, 0.05, 0.95) # Object v1.0.1
26
+
27
+ logging.info(f"Calculated normalized PSFsize value: {psfsize}")
28
+
29
+ if batch_size < 1:
30
+ logging.info(f"mapping batch_size of {batch_size} to 1")
31
+ batch_size = 1
32
+ elif batch_size > 32:
33
+ logging.info(f"mapping batch_size of {batch_size} to 32")
34
+ batch_size = 32
35
+ elif not (batch_size & (batch_size - 1) == 0): # check if batch_size is power of two
36
+ logging.info(f"mapping batch_size of {batch_size} to {2 ** (batch_size).bit_length() // 2}")
37
+ batch_size = 2 ** (batch_size).bit_length() // 2 # map batch_size to power of two
38
+
39
+ if batch_size >= 4 and image.shape[-1] == 3:
40
+ batch_size = batch_size // 4
41
+
42
+ num_colors = image.shape[-1]
43
+
44
+ H, W, _ = image.shape
45
+ offset = int((window_size - stride) / 2)
46
+
47
+ h, w, _ = image.shape
48
+
49
+ ith = int(h / stride) + 1
50
+ itw = int(w / stride) + 1
51
+
52
+ dh = ith * stride - h
53
+ dw = itw * stride - w
54
+
55
+ image = np.concatenate((image, image[(h - dh) :, :, :]), axis=0)
56
+ image = np.concatenate((image, image[:, (w - dw) :, :]), axis=1)
57
+
58
+ h, w, _ = image.shape
59
+ image = np.concatenate((image, image[(h - offset) :, :, :]), axis=0)
60
+ image = np.concatenate((image[:offset, :, :], image), axis=0)
61
+ image = np.concatenate((image, image[:, (w - offset) :, :]), axis=1)
62
+ image = np.concatenate((image[:, :offset, :], image), axis=1)
63
+
64
+ output = copy.deepcopy(image)
65
+
66
+ providers = get_execution_providers_ordered(ai_gpu_acceleration)
67
+ import onnxruntime as ort # Must be after get_execution_providers_ordered
68
+ session = ort.InferenceSession(ai_path, providers=providers)
69
+
70
+ logging.info(f"Available inference providers : {providers}")
71
+ logging.info(f"Used inference providers : {session.get_providers()}")
72
+
73
+ cancel_flag = False
74
+
75
+ def cancel_listener(event):
76
+ nonlocal cancel_flag
77
+ cancel_flag = True
78
+
79
+ eventbus.add_listener(AppEvents.CANCEL_PROCESSING, cancel_listener)
80
+
81
+ last_progress = 0
82
+ for b in range(0, ith * itw + batch_size, batch_size):
83
+
84
+ if cancel_flag:
85
+ logging.info("Deconvolution cancelled")
86
+ eventbus.remove_listener(AppEvents.CANCEL_PROCESSING, cancel_listener)
87
+ return None
88
+
89
+ input_tiles = []
90
+ input_tile_copies = []
91
+ params = []
92
+ for t_idx in range(0, batch_size):
93
+
94
+ index = b + t_idx
95
+ i = index % ith
96
+ j = index // ith
97
+
98
+ if i >= ith or j >= itw:
99
+ break
100
+
101
+ x = stride * i
102
+ y = stride * j
103
+
104
+ tile = image[x : x + window_size, y : y + window_size, :]
105
+
106
+ _min = np.min(tile, axis=(0, 1))
107
+ tile = tile - _min + 1e-5
108
+ tile = np.log(tile)
109
+
110
+ _mean = tile.mean()
111
+ _std = tile.std()
112
+ _mean, _std = _mean.astype(np.float32), _std.astype(np.float32)
113
+ tile = (tile - _mean) / _std * 0.1
114
+ params.append([_mean, _std, _min])
115
+
116
+ input_tile_copies.append(np.copy(tile))
117
+
118
+ input_tiles.append(tile)
119
+
120
+ if not input_tiles:
121
+ continue
122
+
123
+ input_tiles = np.array(input_tiles)
124
+ input_tiles = np.moveaxis(input_tiles, -1, 1)
125
+ input_tiles = np.reshape(input_tiles, [input_tiles.shape[0] * num_colors, 1, window_size, window_size])
126
+
127
+ output_tiles = []
128
+ sigma = np.full(shape=(input_tiles.shape[0], 1), fill_value=psfsize, dtype=np.float32)
129
+ strenght_p = np.full(shape=(input_tiles.shape[0], 1), fill_value=strength, dtype=np.float32)
130
+ conds = np.concatenate([sigma, strenght_p], axis=-1)
131
+ if type == "Obj" and "1.0.0" in ai_path:
132
+ session_result = session.run(None, {"gen_input_image": input_tiles, "sigma": sigma, "strenght": strenght_p})[0]
133
+ else:
134
+ session_result = session.run(None, {"gen_input_image": input_tiles, "params": conds})[0]
135
+ for e in session_result:
136
+ output_tiles.append(e)
137
+
138
+ output_tiles = np.array(output_tiles)
139
+ output_tiles = input_tiles - output_tiles
140
+ output_tiles = np.reshape(output_tiles, [output_tiles.shape[0] // num_colors, num_colors, window_size, window_size])
141
+ output_tiles = np.moveaxis(output_tiles, 1, -1)
142
+
143
+ for idx in range(len(params)):
144
+ output_tiles[idx] = output_tiles[idx] * params[idx][1] / 0.1 + params[idx][0]
145
+ output_tiles[idx] = np.exp(output_tiles[idx])
146
+ output_tiles[idx] = output_tiles[idx] + params[idx][2] - 1e-5
147
+
148
+ for t_idx, tile in enumerate(output_tiles):
149
+
150
+ index = b + t_idx
151
+ i = index % ith
152
+ j = index // ith
153
+
154
+ if i >= ith or j >= itw:
155
+ break
156
+
157
+ x = stride * i
158
+ y = stride * j
159
+ tile = tile[offset : offset + stride, offset : offset + stride, :]
160
+ output[x + offset : stride * (i + 1) + offset, y + offset : stride * (j + 1) + offset, :] = tile
161
+
162
+ p = int(b / (ith * itw + batch_size) * 100)
163
+ if p > last_progress:
164
+ if progress is not None:
165
+ progress.update(p - last_progress)
166
+ else:
167
+ logging.info(f"Progress: {p}%")
168
+ last_progress = p
169
+
170
+ output = output[offset : H + offset, offset : W + offset, :]
171
+ output = np.clip(output, 0.0, 1.0)
172
+
173
+ eventbus.remove_listener(AppEvents.CANCEL_PROCESSING, cancel_listener)
174
+ logging.info("Finished deconvolution")
175
+
176
+ return output
graxpert/denoising.py ADDED
@@ -0,0 +1,178 @@
1
+ import copy
2
+ import logging
3
+ import time
4
+
5
+ import numpy as np
6
+
7
+ from graxpert.ai_model_handling import get_execution_providers_ordered
8
+ from graxpert.application.app_events import AppEvents
9
+ from graxpert.application.eventbus import eventbus
10
+ from graxpert.ui.ui_events import UiEvents
11
+
12
+
13
+ def denoise(image, ai_path, strength, batch_size=4, window_size=256, stride=128, progress=None, ai_gpu_acceleration=True):
14
+ logging.info("Starting denoising")
15
+
16
+ if batch_size < 1:
17
+ logging.info(f"mapping batch_size of {batch_size} to 1")
18
+ batch_size = 1
19
+ elif batch_size > 32:
20
+ logging.info(f"mapping batch_size of {batch_size} to 32")
21
+ batch_size = 32
22
+ elif not (batch_size & (batch_size - 1) == 0): # check if batch_size is power of two
23
+ logging.info(f"mapping batch_size of {batch_size} to {2 ** (batch_size).bit_length() // 2}")
24
+ batch_size = 2 ** (batch_size).bit_length() // 2 # map batch_size to power of two
25
+
26
+ input = copy.deepcopy(image)
27
+
28
+ median = np.median(image[::4, ::4, :], axis=[0, 1])
29
+ mad = np.median(np.abs(image[::4, ::4, :] - median), axis=[0, 1])
30
+
31
+ if "1.0.0" in ai_path or "1.1.0" in ai_path:
32
+ model_threshold = 1.0
33
+ else:
34
+ model_threshold = 10.0
35
+
36
+ global cached_denoised_image
37
+ if cached_denoised_image is not None:
38
+ return blend_images(input, cached_denoised_image, strength, model_threshold, median, mad)
39
+
40
+ num_colors = image.shape[-1]
41
+ if num_colors == 1:
42
+ image = np.array([image[:, :, 0], image[:, :, 0], image[:, :, 0]])
43
+ image = np.moveaxis(image, 0, -1)
44
+
45
+ H, W, _ = image.shape
46
+ offset = int((window_size - stride) / 2)
47
+
48
+ h, w, _ = image.shape
49
+
50
+ ith = int(h / stride) + 1
51
+ itw = int(w / stride) + 1
52
+
53
+ dh = ith * stride - h
54
+ dw = itw * stride - w
55
+
56
+ image = np.concatenate((image, image[(h - dh) :, :, :]), axis=0)
57
+ image = np.concatenate((image, image[:, (w - dw) :, :]), axis=1)
58
+
59
+ h, w, _ = image.shape
60
+ image = np.concatenate((image, image[(h - offset) :, :, :]), axis=0)
61
+ image = np.concatenate((image[:offset, :, :], image), axis=0)
62
+ image = np.concatenate((image, image[:, (w - offset) :, :]), axis=1)
63
+ image = np.concatenate((image[:, :offset, :], image), axis=1)
64
+
65
+ output = copy.deepcopy(image)
66
+
67
+ providers = get_execution_providers_ordered(ai_gpu_acceleration)
68
+ import onnxruntime as ort # Must be after get_execution_providers_ordered
69
+ session = ort.InferenceSession(ai_path, providers=providers)
70
+
71
+ logging.info(f"Available inference providers : {providers}")
72
+ logging.info(f"Used inference providers : {session.get_providers()}")
73
+
74
+ cancel_flag = False
75
+
76
+ def cancel_listener(event):
77
+ nonlocal cancel_flag
78
+ cancel_flag = True
79
+
80
+ eventbus.add_listener(AppEvents.CANCEL_PROCESSING, cancel_listener)
81
+
82
+ last_progress = 0
83
+ for b in range(0, ith * itw + batch_size, batch_size):
84
+
85
+ if cancel_flag:
86
+ logging.info("Denoising cancelled")
87
+ eventbus.remove_listener(AppEvents.CANCEL_PROCESSING, cancel_listener)
88
+ return None
89
+
90
+ input_tiles = []
91
+ input_tile_copies = []
92
+ for t_idx in range(0, batch_size):
93
+
94
+ index = b + t_idx
95
+ i = index % ith
96
+ j = index // ith
97
+
98
+ if i >= ith or j >= itw:
99
+ break
100
+
101
+ x = stride * i
102
+ y = stride * j
103
+
104
+ tile = image[x : x + window_size, y : y + window_size, :]
105
+ tile = (tile - median) / mad * 0.04
106
+ input_tile_copies.append(np.copy(tile))
107
+ tile = np.clip(tile, -model_threshold, model_threshold)
108
+
109
+ input_tiles.append(tile)
110
+
111
+ if not input_tiles:
112
+ continue
113
+
114
+ input_tiles = np.array(input_tiles)
115
+
116
+ output_tiles = []
117
+ session_result = session.run(None, {"gen_input_image": input_tiles})[0]
118
+ for e in session_result:
119
+ output_tiles.append(e)
120
+
121
+ output_tiles = np.array(output_tiles)
122
+
123
+ for t_idx, tile in enumerate(output_tiles):
124
+
125
+ index = b + t_idx
126
+ i = index % ith
127
+ j = index // ith
128
+
129
+ if i >= ith or j >= itw:
130
+ break
131
+
132
+ x = stride * i
133
+ y = stride * j
134
+ tile = np.where(input_tile_copies[t_idx] < model_threshold, tile, input_tile_copies[t_idx])
135
+ tile = tile / 0.04 * mad + median
136
+ tile = tile[offset : offset + stride, offset : offset + stride, :]
137
+ output[x + offset : stride * (i + 1) + offset, y + offset : stride * (j + 1) + offset, :] = tile
138
+
139
+ p = int(b / (ith * itw + batch_size) * 100)
140
+ if p > last_progress:
141
+ if progress is not None:
142
+ progress.update(p - last_progress)
143
+ else:
144
+ logging.info(f"Progress: {p}%")
145
+ last_progress = p
146
+
147
+ output = output[offset : H + offset, offset : W + offset, :]
148
+
149
+ if num_colors == 1:
150
+ output = np.array([output[:, :, 0]])
151
+ output = np.moveaxis(output, 0, -1)
152
+
153
+ cached_denoised_image = output
154
+ output = blend_images(input, output, strength, model_threshold, median, mad)
155
+
156
+ eventbus.remove_listener(AppEvents.CANCEL_PROCESSING, cancel_listener)
157
+ logging.info("Finished denoising")
158
+
159
+ return output
160
+
161
+
162
+ def blend_images(original_image, denoised_image, strength, threshold, median, mad):
163
+ threshold = threshold / 0.04 * mad + median
164
+ blend = np.where(original_image < threshold, denoised_image, original_image)
165
+ blend = blend * strength + original_image * (1 - strength)
166
+ return np.clip(blend, 0, 1)
167
+
168
+
169
+ def reset_cached_denoised_image(event):
170
+ global cached_denoised_image
171
+ cached_denoised_image = None
172
+
173
+
174
+ cached_denoised_image = None
175
+ eventbus.add_listener(AppEvents.LOAD_IMAGE_REQUEST, reset_cached_denoised_image)
176
+ eventbus.add_listener(AppEvents.CALCULATE_REQUEST, reset_cached_denoised_image)
177
+ eventbus.add_listener(UiEvents.APPLY_CROP_REQUEST, reset_cached_denoised_image)
178
+ eventbus.add_listener(AppEvents.DENOISE_AI_VERSION_CHANGED, reset_cached_denoised_image)
graxpert/grid_utils.py ADDED
@@ -0,0 +1,38 @@
1
+ import logging
2
+ import math
3
+
4
+ import numpy as np
5
+
6
+
7
+ def find_darkest_quadrant(x, y, data_padded, sample_size):
8
+
9
+ cords = [
10
+ [x + 0, y + 0],
11
+ [x + sample_size, y + sample_size],
12
+ [x - sample_size, y + sample_size],
13
+ [x + sample_size, y - sample_size],
14
+ [x - sample_size, y - sample_size],
15
+ ]
16
+ median = []
17
+ for point in cords:
18
+ if (
19
+ point[0] < 0
20
+ or point[0] > (data_padded.shape[1] - 2 * sample_size)
21
+ or point[1] < 0
22
+ or point[1] > (data_padded.shape[0] - 2 * sample_size)
23
+ ):
24
+ median.append(2)
25
+ else:
26
+ m = np.median(
27
+ data_padded[
28
+ point[1] : point[1] + 2 * sample_size,
29
+ point[0] : point[0] + 2 * sample_size,
30
+ ]
31
+ )
32
+ if math.isnan(m):
33
+ logging.error("computed median is NaN", stack_info=True)
34
+ median.append(m)
35
+
36
+ min_idx = np.argmin(median)
37
+
38
+ return cords[min_idx], median[min_idx]
graxpert/img/Icon.png ADDED
Binary file
Binary file
Binary file
File without changes
Binary file