Passed
Push — master ( c3d045...3525ae )
by Konstantinos
01:55 queued 43s
created

start_nst_thread()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
import os
2
import tkinter as tk
3
from tkinter import filedialog
4
import threading
5
from pathlib import Path
6
from PIL import Image, ImageTk  # You need to install the Python Imaging Library (PIL)
7
8
# from artificial_artwork._demo import create_algo_runner
9
from artificial_artwork._main import create_algo_runner
10
from artificial_artwork.image import convert_to_uint8
11
12
# Dir in which this python file/script resides within the Source Distribution (ie Git Repo)
13
MY_DIR: str = os.path.dirname(os.path.realpath(__file__))
14
15
16
# CONSTANTS
17
IMAGE_COMP_ASSETS = {
18
    'content': {
19
        'load_button_text': "Select Content Image",
20
        'label_text': "Content Image:",
21
    },
22
    'style': {
23
        'load_button_text': "Select Style Image",
24
        'label_text': "Style Image:",
25
    },
26
}
27
28
# width x height
29
WINDOW_GEOMETRY: str = '2600x1800'
30
31
# Content and Style Images rendering dimensions
32
INPUT_IMAGE_THUMBNAIL_SIZE = (200, 200)
33
34
# Generated Image rendering dimensions
35
GENERATED_IMAGE_THUMBNAIL_SIZE = (500, 500)
36
37
38
# Helpers Objects
39
40
img_type_2_path = {}
41
42
# Helper Functions
43
44
# Handle Click on Load Content/Style Image Button by loading the Image and rendering it on the UI
45 View Code Duplication
def _build_open_image_dialog_callback_v2(x, image_type: str):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
46
    def _open_file_dialog_v2():
47
        file_path = filedialog.askopenfilename()
48
        if file_path:
49
            image_label = x['image_label']
50
            image_pane = x['image_pane']
51
52
            img_type_2_path[image_type] = file_path
53
54
            image = Image.open(file_path)
55
            image.thumbnail(INPUT_IMAGE_THUMBNAIL_SIZE)  # Resize the image to fit in the pane
56
            photo = ImageTk.PhotoImage(image=image)
57
58
            image_pane.config(image=photo)
59
            image_pane.image = photo
60
61
            image_label.config(text=f'{IMAGE_COMP_ASSETS[image_type]["label_text"]} {file_path}')
62
            image_label.update_idletasks()
63
    return _open_file_dialog_v2
64
65
66
def _load_nst_image_and_render(nst_image_ui, file_path):
67
    image_label = nst_image_ui['image_label']  # what gets shown on the UI for a Loaded NST Image (Content or Style)
68
    image_pane = nst_image_ui['image_pane']  # where the image gets rendered on the UI
69
    image_type: str = nst_image_ui['image_type']  # item in set {'content', 'style'}
70
71
    # Inform Global State of the currently selected Image to use as Input for the NST Algorithm
72
    img_type_2_path[image_type] = file_path
73
74
    image = Image.open(file_path)
75
    image.thumbnail(INPUT_IMAGE_THUMBNAIL_SIZE)  # Resize the image to fit in the pane
76
    photo = ImageTk.PhotoImage(image=image)
77
78
    image_pane.config(image=photo)
79
    image_pane.image = photo
80
81
    image_label.config(text=f'{IMAGE_COMP_ASSETS[image_type]["label_text"]} {file_path}')
82
    image_label.update_idletasks()
