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.
@@ -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
+ }