spacr 0.3.1__py3-none-any.whl → 0.3.22__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.
- spacr/__init__.py +19 -3
- spacr/cellpose.py +311 -0
- spacr/core.py +245 -2494
- spacr/deep_spacr.py +316 -48
- spacr/gui.py +1 -0
- spacr/gui_core.py +74 -63
- spacr/gui_elements.py +110 -5
- spacr/gui_utils.py +346 -6
- spacr/io.py +680 -141
- spacr/logger.py +28 -9
- spacr/measure.py +107 -95
- spacr/mediar.py +0 -3
- spacr/ml.py +1051 -0
- spacr/openai.py +37 -0
- spacr/plot.py +707 -20
- spacr/resources/data/lopit.csv +3833 -0
- spacr/resources/data/toxoplasma_metadata.csv +8843 -0
- spacr/resources/icons/convert.png +0 -0
- spacr/resources/{models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model → icons/dna_matrix.mp4} +0 -0
- spacr/sequencing.py +241 -1311
- spacr/settings.py +134 -47
- spacr/sim.py +0 -2
- spacr/submodules.py +349 -0
- spacr/timelapse.py +0 -2
- spacr/toxo.py +238 -0
- spacr/utils.py +419 -180
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/METADATA +31 -22
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/RECORD +32 -33
- spacr/chris.py +0 -50
- spacr/graph_learning.py +0 -340
- spacr/resources/MEDIAR/.git +0 -1
- spacr/resources/MEDIAR_weights/.DS_Store +0 -0
- spacr/resources/icons/.DS_Store +0 -0
- spacr/resources/icons/spacr_logo_rotation.gif +0 -0
- spacr/resources/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model_settings.csv +0 -23
- spacr/resources/models/cp/toxo_pv_lumen.CP_model +0 -0
- spacr/sim_app.py +0 -0
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/LICENSE +0 -0
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/WHEEL +0 -0
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/entry_points.txt +0 -0
- {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/top_level.txt +0 -0
spacr/gui_core.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
import traceback, ctypes, csv, re, platform
|
1
|
+
import os, traceback, ctypes, csv, re, platform
|
2
2
|
import tkinter as tk
|
3
3
|
from tkinter import ttk
|
4
4
|
from tkinter import filedialog
|
5
5
|
from multiprocessing import Process, Value, Queue, set_start_method
|
6
|
-
from tkinter import ttk
|
6
|
+
from tkinter import ttk
|
7
7
|
from matplotlib.figure import Figure
|
8
8
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
9
9
|
import numpy as np
|
@@ -11,15 +11,13 @@ import psutil
|
|
11
11
|
import GPUtil
|
12
12
|
from collections import deque
|
13
13
|
import tracemalloc
|
14
|
-
from tkinter import Menu
|
15
|
-
import io
|
16
14
|
|
17
15
|
try:
|
18
16
|
ctypes.windll.shcore.SetProcessDpiAwareness(True)
|
19
17
|
except AttributeError:
|
20
18
|
pass
|
21
19
|
|
22
|
-
from .gui_elements import spacrProgressBar, spacrButton,
|
20
|
+
from .gui_elements import spacrProgressBar, spacrButton, spacrFrame, spacrDropdownMenu , spacrSlider, set_dark_style
|
23
21
|
|
24
22
|
# Define global variables
|
25
23
|
q = None
|
@@ -35,6 +33,7 @@ figures = None
|
|
35
33
|
figure_index = None
|
36
34
|
progress_bar = None
|
37
35
|
usage_bars = None
|
36
|
+
index_control = None
|
38
37
|
|
39
38
|
thread_control = {"run_thread": None, "stop_requested": False}
|
40
39
|
|
@@ -170,39 +169,6 @@ def display_figure(fig):
|
|
170
169
|
#flash_feedback("right")
|
171
170
|
show_next_figure()
|
172
171
|
|
173
|
-
def zoom_v1(event):
|
174
|
-
nonlocal scale_factor
|
175
|
-
|
176
|
-
zoom_speed = 0.1 # Adjust the zoom speed for smoother experience
|
177
|
-
|
178
|
-
# Adjust zoom factor based on the operating system and mouse event
|
179
|
-
if event.num == 4 or event.delta > 0: # Scroll up
|
180
|
-
scale_factor *= (1 + zoom_speed)
|
181
|
-
elif event.num == 5 or event.delta < 0: # Scroll down
|
182
|
-
scale_factor /= (1 + zoom_speed)
|
183
|
-
|
184
|
-
# Get mouse position relative to the figure
|
185
|
-
x_mouse, y_mouse = event.x, event.y
|
186
|
-
x_ratio = x_mouse / canvas_widget.winfo_width()
|
187
|
-
y_ratio = y_mouse / canvas_widget.winfo_height()
|
188
|
-
|
189
|
-
for ax in fig.get_axes():
|
190
|
-
xlim = ax.get_xlim()
|
191
|
-
ylim = ax.get_ylim()
|
192
|
-
|
193
|
-
# Calculate the new limits
|
194
|
-
x_center = xlim[0] + x_ratio * (xlim[1] - xlim[0])
|
195
|
-
y_center = ylim[0] + (1 - y_ratio) * (ylim[1] - ylim[0])
|
196
|
-
|
197
|
-
x_range = (xlim[1] - xlim[0]) * scale_factor
|
198
|
-
y_range = (ylim[1] - ylim[0]) * scale_factor
|
199
|
-
|
200
|
-
ax.set_xlim([x_center - x_range * x_ratio, x_center + x_range * (1 - x_ratio)])
|
201
|
-
ax.set_ylim([y_center - y_range * (1 - y_ratio), y_center + y_range * y_ratio])
|
202
|
-
|
203
|
-
# Redraw the figure
|
204
|
-
fig.canvas.draw_idle()
|
205
|
-
|
206
172
|
def zoom(event):
|
207
173
|
nonlocal scale_factor
|
208
174
|
|
@@ -282,7 +248,7 @@ def show_next_figure():
|
|
282
248
|
figures.append(fig)
|
283
249
|
figure_index += 1
|
284
250
|
display_figure(fig)
|
285
|
-
|
251
|
+
|
286
252
|
def process_fig_queue():
|
287
253
|
global canvas, fig_queue, canvas_widget, parent_frame, uppdate_frequency, figures, figure_index, index_control
|
288
254
|
|
@@ -329,44 +295,60 @@ def update_figure(value):
|
|
329
295
|
index_control.set_to(len(figures) - 1)
|
330
296
|
index_control.set(figure_index)
|
331
297
|
|
332
|
-
def setup_plot_section(vertical_container):
|
298
|
+
def setup_plot_section(vertical_container, settings_type):
|
333
299
|
global canvas, canvas_widget, figures, figure_index, index_control
|
300
|
+
from .gui_utils import display_media_in_plot_frame
|
301
|
+
|
302
|
+
style_out = set_dark_style(ttk.Style())
|
303
|
+
bg = style_out['bg_color']
|
304
|
+
fg = style_out['fg_color']
|
334
305
|
|
335
306
|
# Initialize deque for storing figures and the current index
|
336
307
|
figures = deque()
|
337
308
|
|
338
309
|
# Create a frame for the plot section
|
339
310
|
plot_frame = tk.Frame(vertical_container)
|
311
|
+
plot_frame.configure(bg=bg)
|
340
312
|
vertical_container.add(plot_frame, stretch="always")
|
341
313
|
|
342
|
-
#
|
314
|
+
# Clear the plot_frame (optional, to handle cases where it may already have content)
|
315
|
+
for widget in plot_frame.winfo_children():
|
316
|
+
widget.destroy()
|
317
|
+
|
318
|
+
# Create a figure and plot
|
343
319
|
figure = Figure(figsize=(30, 4), dpi=100)
|
344
320
|
plot = figure.add_subplot(111)
|
345
321
|
plot.plot([], [])
|
346
322
|
plot.axis('off')
|
323
|
+
|
324
|
+
if settings_type == 'map_barcodes':
|
325
|
+
# Load and display GIF
|
326
|
+
current_dir = os.path.dirname(__file__)
|
327
|
+
resources_path = os.path.join(current_dir, 'resources', 'icons')
|
328
|
+
gif_path = os.path.join(resources_path, 'dna_matrix.mp4')
|
329
|
+
|
330
|
+
display_media_in_plot_frame(gif_path, plot_frame)
|
331
|
+
canvas = FigureCanvasTkAgg(figure, master=plot_frame)
|
332
|
+
canvas.get_tk_widget().configure(cursor='arrow', highlightthickness=0)
|
333
|
+
canvas_widget = canvas.get_tk_widget()
|
334
|
+
return canvas, canvas_widget
|
347
335
|
|
348
336
|
canvas = FigureCanvasTkAgg(figure, master=plot_frame)
|
349
337
|
canvas.get_tk_widget().configure(cursor='arrow', highlightthickness=0)
|
350
338
|
canvas_widget = canvas.get_tk_widget()
|
351
339
|
canvas_widget.grid(row=0, column=0, sticky="nsew")
|
352
|
-
|
353
340
|
plot_frame.grid_rowconfigure(0, weight=1)
|
354
341
|
plot_frame.grid_columnconfigure(0, weight=1)
|
355
|
-
|
356
342
|
canvas.draw()
|
357
|
-
canvas.figure = figure
|
358
|
-
style_out = set_dark_style(ttk.Style())
|
359
|
-
bg = style_out['bg_color']
|
360
|
-
fg = style_out['fg_color']
|
361
|
-
|
343
|
+
canvas.figure = figure
|
362
344
|
figure.patch.set_facecolor(bg)
|
363
345
|
plot.set_facecolor(bg)
|
364
346
|
containers = [plot_frame]
|
365
347
|
|
366
348
|
# Create slider
|
367
|
-
control_frame = tk.Frame(plot_frame, height=15*2, bg=bg)
|
349
|
+
control_frame = tk.Frame(plot_frame, height=15*2, bg=bg)
|
368
350
|
control_frame.grid(row=1, column=0, sticky="ew", padx=10, pady=5)
|
369
|
-
control_frame.grid_propagate(False)
|
351
|
+
control_frame.grid_propagate(False)
|
370
352
|
|
371
353
|
# Pass the update_figure function as the command to spacrSlider
|
372
354
|
index_control = spacrSlider(control_frame, from_=0, to=0, value=0, thickness=2, knob_radius=10, position="center", show_index=True, command=update_figure)
|
@@ -442,6 +424,8 @@ def import_settings(settings_type='mask'):
|
|
442
424
|
settings = get_analyze_recruitment_default_settings(settings={})
|
443
425
|
elif settings_type == 'analyze_plaques':
|
444
426
|
settings = {}
|
427
|
+
elif settings_type == 'convert':
|
428
|
+
settings = {}
|
445
429
|
else:
|
446
430
|
raise ValueError(f"Invalid settings type: {settings_type}")
|
447
431
|
|
@@ -452,7 +436,8 @@ def import_settings(settings_type='mask'):
|
|
452
436
|
|
453
437
|
def setup_settings_panel(vertical_container, settings_type='mask'):
|
454
438
|
global vars_dict, scrollable_frame
|
455
|
-
from .settings import get_identify_masks_finetune_default_settings, set_default_analyze_screen, set_default_settings_preprocess_generate_masks, get_measure_crop_settings, deep_spacr_defaults, set_default_generate_barecode_mapping, set_default_umap_image_settings
|
439
|
+
from .settings import get_identify_masks_finetune_default_settings, set_default_analyze_screen, set_default_settings_preprocess_generate_masks, get_measure_crop_settings, deep_spacr_defaults, set_default_generate_barecode_mapping, set_default_umap_image_settings
|
440
|
+
from .settings import get_map_barcodes_default_settings, get_analyze_recruitment_default_settings, get_check_cellpose_models_default_settings, generate_fields, get_perform_regression_default_settings, get_train_cellpose_default_settings
|
456
441
|
from .gui_utils import convert_settings_dict_for_gui
|
457
442
|
from .gui_elements import set_element_size
|
458
443
|
|
@@ -496,7 +481,9 @@ def setup_settings_panel(vertical_container, settings_type='mask'):
|
|
496
481
|
elif settings_type == 'recruitment':
|
497
482
|
settings = get_analyze_recruitment_default_settings(settings={})
|
498
483
|
elif settings_type == 'analyze_plaques':
|
499
|
-
settings = {}
|
484
|
+
settings = {'src':'path to images'}
|
485
|
+
elif settings_type == 'convert':
|
486
|
+
settings = {'src':'path to images'}
|
500
487
|
else:
|
501
488
|
raise ValueError(f"Invalid settings type: {settings_type}")
|
502
489
|
|
@@ -515,7 +502,7 @@ def setup_settings_panel(vertical_container, settings_type='mask'):
|
|
515
502
|
def setup_console(vertical_container):
|
516
503
|
global console_output
|
517
504
|
from .gui_elements import set_dark_style
|
518
|
-
|
505
|
+
|
519
506
|
# Apply dark style and get style output
|
520
507
|
style = ttk.Style()
|
521
508
|
style_out = set_dark_style(style)
|
@@ -546,9 +533,27 @@ def setup_console(vertical_container):
|
|
546
533
|
def on_leave(event):
|
547
534
|
top_border.config(bg=style_out['bg_color'])
|
548
535
|
|
536
|
+
#def on_enter_key(event):
|
537
|
+
# user_input = console_output.get("1.0", "end-1c").strip() # Get the user input from the console
|
538
|
+
# if user_input:
|
539
|
+
# # Print the user input with the (user) tag
|
540
|
+
# console_output.insert("end", f"\n(user): {user_input}\n")
|
541
|
+
#
|
542
|
+
# # Get the AI response from the chatbot
|
543
|
+
# response = chatbot.ask_question(user_input)
|
544
|
+
#
|
545
|
+
# # Print the AI response with the (ai) tag
|
546
|
+
# console_output.insert("end", f"(ai): {response}\n")
|
547
|
+
#
|
548
|
+
# console_output.see("end") # Scroll to the end
|
549
|
+
# #console_output.delete("1.0", "end") # Clear the input field
|
550
|
+
# return "break" # Prevent the default behavior of inserting a new line
|
551
|
+
|
549
552
|
console_output.bind("<Enter>", on_enter)
|
550
553
|
console_output.bind("<Leave>", on_leave)
|
551
554
|
|
555
|
+
#console_output.bind("<Return>", on_enter_key)
|
556
|
+
|
552
557
|
return console_output, console_frame
|
553
558
|
|
554
559
|
def setup_button_section(horizontal_container, settings_type='mask', run=True, abort=True, download=True, import_btn=True):
|
@@ -755,7 +760,7 @@ def initiate_abort():
|
|
755
760
|
def start_process(q=None, fig_queue=None, settings_type='mask'):
|
756
761
|
global thread_control, vars_dict, parent_frame
|
757
762
|
from .settings import check_settings, expected_types
|
758
|
-
from .gui_utils import run_function_gui,
|
763
|
+
from .gui_utils import run_function_gui, set_cpu_affinity, initialize_cuda, display_gif_in_plot_frame, print_widget_structure
|
759
764
|
|
760
765
|
if q is None:
|
761
766
|
q = Queue()
|
@@ -778,16 +783,14 @@ def start_process(q=None, fig_queue=None, settings_type='mask'):
|
|
778
783
|
|
779
784
|
process_args = (settings_type, settings, q, fig_queue, stop_requested)
|
780
785
|
if settings_type in ['mask', 'umap', 'measure', 'simulation', 'sequencing', 'classify', 'analyze_plaques',
|
781
|
-
'cellpose_dataset', 'train_cellpose', 'ml_analyze', 'cellpose_masks', 'cellpose_all',
|
782
|
-
'regression', 'recruitment', 'cellpose_compare', 'vision_scores',
|
786
|
+
'cellpose_dataset', 'train_cellpose', 'ml_analyze', 'cellpose_masks', 'cellpose_all',
|
787
|
+
'map_barcodes', 'regression', 'recruitment', 'cellpose_compare', 'vision_scores',
|
788
|
+
'vision_dataset', 'convert']:
|
783
789
|
|
784
790
|
# Start the process
|
785
791
|
process = Process(target=run_function_gui, args=process_args)
|
786
792
|
process.start()
|
787
793
|
|
788
|
-
# Set high priority for the process
|
789
|
-
#set_high_priority(process)
|
790
|
-
|
791
794
|
# Set CPU affinity if necessary
|
792
795
|
set_cpu_affinity(process)
|
793
796
|
|
@@ -889,10 +892,14 @@ def initiate_root(parent, settings_type='mask'):
|
|
889
892
|
|
890
893
|
global q, fig_queue, thread_control, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, button_scrollable_frame, progress_bar, uppdate_frequency, figures, figure_index, index_control, usage_bars
|
891
894
|
|
892
|
-
from .gui_utils import setup_frame
|
895
|
+
from .gui_utils import setup_frame, get_screen_dimensions
|
893
896
|
from .settings import descriptions
|
897
|
+
#from .openai import Chatbot
|
894
898
|
|
895
899
|
uppdate_frequency = 500
|
900
|
+
num_cores = os.cpu_count()
|
901
|
+
|
902
|
+
#chatbot = Chatbot(api_key="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")
|
896
903
|
|
897
904
|
# Start tracemalloc and initialize global variables
|
898
905
|
tracemalloc.start()
|
@@ -930,10 +937,14 @@ def initiate_root(parent, settings_type='mask'):
|
|
930
937
|
else:
|
931
938
|
scrollable_frame, vars_dict = setup_settings_panel(settings_container, settings_type)
|
932
939
|
print('setup_settings_panel')
|
933
|
-
canvas, canvas_widget = setup_plot_section(vertical_container)
|
934
|
-
console_output, _ = setup_console(vertical_container)
|
940
|
+
canvas, canvas_widget = setup_plot_section(vertical_container, settings_type)
|
941
|
+
console_output, _ = setup_console(vertical_container) #, chatbot)
|
935
942
|
button_scrollable_frame, btn_col = setup_button_section(horizontal_container, settings_type)
|
936
|
-
|
943
|
+
|
944
|
+
if num_cores > 12:
|
945
|
+
_, usage_bars, btn_col = setup_usage_panel(horizontal_container, btn_col, uppdate_frequency)
|
946
|
+
else:
|
947
|
+
usage_bars = []
|
937
948
|
|
938
949
|
set_globals(thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, figures, figure_index, index_control, progress_bar, usage_bars)
|
939
950
|
description_text = descriptions.get(settings_type, "No description available for this module.")
|
spacr/gui_elements.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import os, threading, time, sqlite3, webbrowser, pyautogui
|
1
|
+
import os, threading, time, sqlite3, webbrowser, pyautogui, random, cv2
|
2
2
|
import tkinter as tk
|
3
3
|
from tkinter import ttk
|
4
4
|
import tkinter.font as tkFont
|
@@ -7,7 +7,7 @@ from tkinter import font
|
|
7
7
|
from queue import Queue
|
8
8
|
from tkinter import Label, Frame, Button
|
9
9
|
import numpy as np
|
10
|
-
from PIL import Image, ImageOps, ImageTk
|
10
|
+
from PIL import Image, ImageOps, ImageTk, ImageDraw, ImageFont, ImageEnhance
|
11
11
|
from concurrent.futures import ThreadPoolExecutor
|
12
12
|
from skimage.exposure import rescale_intensity
|
13
13
|
from IPython.display import display, HTML
|
@@ -16,7 +16,8 @@ from collections import deque
|
|
16
16
|
from skimage.draw import polygon, line
|
17
17
|
from skimage.transform import resize
|
18
18
|
from scipy.ndimage import binary_fill_holes, label
|
19
|
-
from tkinter import ttk, scrolledtext
|
19
|
+
from tkinter import ttk, scrolledtext
|
20
|
+
|
20
21
|
fig = None
|
21
22
|
|
22
23
|
def set_element_size():
|
@@ -2512,7 +2513,6 @@ def create_menu_bar(root):
|
|
2512
2513
|
"Annotate": lambda: initiate_root(root, settings_type='annotate'),
|
2513
2514
|
"Make Masks": lambda: initiate_root(root, settings_type='make_masks'),
|
2514
2515
|
"Classify": lambda: initiate_root(root, settings_type='classify'),
|
2515
|
-
"Sequencing": lambda: initiate_root(root, settings_type='sequencing'),
|
2516
2516
|
"Umap": lambda: initiate_root(root, settings_type='umap'),
|
2517
2517
|
"Train Cellpose": lambda: initiate_root(root, settings_type='train_cellpose'),
|
2518
2518
|
"ML Analyze": lambda: initiate_root(root, settings_type='ml_analyze'),
|
@@ -2842,4 +2842,109 @@ def modify_figure(fig):
|
|
2842
2842
|
|
2843
2843
|
# Apply button
|
2844
2844
|
apply_button = tk.Button(modify_window, text="Apply", command=apply_modifications, bg="#2E2E2E", fg="white")
|
2845
|
-
apply_button.grid(row=len(options) + len(checkboxes), column=0, columnspan=2, pady=10)
|
2845
|
+
apply_button.grid(row=len(options) + len(checkboxes), column=0, columnspan=2, pady=10)
|
2846
|
+
|
2847
|
+
def generate_dna_matrix(output_path='dna_matrix.gif', canvas_width=1500, canvas_height=1000, duration=30, fps=20, base_size=20, transition_frames=30, font_type='arial.ttf', enhance=[1.1, 1.5, 1.2, 1.5], lowercase_prob=0.3):
|
2848
|
+
"""
|
2849
|
+
Generate a DNA matrix animation and save it as GIF, MP4, or AVI using OpenCV for videos.
|
2850
|
+
"""
|
2851
|
+
|
2852
|
+
def save_output(frames, output_path, fps, output_format):
|
2853
|
+
"""Save the animation based on output format."""
|
2854
|
+
if output_format in ['.mp4', '.avi']:
|
2855
|
+
images = [np.array(img.convert('RGB')) for img in frames]
|
2856
|
+
fourcc = cv2.VideoWriter_fourcc(*('mp4v' if output_format == '.mp4' else 'XVID'))
|
2857
|
+
out = cv2.VideoWriter(output_path, fourcc, fps, (canvas_width, canvas_height))
|
2858
|
+
for img in images:
|
2859
|
+
out.write(cv2.cvtColor(img, cv2.COLOR_RGB2BGR))
|
2860
|
+
out.release()
|
2861
|
+
elif output_format == '.gif':
|
2862
|
+
frames[0].save(output_path, save_all=True, append_images=frames[1:], duration=int(1000/fps), loop=0)
|
2863
|
+
|
2864
|
+
def draw_base(draw, col_idx, base_position, base, font, alpha=255, fill_color=None):
|
2865
|
+
"""Draws a DNA base at the specified position."""
|
2866
|
+
draw.text((col_idx * base_size, base_position * base_size), base, fill=(*fill_color, alpha), font=font)
|
2867
|
+
|
2868
|
+
# Setup variables
|
2869
|
+
num_frames = duration * fps
|
2870
|
+
num_columns = canvas_width // base_size
|
2871
|
+
bases = ['A', 'T', 'C', 'G']
|
2872
|
+
active_color = (155, 55, 155)
|
2873
|
+
color = (255, 255, 255)
|
2874
|
+
base_colors = {'A': color, 'T': color, 'C': color, 'G': color}
|
2875
|
+
|
2876
|
+
_, output_format = os.path.splitext(output_path)
|
2877
|
+
|
2878
|
+
# Initialize font
|
2879
|
+
try:
|
2880
|
+
font = ImageFont.truetype(font_type, base_size)
|
2881
|
+
except IOError:
|
2882
|
+
font = ImageFont.load_default()
|
2883
|
+
|
2884
|
+
# DNA string and positions
|
2885
|
+
string_lengths = [random.randint(10, 100) for _ in range(num_columns)]
|
2886
|
+
visible_bases = [0] * num_columns
|
2887
|
+
base_positions = [random.randint(-canvas_height // base_size, 0) for _ in range(num_columns)]
|
2888
|
+
column_strings = [[''] * 100 for _ in range(num_columns)]
|
2889
|
+
random_white_sequences = [None] * num_columns
|
2890
|
+
|
2891
|
+
frames = []
|
2892
|
+
end_frame_start = int(num_frames * 0.8)
|
2893
|
+
|
2894
|
+
for frame_idx in range(num_frames):
|
2895
|
+
img = Image.new('RGBA', (canvas_width, canvas_height), color=(0, 0, 0, 255))
|
2896
|
+
draw = ImageDraw.Draw(img)
|
2897
|
+
|
2898
|
+
for col_idx in range(num_columns):
|
2899
|
+
if base_positions[col_idx] >= canvas_height // base_size and frame_idx < end_frame_start:
|
2900
|
+
string_lengths[col_idx] = random.randint(10, 100)
|
2901
|
+
base_positions[col_idx] = -string_lengths[col_idx]
|
2902
|
+
visible_bases[col_idx] = 0
|
2903
|
+
# Randomly choose whether to make each base lowercase
|
2904
|
+
column_strings[col_idx] = [
|
2905
|
+
random.choice([base.lower(), base]) if random.random() < lowercase_prob else base
|
2906
|
+
for base in [random.choice(bases) for _ in range(string_lengths[col_idx])]
|
2907
|
+
]
|
2908
|
+
if string_lengths[col_idx] > 8:
|
2909
|
+
random_start = random.randint(0, string_lengths[col_idx] - 8)
|
2910
|
+
random_white_sequences[col_idx] = range(random_start, random_start + 8)
|
2911
|
+
|
2912
|
+
last_10_percent_start = max(0, int(string_lengths[col_idx] * 0.9))
|
2913
|
+
|
2914
|
+
for row_idx in range(min(visible_bases[col_idx], string_lengths[col_idx])):
|
2915
|
+
base_position = base_positions[col_idx] + row_idx
|
2916
|
+
if 0 <= base_position * base_size < canvas_height:
|
2917
|
+
base = column_strings[col_idx][row_idx]
|
2918
|
+
if base:
|
2919
|
+
if row_idx == visible_bases[col_idx] - 1:
|
2920
|
+
draw_base(draw, col_idx, base_position, base, font, fill_color=active_color)
|
2921
|
+
elif row_idx >= last_10_percent_start:
|
2922
|
+
alpha = 255 - int(((row_idx - last_10_percent_start) / (string_lengths[col_idx] - last_10_percent_start)) * 127)
|
2923
|
+
draw_base(draw, col_idx, base_position, base, font, alpha=alpha, fill_color=base_colors[base.upper()])
|
2924
|
+
elif random_white_sequences[col_idx] and row_idx in random_white_sequences[col_idx]:
|
2925
|
+
draw_base(draw, col_idx, base_position, base, font, fill_color=active_color)
|
2926
|
+
else:
|
2927
|
+
draw_base(draw, col_idx, base_position, base, font, fill_color=base_colors[base.upper()])
|
2928
|
+
|
2929
|
+
if visible_bases[col_idx] < string_lengths[col_idx]:
|
2930
|
+
visible_bases[col_idx] += 1
|
2931
|
+
base_positions[col_idx] += 2
|
2932
|
+
|
2933
|
+
# Convert the image to numpy array to check unique pixel values
|
2934
|
+
img_array = np.array(img)
|
2935
|
+
if len(np.unique(img_array)) > 2: # Only append frames with more than two unique pixel values (avoid black frames)
|
2936
|
+
# Enhance contrast and saturation
|
2937
|
+
if enhance:
|
2938
|
+
img = ImageEnhance.Brightness(img).enhance(enhance[0]) # Slightly increase brightness
|
2939
|
+
img = ImageEnhance.Sharpness(img).enhance(enhance[1]) # Sharpen the image
|
2940
|
+
img = ImageEnhance.Contrast(img).enhance(enhance[2]) # Enhance contrast
|
2941
|
+
img = ImageEnhance.Color(img).enhance(enhance[3]) # Boost color saturation
|
2942
|
+
|
2943
|
+
frames.append(img)
|
2944
|
+
|
2945
|
+
for i in range(transition_frames):
|
2946
|
+
alpha = i / float(transition_frames)
|
2947
|
+
transition_frame = Image.blend(frames[-1], frames[0], alpha)
|
2948
|
+
frames.append(transition_frame)
|
2949
|
+
|
2950
|
+
save_output(frames, output_path, fps, output_format)
|