83
84
# MAIN
85
86
images_components_data = {
87
    'content': dict(
88
        # Data to be shared when implementing handling of initialization (initial Render) or updating (re-render 'request') of UI Components 
89
        IMAGE_COMP_ASSETS['content'],
90
        # image_dialog key maps to a Callable that takes NO input (args and/or kwargs)
91
        # this callable should be an object compatible as value to the 'command' (kwarg) of a tkinter.Button constructor
92
        # the callable implements what happens when the User clicks the button
93
        image_dialog=lambda x: _build_open_image_dialog_callback_v2(x, 'content'),
94
    ),
95
    'style': dict(
96
        # Data to be shared when implementing handling of initialization (initial Render) or updating (re-render 'request') of UI Components 
97
        IMAGE_COMP_ASSETS['style'],
98
        image_dialog=lambda x: _build_open_image_dialog_callback_v2(x, 'style'),
99
    ),
100
}
101
102
103
# Create the main window
104
root = tk.Tk()
105
root.title("Neural Style Transfer - Desktop")
106
# width x height
107
root.geometry("2600x1800")  # Larger window size
108
109
# Add a label to describe the purpose of the GUI
110
description_label = tk.Label(root, text="Select a file using the buttons below:")
111
description_label.pack(pady=10)  # Add padding
112
113
114
# START - CONTENT IMAGE UI/UX
115
116
# BUTTON -> Load Content Image
117
select_content_image_btn = tk.Button(
118
    root,
119
    text=images_components_data['content']['load_button_text'],
120
    command=lambda: images_components_data['content']['image_dialog']({
121
        'image_label': content_image_label,  # The Label UI Element to update when selected and loaded Loaded
122
        'image_pane': content_image_pane,  # The Pane to Render the Content Image once Selected and Loaded
123
    })(),
124
)
125
select_content_image_btn.pack(pady=5)  # Add padding
126
127
# LABEL -> Show path of loaded Content Image
128
DEMO_IMAGE = Path(MY_DIR) / 'tests' / 'data' / 'canoe_water_w300-h225.jpg'
129
130
# Initialize with rendered text conveying the message that no image has been selected yet
131
132
content_image_label = tk.Label(root, text=images_components_data['content']['label_text'])
133
content_image_label.pack()
134
135
# LABEL -> PANE to Render the Content Image
136
content_image_pane = tk.Label(root, width=0, height=0, bg="white")  # Set initial dimensions to 0
137
# content_image_pane = tk.Label(root, width=200, height=200, bg="white")
138
content_image_pane.pack()
139
140
# Automatically Load the Demo Content Image: read from disk update Image Pane and Label in UI
141
# content_image_label.config(text=f"{images_components_data['content']['label_text']} {DEMO_IMAGE}")
142
# content_image_label.update_idletasks()
143
# content_image_label.pack()
144
145
content_image_label.pack()
146
_load_nst_image_and_render({
147
    'image_label': content_image_label,
148
    'image_pane': content_image_pane,
149
    'image_type': 'content',
150
}, DEMO_IMAGE)
151
152
# END - CONTENT IMAGE UI/UX
153
154
155
# Start - STYLE IMAGE UI/UX
156
157
# BUTTON -> Load Style Image
158
load_style_image_btn = tk.Button(
159
    root,
160
    text=images_components_data['style']['load_button_text'],
161
    command=lambda: images_components_data['style']['image_dialog']({
162
        'image_label': style_image_label,  # update label once user selected a file from the dialog
163
        'image_pane': style_image_pane,
164
    })(),
165
)
166
load_style_image_btn.pack(pady=5)  # Add padding
167
168
DEMO_STYLE_IMAGE = Path(MY_DIR) / 'tests' / 'data' / 'blue-red_w300-h225.jpg'
169
170
# LABEL -> Show path of loaded Style Image
171
172
# Initialize and Render constant Placeholder text
173
style_image_label = tk.Label(root, text=images_components_data['style']['label_text'])
174
style_image_label.pack()
175
176
# LABEL -> PANE to Render the Style Image
177
style_image_pane = tk.Label(root, width=0, height=0, bg="white")  # Set initial dimensions to 0
178
# style_image_pane = tk.Label(root, width=200, height=200, bg="white")
179
style_image_pane.pack()
180
181
# OR Initialize with preloaded Demo Content Image
182
# style_image_label = tk.Label(root,
183
#     text=f"{images_components_data['content']['label_text']} {DEMO_STYLE_IMAGE}"
184
# )
185
186
# style_image_label.config(text=f"{images_components_data['content']['label_text']} {DEMO_STYLE_IMAGE}")
187
# style_image_label.update_idletasks()
188
189
# style_image_label.pack()
190
191
_load_nst_image_and_render({
192
    'image_label': style_image_label,
193
    'image_pane': style_image_pane,
194
    'image_type': 'style',
195
}, DEMO_STYLE_IMAGE)
196
197
198
# End - STYLE IMAGE UI/UX
199
200
201
# GENERATED IMAGE UI/UX
202
203
# Helper Update Callback
204
def update_image_thread(progress, gen_image_pane, _iteration_count_label):
205
    t = threading.Thread(
206
        target=update_image,
207
        args=(progress, gen_image_pane, _iteration_count_label)
208
    )
