clabtoolkit 0.3.3__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.
- clabtoolkit/__init__.py +5 -0
- clabtoolkit/bidstools.py +2260 -0
- clabtoolkit/config/bids.json +147 -0
- clabtoolkit/config/config.json +58 -0
- clabtoolkit/config/lobes.json +112 -0
- clabtoolkit/config/stats_mapping.json +140 -0
- clabtoolkit/config/viz_views.json +268 -0
- clabtoolkit/connectivitytools.py +0 -0
- clabtoolkit/dicomtools.py +941 -0
- clabtoolkit/dwitools.py +1999 -0
- clabtoolkit/freesurfertools.py +5378 -0
- clabtoolkit/imagetools.py +2734 -0
- clabtoolkit/misctools.py +5204 -0
- clabtoolkit/morphometrytools.py +2970 -0
- clabtoolkit/networktools.py +351 -0
- clabtoolkit/parcellationtools.py +3239 -0
- clabtoolkit/pipelinetools.py +1187 -0
- clabtoolkit/plottools.py +682 -0
- clabtoolkit/qcqatools.py +1305 -0
- clabtoolkit/segmentationtools.py +356 -0
- clabtoolkit/surfacetools.py +2785 -0
- clabtoolkit/visualizationtools.py +5575 -0
- clabtoolkit-0.3.3.dist-info/METADATA +212 -0
- clabtoolkit-0.3.3.dist-info/RECORD +28 -0
- clabtoolkit-0.3.3.dist-info/WHEEL +5 -0
- clabtoolkit-0.3.3.dist-info/licenses/AUTHORS.rst +13 -0
- clabtoolkit-0.3.3.dist-info/licenses/LICENSE +16 -0
- clabtoolkit-0.3.3.dist-info/top_level.txt +1 -0
clabtoolkit/plottools.py
ADDED
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
from typing import Tuple, List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
#####################################################################################################
|
|
7
|
+
def get_screen_size() -> Tuple[int, int]:
|
|
8
|
+
"""
|
|
9
|
+
Get the current screen size in pixels.
|
|
10
|
+
|
|
11
|
+
Returns
|
|
12
|
+
-------
|
|
13
|
+
tuple of int
|
|
14
|
+
Screen width and height in pixels (width, height).
|
|
15
|
+
|
|
16
|
+
Examples
|
|
17
|
+
--------
|
|
18
|
+
>>> width, height = get_screen_size()
|
|
19
|
+
>>> print(f"Screen size: {width}x{height}")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import tkinter as tk
|
|
23
|
+
|
|
24
|
+
root = tk.Tk()
|
|
25
|
+
root.withdraw() # Hide the main window
|
|
26
|
+
width = root.winfo_screenwidth()
|
|
27
|
+
height = root.winfo_screenheight()
|
|
28
|
+
root.destroy() # Clean up the Tkinter instance
|
|
29
|
+
|
|
30
|
+
return width, height
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#####################################################################################################
|
|
34
|
+
def get_current_monitor_size() -> Tuple[int, int]:
|
|
35
|
+
"""Get the size of the monitor where the mouse cursor is located."""
|
|
36
|
+
import tkinter as tk
|
|
37
|
+
import screeninfo
|
|
38
|
+
|
|
39
|
+
# Get mouse position
|
|
40
|
+
root = tk.Tk()
|
|
41
|
+
root.withdraw()
|
|
42
|
+
mouse_x = root.winfo_pointerx()
|
|
43
|
+
mouse_y = root.winfo_pointery()
|
|
44
|
+
root.destroy()
|
|
45
|
+
|
|
46
|
+
# Find which monitor contains the mouse
|
|
47
|
+
monitors = screeninfo.get_monitors()
|
|
48
|
+
for monitor in monitors:
|
|
49
|
+
if (
|
|
50
|
+
monitor.x <= mouse_x < monitor.x + monitor.width
|
|
51
|
+
and monitor.y <= mouse_y < monitor.y + monitor.height
|
|
52
|
+
):
|
|
53
|
+
return monitor.width, monitor.height
|
|
54
|
+
|
|
55
|
+
# Fallback to primary monitor
|
|
56
|
+
primary = next((m for m in monitors if m.is_primary), monitors[0])
|
|
57
|
+
return primary.width, primary.height
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
#######################################################################################################
|
|
61
|
+
def estimate_monitor_dpi(screen_width: int, screen_height: int) -> float:
|
|
62
|
+
"""
|
|
63
|
+
Estimate monitor DPI based on screen resolution using common monitor configurations.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
screen_width : int
|
|
68
|
+
Screen width in pixels
|
|
69
|
+
|
|
70
|
+
screen_height : int
|
|
71
|
+
Screen height in pixels
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
float
|
|
76
|
+
Estimated DPI based on common monitor size/resolution combinations
|
|
77
|
+
|
|
78
|
+
Examples
|
|
79
|
+
--------
|
|
80
|
+
>>> estimate_monitor_dpi(1920, 1080)
|
|
81
|
+
96.0
|
|
82
|
+
>>>
|
|
83
|
+
>>> estimate_monitor_dpi(2560, 1440)
|
|
84
|
+
109.0
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
# Common monitor configurations: (width, height): typical_dpi
|
|
88
|
+
monitor_configs = {
|
|
89
|
+
# Full HD displays
|
|
90
|
+
(1920, 1080): {
|
|
91
|
+
"laptop_13_15": 147, # 13-15" laptop
|
|
92
|
+
"laptop_17": 130, # 17" laptop
|
|
93
|
+
"monitor_21_24": 92, # 21-24" monitor
|
|
94
|
+
"monitor_27": 82, # 27" monitor
|
|
95
|
+
},
|
|
96
|
+
# QHD displays
|
|
97
|
+
(2560, 1440): {
|
|
98
|
+
"laptop_13_15": 196, # 13-15" laptop
|
|
99
|
+
"monitor_27": 109, # 27" monitor
|
|
100
|
+
"monitor_32": 92, # 32" monitor
|
|
101
|
+
},
|
|
102
|
+
# 4K displays
|
|
103
|
+
(3840, 2160): {
|
|
104
|
+
"laptop_15_17": 294, # 15-17" laptop
|
|
105
|
+
"monitor_27": 163, # 27" monitor
|
|
106
|
+
"monitor_32": 138, # 32" monitor
|
|
107
|
+
"monitor_43": 103, # 43" monitor
|
|
108
|
+
},
|
|
109
|
+
# Other common resolutions
|
|
110
|
+
(1366, 768): {
|
|
111
|
+
"laptop_11_14": 112, # Small laptops
|
|
112
|
+
},
|
|
113
|
+
(1680, 1050): {
|
|
114
|
+
"monitor_22": 90, # 22" monitor
|
|
115
|
+
},
|
|
116
|
+
(2880, 1800): {
|
|
117
|
+
"laptop_15": 220, # MacBook Pro 15"
|
|
118
|
+
},
|
|
119
|
+
(3440, 1440): {
|
|
120
|
+
"ultrawide_34": 110, # 34" ultrawide
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Find exact match first
|
|
125
|
+
resolution = (screen_width, screen_height)
|
|
126
|
+
if resolution in monitor_configs:
|
|
127
|
+
# For known resolutions, estimate based on pixel density
|
|
128
|
+
configs = monitor_configs[resolution]
|
|
129
|
+
pixel_count = screen_width * screen_height
|
|
130
|
+
|
|
131
|
+
# Estimate based on total pixels and common usage patterns
|
|
132
|
+
if pixel_count < 1500000: # < 1.5M pixels (likely smaller screen)
|
|
133
|
+
return max(configs.values()) # Higher DPI (smaller screen)
|
|
134
|
+
elif pixel_count > 8000000: # > 8M pixels (4K+, likely larger screen)
|
|
135
|
+
return min(configs.values()) # Lower DPI (larger screen)
|
|
136
|
+
else:
|
|
137
|
+
# Medium resolution, use median DPI
|
|
138
|
+
return sorted(configs.values())[len(configs.values()) // 2]
|
|
139
|
+
|
|
140
|
+
# Fallback: calculate approximate DPI based on pixel density
|
|
141
|
+
# Assume reasonable screen diagonal based on resolution
|
|
142
|
+
total_pixels = screen_width * screen_height
|
|
143
|
+
|
|
144
|
+
if total_pixels <= 1000000: # ≤ 1M pixels
|
|
145
|
+
estimated_diagonal = 13 # Small laptop/tablet
|
|
146
|
+
|
|
147
|
+
elif total_pixels <= 2000000: # ≤ 2M pixels
|
|
148
|
+
estimated_diagonal = 21 # Standard monitor
|
|
149
|
+
|
|
150
|
+
elif total_pixels <= 4000000: # ≤ 4M pixels
|
|
151
|
+
estimated_diagonal = 24 # Larger monitor
|
|
152
|
+
|
|
153
|
+
elif total_pixels <= 8000000: # ≤ 8M pixels
|
|
154
|
+
estimated_diagonal = 27 # QHD monitor
|
|
155
|
+
|
|
156
|
+
else: # > 8M pixels
|
|
157
|
+
estimated_diagonal = 32 # 4K monitor
|
|
158
|
+
|
|
159
|
+
# Calculate DPI: sqrt(width² + height²) / diagonal_inches
|
|
160
|
+
diagonal_pixels = (screen_width**2 + screen_height**2) ** 0.5
|
|
161
|
+
estimated_dpi = diagonal_pixels / estimated_diagonal
|
|
162
|
+
|
|
163
|
+
return round(estimated_dpi, 1)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
###############################################################################################
|
|
167
|
+
def calculate_optimal_subplots_grid(num_views: int) -> List[int]:
|
|
168
|
+
"""
|
|
169
|
+
Calculate optimal grid dimensions for a given number of views.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
num_views : int
|
|
174
|
+
Number of views to arrange.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
List[int]
|
|
179
|
+
[rows, columns] for optimal grid layout.
|
|
180
|
+
|
|
181
|
+
Examples
|
|
182
|
+
--------
|
|
183
|
+
>>> calculate_optimal_subplots_grid(4)
|
|
184
|
+
[2, 2]
|
|
185
|
+
>>>
|
|
186
|
+
>>> calculate_optimal_subplots_grid(6)
|
|
187
|
+
[2, 3]
|
|
188
|
+
>>>
|
|
189
|
+
>>> calculate_optimal_subplots_grid(1)
|
|
190
|
+
[1, 1]
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
# Calculate optimal grid dimensions based on number of views
|
|
194
|
+
if num_views == 1:
|
|
195
|
+
grid_size = [1, 1]
|
|
196
|
+
position = [(0, 0)]
|
|
197
|
+
return grid_size, position
|
|
198
|
+
|
|
199
|
+
elif num_views == 2:
|
|
200
|
+
grid_size = [1, 2]
|
|
201
|
+
position = [(0, 0), (0, 1)]
|
|
202
|
+
return grid_size, position
|
|
203
|
+
|
|
204
|
+
elif num_views == 3:
|
|
205
|
+
grid_size = [1, 3]
|
|
206
|
+
position = [(0, 0), (0, 1), (0, 2)]
|
|
207
|
+
return grid_size, position
|
|
208
|
+
|
|
209
|
+
elif num_views == 4:
|
|
210
|
+
# For 4 views, arrange in a 2x2 grid
|
|
211
|
+
grid_size = [2, 2]
|
|
212
|
+
position = [(0, 0), (0, 1), (1, 0), (1, 1)]
|
|
213
|
+
return grid_size, position
|
|
214
|
+
|
|
215
|
+
elif num_views <= 6:
|
|
216
|
+
# For 5 or 6 views, arrange in a 2x3 grid
|
|
217
|
+
grid_size = [2, 3]
|
|
218
|
+
position = [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
|
|
219
|
+
return grid_size, position
|
|
220
|
+
|
|
221
|
+
elif num_views <= 8:
|
|
222
|
+
# For 7 or 8 views, arrange in a 2x4 grid
|
|
223
|
+
grid_size = [2, 4]
|
|
224
|
+
position = [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3)]
|
|
225
|
+
return grid_size, position
|
|
226
|
+
|
|
227
|
+
else:
|
|
228
|
+
# For more than 8 views, try to keep a proportion with the screen shape
|
|
229
|
+
screen_size = get_screen_size()
|
|
230
|
+
|
|
231
|
+
# Calculate the number of columns and rows based on the number of views
|
|
232
|
+
rows, cols, aspect = calculate_subplot_layout(
|
|
233
|
+
num_views, screen_size[0], screen_size[1]
|
|
234
|
+
)
|
|
235
|
+
grid_size = [rows, cols]
|
|
236
|
+
position = []
|
|
237
|
+
for i in range(rows):
|
|
238
|
+
for j in range(cols):
|
|
239
|
+
if len(position) < num_views:
|
|
240
|
+
position.append((i, j))
|
|
241
|
+
|
|
242
|
+
return grid_size, position
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
#####################################################################################################
|
|
246
|
+
def calculate_subplot_layout(
|
|
247
|
+
n_plots, screen_width=None, screen_height=None, target_aspect_ratio=None
|
|
248
|
+
):
|
|
249
|
+
"""
|
|
250
|
+
Calculate optimal rows and columns for subplots based on screen proportions
|
|
251
|
+
|
|
252
|
+
Parameters:
|
|
253
|
+
-----------
|
|
254
|
+
n_plots : int
|
|
255
|
+
Number of subplots needed
|
|
256
|
+
|
|
257
|
+
screen_width : int, optional
|
|
258
|
+
Screen width in pixels (auto-detected if not provided)
|
|
259
|
+
|
|
260
|
+
screen_height : int, optional
|
|
261
|
+
Screen height in pixels (auto-detected if not provided)
|
|
262
|
+
|
|
263
|
+
target_aspect_ratio : float, optional
|
|
264
|
+
Target aspect ratio (width/height). If provided, overrides screen detection
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
--------
|
|
268
|
+
tuple: (rows, cols, aspect_ratio_used)
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
if target_aspect_ratio is None:
|
|
272
|
+
if screen_width is None or screen_height is None:
|
|
273
|
+
screen_width, screen_height = get_screen_size()
|
|
274
|
+
aspect_ratio = screen_width / screen_height
|
|
275
|
+
else:
|
|
276
|
+
aspect_ratio = target_aspect_ratio
|
|
277
|
+
|
|
278
|
+
# Start with square root as baseline
|
|
279
|
+
base_dim = np.ceil(np.sqrt(n_plots))
|
|
280
|
+
|
|
281
|
+
best_rows, best_cols = base_dim, base_dim
|
|
282
|
+
best_score = float("inf")
|
|
283
|
+
|
|
284
|
+
# Try different combinations around the baseline
|
|
285
|
+
for rows in range(1, n_plots + 1):
|
|
286
|
+
cols = np.ceil(n_plots / rows)
|
|
287
|
+
|
|
288
|
+
# Skip if this creates too many empty subplots
|
|
289
|
+
if rows * cols - n_plots > min(rows, cols):
|
|
290
|
+
continue
|
|
291
|
+
|
|
292
|
+
# Calculate how close this layout's aspect ratio is to screen ratio
|
|
293
|
+
layout_aspect_ratio = cols / rows
|
|
294
|
+
aspect_diff = abs(layout_aspect_ratio - aspect_ratio)
|
|
295
|
+
|
|
296
|
+
# Prefer layouts that minimize aspect ratio difference
|
|
297
|
+
# and minimize total subplots (less wasted space)
|
|
298
|
+
total_subplots = rows * cols
|
|
299
|
+
score = aspect_diff + 0.1 * (total_subplots - n_plots)
|
|
300
|
+
|
|
301
|
+
if score < best_score:
|
|
302
|
+
best_score = score
|
|
303
|
+
best_rows, best_cols = rows, cols
|
|
304
|
+
|
|
305
|
+
return int(best_rows), int(best_cols), aspect_ratio
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
#####################################################################################################
|
|
309
|
+
def create_proportional_subplots(n_plots, figsize_base=4, **layout_kwargs):
|
|
310
|
+
"""
|
|
311
|
+
Create a figure with subplots arranged according to screen proportions
|
|
312
|
+
|
|
313
|
+
Parameters:
|
|
314
|
+
-----------
|
|
315
|
+
n_plots : int
|
|
316
|
+
Number of subplots
|
|
317
|
+
|
|
318
|
+
figsize_base : float
|
|
319
|
+
Base size for figure scaling
|
|
320
|
+
|
|
321
|
+
**layout_kwargs :
|
|
322
|
+
Additional arguments for calculate_subplot_layout()
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
--------
|
|
326
|
+
tuple: (fig, axes, layout_info)
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
rows, cols, aspect_ratio = calculate_subplot_layout(n_plots, **layout_kwargs)
|
|
330
|
+
|
|
331
|
+
# Calculate figure size based on layout and aspect ratio
|
|
332
|
+
fig_width = figsize_base * cols
|
|
333
|
+
fig_height = fig_width / aspect_ratio
|
|
334
|
+
|
|
335
|
+
# Create the figure and subplots
|
|
336
|
+
fig, axes = plt.subplots(rows, cols, figsize=(fig_width, fig_height))
|
|
337
|
+
|
|
338
|
+
# Handle the case where there's only one subplot
|
|
339
|
+
if n_plots == 1:
|
|
340
|
+
axes = [axes]
|
|
341
|
+
elif rows == 1 or cols == 1:
|
|
342
|
+
axes = axes.flatten()
|
|
343
|
+
else:
|
|
344
|
+
axes = axes.flatten()
|
|
345
|
+
|
|
346
|
+
# Hide extra subplots if any
|
|
347
|
+
for i in range(n_plots, len(axes)):
|
|
348
|
+
axes[i].set_visible(False)
|
|
349
|
+
|
|
350
|
+
layout_info = {
|
|
351
|
+
"rows": rows,
|
|
352
|
+
"cols": cols,
|
|
353
|
+
"aspect_ratio": aspect_ratio,
|
|
354
|
+
"total_subplots": rows * cols,
|
|
355
|
+
"used_subplots": n_plots,
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
plt.tight_layout()
|
|
359
|
+
|
|
360
|
+
return fig, axes, layout_info
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
######################################################################################################
|
|
364
|
+
def calculate_font_sizes(
|
|
365
|
+
plot_width,
|
|
366
|
+
plot_height,
|
|
367
|
+
screen_width=None,
|
|
368
|
+
screen_height=None,
|
|
369
|
+
colorbar_orientation="vertical",
|
|
370
|
+
colorbar_width=None,
|
|
371
|
+
colorbar_height=None,
|
|
372
|
+
auto_detect_monitor=True,
|
|
373
|
+
):
|
|
374
|
+
"""
|
|
375
|
+
Calculate appropriate font sizes for matplotlib plots based on dimensions, monitor DPI, and colorbar configuration.
|
|
376
|
+
|
|
377
|
+
This function automatically scales font sizes for plot elements (title, axis labels, tick labels,
|
|
378
|
+
colorbar title, and colorbar ticks) based on the plot dimensions, monitor characteristics, and
|
|
379
|
+
colorbar orientation. The scaling ensures optimal readability across different plot sizes and
|
|
380
|
+
display configurations by calculating monitor DPI from screen resolution.
|
|
381
|
+
|
|
382
|
+
The algorithm uses a reference plot size of 6×4 inches as a baseline and scales font sizes
|
|
383
|
+
proportionally based on plot area and monitor DPI. Colorbar fonts are scaled independently
|
|
384
|
+
based on colorbar dimensions and orientation constraints.
|
|
385
|
+
|
|
386
|
+
Parameters
|
|
387
|
+
----------
|
|
388
|
+
plot_width : float
|
|
389
|
+
Width of the plot in inches. Must be positive.
|
|
390
|
+
Typical values: 3-20 inches for scientific plots.
|
|
391
|
+
|
|
392
|
+
plot_height : float
|
|
393
|
+
Height of the plot in inches. Must be positive.
|
|
394
|
+
Typical values: 2-15 inches for scientific plots.
|
|
395
|
+
|
|
396
|
+
screen_width : int, optional
|
|
397
|
+
Screen width in pixels, by default None.
|
|
398
|
+
If None and auto_detect_monitor=True, automatically detected.
|
|
399
|
+
Used together with screen_height to calculate monitor DPI.
|
|
400
|
+
|
|
401
|
+
screen_height : int, optional
|
|
402
|
+
Screen height in pixels, by default None.
|
|
403
|
+
If None and auto_detect_monitor=True, automatically detected.
|
|
404
|
+
Used together with screen_width to calculate monitor DPI.
|
|
405
|
+
|
|
406
|
+
colorbar_orientation : {'vertical', 'horizontal'}, optional
|
|
407
|
+
Orientation of the colorbar, by default 'vertical'.
|
|
408
|
+
- 'vertical': Colorbar positioned to the right/left of the plot
|
|
409
|
+
- 'horizontal': Colorbar positioned above/below the plot
|
|
410
|
+
|
|
411
|
+
colorbar_width : float, optional
|
|
412
|
+
Width of the colorbar in inches, by default None.
|
|
413
|
+
If None, automatically calculated as 5% of plot width (vertical) or
|
|
414
|
+
80% of plot width (horizontal), with minimum constraints.
|
|
415
|
+
|
|
416
|
+
colorbar_height : float, optional
|
|
417
|
+
Height of the colorbar in inches, by default None.
|
|
418
|
+
If None, automatically calculated as 80% of plot height (vertical) or
|
|
419
|
+
5% of plot height (horizontal), with minimum constraints.
|
|
420
|
+
|
|
421
|
+
auto_detect_monitor : bool, optional
|
|
422
|
+
Whether to automatically detect current monitor size, by default True.
|
|
423
|
+
If False, uses fallback values when screen dimensions are not provided.
|
|
424
|
+
|
|
425
|
+
Returns
|
|
426
|
+
-------
|
|
427
|
+
dict
|
|
428
|
+
Dictionary containing font sizes for different plot elements:
|
|
429
|
+
|
|
430
|
+
- 'title' : float
|
|
431
|
+
Font size for the main plot title (8-28 pt range)
|
|
432
|
+
|
|
433
|
+
- 'axis_labels' : float
|
|
434
|
+
Font size for axis labels (6-20 pt range)
|
|
435
|
+
|
|
436
|
+
- 'tick_labels' : float
|
|
437
|
+
Font size for axis tick labels (6-20 pt range)
|
|
438
|
+
|
|
439
|
+
- 'colorbar_title' : float
|
|
440
|
+
Font size for colorbar title (6-16 pt range, orientation-dependent)
|
|
441
|
+
|
|
442
|
+
- 'colorbar_ticks' : float
|
|
443
|
+
Font size for colorbar tick labels (5-12 pt range, orientation-dependent)
|
|
444
|
+
|
|
445
|
+
- '_monitor_info' : dict
|
|
446
|
+
Debug information about monitor detection and DPI calculation
|
|
447
|
+
|
|
448
|
+
All font sizes are rounded to 1 decimal place.
|
|
449
|
+
|
|
450
|
+
Raises
|
|
451
|
+
------
|
|
452
|
+
ValueError
|
|
453
|
+
If plot_width or plot_height are not positive numbers.
|
|
454
|
+
|
|
455
|
+
ValueError
|
|
456
|
+
If colorbar_orientation is not 'vertical' or 'horizontal'.
|
|
457
|
+
|
|
458
|
+
TypeError
|
|
459
|
+
If numeric parameters are not of appropriate numeric types.
|
|
460
|
+
|
|
461
|
+
ImportError
|
|
462
|
+
If auto_detect_monitor=True but required packages (tkinter, screeninfo) are not available.
|
|
463
|
+
|
|
464
|
+
Examples
|
|
465
|
+
--------
|
|
466
|
+
Basic usage with automatic monitor detection:
|
|
467
|
+
|
|
468
|
+
>>> fonts = calculate_font_sizes(6, 4)
|
|
469
|
+
>>> print(f"Title: {fonts['title']}, Colorbar title: {fonts['colorbar_title']}")
|
|
470
|
+
Title: 14.2, Colorbar title: 11.4
|
|
471
|
+
|
|
472
|
+
Specify monitor dimensions manually:
|
|
473
|
+
|
|
474
|
+
>>> fonts = calculate_font_sizes(6, 4, screen_width=2560, screen_height=1440)
|
|
475
|
+
>>> fonts['_monitor_info']['estimated_dpi']
|
|
476
|
+
109.0
|
|
477
|
+
|
|
478
|
+
Small subplot with horizontal colorbar on high-DPI display:
|
|
479
|
+
|
|
480
|
+
>>> fonts = calculate_font_sizes(3, 2,
|
|
481
|
+
... screen_width=3840, screen_height=2160,
|
|
482
|
+
... colorbar_orientation='horizontal')
|
|
483
|
+
>>> fonts['colorbar_title'] # Scaled up for high DPI
|
|
484
|
+
10.2
|
|
485
|
+
|
|
486
|
+
Disable auto-detection for headless environments:
|
|
487
|
+
|
|
488
|
+
>>> fonts = calculate_font_sizes(12, 8,
|
|
489
|
+
... screen_width=1920, screen_height=1080,
|
|
490
|
+
... auto_detect_monitor=False)
|
|
491
|
+
>>> fonts['title']
|
|
492
|
+
19.1
|
|
493
|
+
|
|
494
|
+
Custom colorbar dimensions:
|
|
495
|
+
|
|
496
|
+
>>> fonts = calculate_font_sizes(8, 6,
|
|
497
|
+
... colorbar_orientation='horizontal',
|
|
498
|
+
... colorbar_width=6.0,
|
|
499
|
+
... colorbar_height=0.5)
|
|
500
|
+
|
|
501
|
+
Apply to matplotlib plot:
|
|
502
|
+
|
|
503
|
+
>>> import matplotlib.pyplot as plt
|
|
504
|
+
>>> fig, ax = plt.subplots(figsize=(6, 4))
|
|
505
|
+
>>> fonts = calculate_font_sizes(6, 4, colorbar_orientation='horizontal')
|
|
506
|
+
>>> ax.set_title('Brain Activation', fontsize=fonts['title'])
|
|
507
|
+
>>> ax.tick_params(labelsize=fonts['tick_labels'])
|
|
508
|
+
>>> # For colorbar:
|
|
509
|
+
>>> # cbar.set_label('Values', fontsize=fonts['colorbar_title'])
|
|
510
|
+
>>> # cbar.ax.tick_params(labelsize=fonts['colorbar_ticks'])
|
|
511
|
+
|
|
512
|
+
Notes
|
|
513
|
+
-----
|
|
514
|
+
DPI Calculation and Scaling:
|
|
515
|
+
1. Automatically detects current monitor resolution using get_current_monitor_size()
|
|
516
|
+
2. Estimates monitor DPI based on resolution and common monitor configurations
|
|
517
|
+
3. Applies DPI-based scaling factor: scale = estimated_dpi / 96.0 (Windows standard)
|
|
518
|
+
4. Combines with plot area scaling for final font sizes
|
|
519
|
+
|
|
520
|
+
Font scaling algorithm:
|
|
521
|
+
1. Calculate plot area scaling factor relative to 6×4 inch reference
|
|
522
|
+
2. Calculate monitor DPI scaling factor relative to 96 DPI baseline
|
|
523
|
+
3. Scale colorbar fonts based on constraining dimension:
|
|
524
|
+
- Vertical colorbars: limited by width → scale by width/0.5"
|
|
525
|
+
- Horizontal colorbars: limited by height → scale by height/0.4"
|
|
526
|
+
4. Apply orientation-specific bounds to ensure readability
|
|
527
|
+
|
|
528
|
+
The function prioritizes readability over exact proportional scaling, applying
|
|
529
|
+
reasonable minimum and maximum font sizes for each element type.
|
|
530
|
+
|
|
531
|
+
Monitor configurations used for DPI estimation:
|
|
532
|
+
- 1920×1080: 82-147 DPI (depending on screen size)
|
|
533
|
+
- 2560×1440: 92-196 DPI
|
|
534
|
+
- 3840×2160: 103-294 DPI
|
|
535
|
+
- Other resolutions estimated using pixel density
|
|
536
|
+
|
|
537
|
+
For PyVista compatibility, convert results to integers:
|
|
538
|
+
>>> pv_fonts = {k: int(round(v)) for k, v in fonts.items() if not k.startswith('_')}
|
|
539
|
+
"""
|
|
540
|
+
|
|
541
|
+
# Input validation
|
|
542
|
+
if not isinstance(plot_width, (int, float)) or plot_width <= 0:
|
|
543
|
+
raise ValueError("plot_width must be a positive number")
|
|
544
|
+
if not isinstance(plot_height, (int, float)) or plot_height <= 0:
|
|
545
|
+
raise ValueError("plot_height must be a positive number")
|
|
546
|
+
if colorbar_orientation not in ["vertical", "horizontal"]:
|
|
547
|
+
raise ValueError("colorbar_orientation must be 'vertical' or 'horizontal'")
|
|
548
|
+
|
|
549
|
+
# Get monitor dimensions
|
|
550
|
+
if screen_width is None or screen_height is None:
|
|
551
|
+
if auto_detect_monitor:
|
|
552
|
+
try:
|
|
553
|
+
detected_width, detected_height = get_current_monitor_size()
|
|
554
|
+
screen_width = screen_width or detected_width
|
|
555
|
+
screen_height = screen_height or detected_height
|
|
556
|
+
except ImportError as e:
|
|
557
|
+
raise ImportError(
|
|
558
|
+
"Auto monitor detection requires 'tkinter' and 'screeninfo' packages. "
|
|
559
|
+
"Install with: pip install screeninfo\n"
|
|
560
|
+
"Or disable auto-detection: auto_detect_monitor=False"
|
|
561
|
+
) from e
|
|
562
|
+
else:
|
|
563
|
+
# Fallback to common resolution
|
|
564
|
+
screen_width = screen_width or 1920
|
|
565
|
+
screen_height = screen_height or 1080
|
|
566
|
+
|
|
567
|
+
# Calculate monitor DPI
|
|
568
|
+
estimated_dpi = estimate_monitor_dpi(screen_width, screen_height)
|
|
569
|
+
|
|
570
|
+
# Calculate scaling factors
|
|
571
|
+
plot_area = plot_width * plot_height
|
|
572
|
+
base_area = 24.0 # 6 * 4 inches reference
|
|
573
|
+
area_scale_factor = (plot_area / base_area) ** 0.5
|
|
574
|
+
|
|
575
|
+
# DPI scaling relative to 96 DPI baseline (Windows standard)
|
|
576
|
+
dpi_scale_factor = estimated_dpi / 96.0
|
|
577
|
+
# Cap DPI scaling to reasonable bounds
|
|
578
|
+
dpi_scale_factor = max(0.8, min(2.0, dpi_scale_factor))
|
|
579
|
+
|
|
580
|
+
# Combined plot scaling
|
|
581
|
+
plot_scale = area_scale_factor * dpi_scale_factor
|
|
582
|
+
|
|
583
|
+
# Colorbar-specific scaling
|
|
584
|
+
if colorbar_orientation == "vertical":
|
|
585
|
+
if colorbar_width is None:
|
|
586
|
+
colorbar_width = max(0.3, plot_width * 0.05)
|
|
587
|
+
if colorbar_height is None:
|
|
588
|
+
colorbar_height = plot_height * 0.8
|
|
589
|
+
colorbar_scale = (
|
|
590
|
+
colorbar_width / 0.5
|
|
591
|
+
) * dpi_scale_factor # Reference: 0.5" wide
|
|
592
|
+
else: # horizontal
|
|
593
|
+
if colorbar_width is None:
|
|
594
|
+
colorbar_width = plot_width * 0.8
|
|
595
|
+
if colorbar_height is None:
|
|
596
|
+
colorbar_height = max(0.3, plot_height * 0.05)
|
|
597
|
+
colorbar_scale = (
|
|
598
|
+
colorbar_height / 0.4
|
|
599
|
+
) * dpi_scale_factor # Reference: 0.4" tall
|
|
600
|
+
|
|
601
|
+
# Base font sizes (optimized for 96 DPI)
|
|
602
|
+
base_sizes = {
|
|
603
|
+
"title": 14,
|
|
604
|
+
"axis_labels": 12,
|
|
605
|
+
"tick_labels": 10,
|
|
606
|
+
"colorbar_title": 11,
|
|
607
|
+
"colorbar_ticks": 9,
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
font_sizes = {}
|
|
611
|
+
for element, base_size in base_sizes.items():
|
|
612
|
+
if element.startswith("colorbar"):
|
|
613
|
+
scaled_size = base_size * colorbar_scale
|
|
614
|
+
else:
|
|
615
|
+
scaled_size = base_size * plot_scale
|
|
616
|
+
|
|
617
|
+
# Apply reasonable bounds
|
|
618
|
+
if element == "title":
|
|
619
|
+
scaled_size = max(8, min(28, scaled_size))
|
|
620
|
+
elif element == "colorbar_title":
|
|
621
|
+
if colorbar_orientation == "horizontal":
|
|
622
|
+
scaled_size = max(7, min(16, scaled_size))
|
|
623
|
+
else:
|
|
624
|
+
scaled_size = max(6, min(14, scaled_size))
|
|
625
|
+
elif element == "colorbar_ticks":
|
|
626
|
+
if colorbar_orientation == "horizontal":
|
|
627
|
+
scaled_size = max(6, min(12, scaled_size))
|
|
628
|
+
else:
|
|
629
|
+
scaled_size = max(5, min(10, scaled_size))
|
|
630
|
+
else:
|
|
631
|
+
scaled_size = max(6, min(20, scaled_size))
|
|
632
|
+
|
|
633
|
+
font_sizes[element] = round(scaled_size, 1)
|
|
634
|
+
|
|
635
|
+
# Add monitor info for debugging
|
|
636
|
+
font_sizes["_monitor_info"] = {
|
|
637
|
+
"screen_width": screen_width,
|
|
638
|
+
"screen_height": screen_height,
|
|
639
|
+
"estimated_dpi": estimated_dpi,
|
|
640
|
+
"dpi_scale_factor": round(dpi_scale_factor, 2),
|
|
641
|
+
"area_scale_factor": round(area_scale_factor, 2),
|
|
642
|
+
"plot_scale": round(plot_scale, 2),
|
|
643
|
+
"colorbar_scale": round(colorbar_scale, 2),
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return font_sizes
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
######################################################################################################
|
|
650
|
+
# Additional utility functions for common use cases
|
|
651
|
+
def get_pyvista_fonts(plot_width, plot_height, **kwargs):
|
|
652
|
+
"""
|
|
653
|
+
Get integer font sizes specifically for PyVista compatibility.
|
|
654
|
+
|
|
655
|
+
Parameters
|
|
656
|
+
----------
|
|
657
|
+
plot_width : float
|
|
658
|
+
Width of the plot in inches.
|
|
659
|
+
|
|
660
|
+
plot_height : float
|
|
661
|
+
Height of the plot in inches.
|
|
662
|
+
|
|
663
|
+
**kwargs
|
|
664
|
+
Additional keyword arguments passed to calculate_font_sizes().
|
|
665
|
+
|
|
666
|
+
Returns
|
|
667
|
+
-------
|
|
668
|
+
dict
|
|
669
|
+
Dictionary with integer font sizes suitable for PyVista (excludes debug info).
|
|
670
|
+
|
|
671
|
+
Examples
|
|
672
|
+
--------
|
|
673
|
+
>>> fonts = get_pyvista_fonts(8, 6, colorbar_orientation='vertical')
|
|
674
|
+
>>> plotter.add_title('3D Brain', font_size=fonts['title'])
|
|
675
|
+
"""
|
|
676
|
+
fonts = calculate_font_sizes(plot_width, plot_height, **kwargs)
|
|
677
|
+
# Exclude debug info for clean PyVista usage
|
|
678
|
+
return {
|
|
679
|
+
key: int(round(value))
|
|
680
|
+
for key, value in fonts.items()
|
|
681
|
+
if not key.startswith("_")
|
|
682
|
+
}
|