209
    t.start()
210
211
# Function to update the GUI with the result from the backend task
212
def update_image(progress, gen_image_pane, _iteration_count_label):
213
    numpy_image_array = progress.state.matrix
214
    current_iteration_count: int = progress.state.metrics['iterations']
215
216
    # if we have shape of form (1, Width, Height, Number_of_Color_Channels)
217
    if numpy_image_array.ndim == 4 and numpy_image_array.shape[0] == 1:
218
        import numpy as np
219
        # reshape to (Width, Height, Number_of_Color_Channels)
220
        matrix = np.reshape(numpy_image_array, tuple(numpy_image_array.shape[1:]))
221
222
    if str(matrix.dtype) != 'uint8':
0 ignored issues
show
introduced by
The variable matrix does not seem to be defined in case numpy_image_array.ndim == 4 and SubscriptNode == 1 on line 217 is False. Are you sure this can never be the case?
Loading history...
223
        matrix = convert_to_uint8(matrix)
224
225
    ## Prod code: broken
226
    # convert numpy array to PIL image
227
    # image = Image.fromarray(numpy_image_array)
228
    ##
229
230
    image = Image.fromarray(matrix)
231
232
    # Resize the image to fit in the pane
233
    image.thumbnail(GENERATED_IMAGE_THUMBNAIL_SIZE)
234
    # Convert the image to PhotoImage
235
    photo = ImageTk.PhotoImage(image=image)
236
    # Update the image label with the new image
237
    gen_image_pane.config(image=photo)
238
    gen_image_pane.image = photo
239
240
    _iteration_count_label.config(text=f'Iteration Count: {current_iteration_count}')
241
242
backend_object = create_algo_runner(
243
    iterations=100,  # NB of Times to pass Image through the Network
244
    output_folder='gui-output-folder',  # Output Folder to store gen img snapshots
245
    noisy_ratio=0.6,
246
)
247
# {
248
#     'algorithm_runner': algorithm_runner,
249
#     'run': lambda: algorithm_runner.run(algorithm, model_design),
250
#     'subscribe': lambda observer: algorithm_runner.progress_subject.add(observer),
251
# }
252
253
observer = type('Observer', (), {
254
    # 'update': lambda progress: update_image(progress, generated_image_pane, iteration_count_label),
255
    'update': lambda progress: update_image_thread(progress, generated_image_pane, iteration_count_label),
256
})
257
backend_object['subscribe'](observer)
258
259
# Pane for displaying generated image (this will be updated during the learning loop)
260
generated_image_label = tk.Label(root, text="Generated Image:")
261
generated_image_label.pack(pady=10)
262
263
generated_image_pane = tk.Label(root, width=0, height=0, bg="white")  # Set initial dimensions to 0
264
# generated_image_pane = tk.Label(root, width=600, height=600, bg="white")
265
generated_image_pane.pack(pady=5)
266
267
# ITERATION COUNT UI/UX
268
iteration_count_label = tk.Label(root, text="Iteration Count:")
269
iteration_count_label.pack(pady=5)
270
271
272
# RUN NST ALGORITHM UI/UX
273
274
# Helper Run Functions
275
276
# Run Computations
277 View Code Duplication
def run_nst():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
278
    backend_object = create_algo_runner(
279
        iterations=100,  # NB of Times to pass Image through the Network
280
        output_folder='gui-output-folder',  # Output Folder to store gen img snapshots
281
        noisy_ratio=0.6,
282
    )
283
    observer = type('Observer', (), {
284
        'update': lambda progress: update_image(progress, generated_image_pane, iteration_count_label),
285
        # 'update': lambda progress: update_image_thread(progress, generated_image_pane, iteration_count_label),
286
    })
287
    backend_object['subscribe'](observer)
288
289
    content_image_path = img_type_2_path['content']
290
    style_image_path = img_type_2_path['style']
291
292
    if content_image_path and style_image_path:
293
        backend_object['run'](
294
            content_image_path,
295
            style_image_path,
296
        )
297
298
import concurrent.futures
299
# Function to run the backend task in a separate thread
300
def start_backend_task():
301
    
302
    # # Create your backend object using create_algo_runner
303
    # backend_object = create_algo_runner(iterations=100, output_folder='gui-output-folder', noisy_ratio=0.6)
304
    
305
    # # Define an observer object to handle progress updates, by updating the UI
306
    # observer = type('Observer', (), {
307
    #     'update': lambda progress: update_image(progress, generated_image_pane, iteration_count_label),
308
    #     # 'update': lambda progress: update_image_thread(progress, generated_image_pane, iteration_count_label),
309
    # })   
310
311
    # # Subscribe the observer to the backend object's progress
312
    # backend_object['subscribe'](observer)
313
314
    content_image_path = img_type_2_path['content']
315
    style_image_path = img_type_2_path['style']
316
317
    if content_image_path and style_image_path:
318
        def _run():
319
            backend_object['run'](
320
                content_image_path,
321
                style_image_path,
322
            )
323
        # Create a thread to run the backend task
324
        with concurrent.futures.ThreadPoolExecutor() as executor:
325
            future = executor.submit(_run)
326
327
328
# Function to execute run_nst in a separate thread
329
# def start_nst_thread():
330
#     with concurrent.futures.ThreadPoolExecutor() as executor:
331
#         future = executor.submit(run_nst)
332
        # You can optionally add callbacks for handling the results
333
334
# Threaded Run Computations
335
# def start_nst_thread():
336
#     import tensorflow as tf
337
338
#     # Create a new TensorFlow graph and session in the new thread
339
#     # with tf.Graph().as_default(), tf.Session() as sess:
340
#         # Define TensorFlow operations
341
#         # ...
342
343
#         # Enqueue TensorFlow operations to be executed in the new thread
344
#     coord = tf.train.Coordinator()
345
#     enqueue_thread = tf.train.QueueRunner(tf.train.string_input_producer(["dummy_data"]))
346
#     threads = enqueue_thread.create_threads(sess, coord=coord, start=True)
347
348
#     # Start the TensorFlow operations within the new thread
349
#     run_nst()
350
351
def start_nst_thread():
352
    nst_thread = threading.Thread(target=run_nst)
353
    nst_thread.daemon = True  # Set as a daemon thread to exit when the main program exits
354
    nst_thread.start()
355
356
# BUTTON -> Run NST Algorithm on press
357
# run_nst_btn = tk.Button(
358
#     root,
359
#     text="Run NST Algorithm",
360
#     command=start_nst_thread,  # Start the thread when the button is pressed
361
# )
362
run_nst_btn = tk.Button(
363
    root,
364
    text="Run NST Algorithm",
365
    command=start_nst_thread,
366
)
367
368
run_nst_btn.pack(pady=5)  # Add padding
369
370
371
# Add a label to display the selected file
372
# file_label = tk.Label(root, text="", wraplength=300)  # Wrap text for better display
373
# file_label.pack(pady=10)  # Add padding
374
375
376
# style_image_pane = tk.Label(root, width=200, height=200, bg="white")
377
# style_image_pane.pack()
378
379
root.mainloop()
380