ml-dash 0.5.5__tar.gz → 0.5.7__tar.gz
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.
- {ml_dash-0.5.5 → ml_dash-0.5.7}/PKG-INFO +4 -1
- {ml_dash-0.5.5 → ml_dash-0.5.7}/pyproject.toml +4 -1
- {ml_dash-0.5.5 → ml_dash-0.5.7}/src/ml_dash/files.py +198 -5
- {ml_dash-0.5.5 → ml_dash-0.5.7}/LICENSE +0 -0
- {ml_dash-0.5.5 → ml_dash-0.5.7}/README.md +0 -0
- {ml_dash-0.5.5 → ml_dash-0.5.7}/src/ml_dash/__init__.py +0 -0
- {ml_dash-0.5.5 → ml_dash-0.5.7}/src/ml_dash/auto_start.py +0 -0
- {ml_dash-0.5.5 → ml_dash-0.5.7}/src/ml_dash/client.py +0 -0
- {ml_dash-0.5.5 → ml_dash-0.5.7}/src/ml_dash/experiment.py +0 -0
- {ml_dash-0.5.5 → ml_dash-0.5.7}/src/ml_dash/log.py +0 -0
- {ml_dash-0.5.5 → ml_dash-0.5.7}/src/ml_dash/metric.py +0 -0
- {ml_dash-0.5.5 → ml_dash-0.5.7}/src/ml_dash/params.py +0 -0
- {ml_dash-0.5.5 → ml_dash-0.5.7}/src/ml_dash/py.typed +0 -0
- {ml_dash-0.5.5 → ml_dash-0.5.7}/src/ml_dash/storage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: ml-dash
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.7
|
|
4
4
|
Summary: ML experiment tracking and data storage
|
|
5
5
|
Keywords: machine-learning,experiment-tracking,mlops,data-storage
|
|
6
6
|
Author: Ge Yang, Tom Tao
|
|
@@ -38,6 +38,9 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
38
38
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
39
39
|
Requires-Dist: httpx>=0.27.0
|
|
40
40
|
Requires-Dist: pyjwt>=2.8.0
|
|
41
|
+
Requires-Dist: imageio>=2.31.0
|
|
42
|
+
Requires-Dist: imageio-ffmpeg>=0.4.9
|
|
43
|
+
Requires-Dist: scikit-image>=0.21.0
|
|
41
44
|
Requires-Dist: pytest>=8.0.0 ; extra == 'dev'
|
|
42
45
|
Requires-Dist: pytest-asyncio>=0.23.0 ; extra == 'dev'
|
|
43
46
|
Requires-Dist: sphinx>=7.2.0 ; extra == 'dev'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ml-dash"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.7"
|
|
4
4
|
description = "ML experiment tracking and data storage"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.9"
|
|
@@ -26,6 +26,9 @@ classifiers = [
|
|
|
26
26
|
dependencies = [
|
|
27
27
|
"httpx>=0.27.0",
|
|
28
28
|
"pyjwt>=2.8.0",
|
|
29
|
+
"imageio>=2.31.0",
|
|
30
|
+
"imageio-ffmpeg>=0.4.9",
|
|
31
|
+
"scikit-image>=0.21.0",
|
|
29
32
|
]
|
|
30
33
|
|
|
31
34
|
[project.optional-dependencies]
|
|
@@ -6,7 +6,7 @@ Provides fluent API for file upload, download, list, and delete operations.
|
|
|
6
6
|
|
|
7
7
|
import hashlib
|
|
8
8
|
import mimetypes
|
|
9
|
-
from typing import Dict, Any, List, Optional, TYPE_CHECKING
|
|
9
|
+
from typing import Dict, Any, List, Optional, Union, TYPE_CHECKING
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
@@ -69,7 +69,7 @@ class FileBuilder:
|
|
|
69
69
|
Raises:
|
|
70
70
|
RuntimeError: If experiment is not open or write-protected
|
|
71
71
|
ValueError: If file_path not provided or file doesn't exist
|
|
72
|
-
ValueError: If file size exceeds
|
|
72
|
+
ValueError: If file size exceeds 100GB limit
|
|
73
73
|
|
|
74
74
|
Examples:
|
|
75
75
|
result = experiment.files(file_path="./model.pt", prefix="/models").save()
|
|
@@ -91,11 +91,11 @@ class FileBuilder:
|
|
|
91
91
|
if not file_path.is_file():
|
|
92
92
|
raise ValueError(f"Path is not a file: {self._file_path}")
|
|
93
93
|
|
|
94
|
-
# Check file size (max
|
|
94
|
+
# Check file size (max 100GB)
|
|
95
95
|
file_size = file_path.stat().st_size
|
|
96
|
-
MAX_FILE_SIZE =
|
|
96
|
+
MAX_FILE_SIZE = 100 * 1024 * 1024 * 1024 # 100GB in bytes
|
|
97
97
|
if file_size > MAX_FILE_SIZE:
|
|
98
|
-
raise ValueError(f"File size ({file_size} bytes) exceeds
|
|
98
|
+
raise ValueError(f"File size ({file_size} bytes) exceeds 100GB limit")
|
|
99
99
|
|
|
100
100
|
# Compute checksum
|
|
101
101
|
checksum = compute_sha256(str(file_path))
|
|
@@ -414,6 +414,199 @@ class FileBuilder:
|
|
|
414
414
|
except Exception:
|
|
415
415
|
pass
|
|
416
416
|
|
|
417
|
+
def save_fig(self, fig: Optional[Any] = None, file_name: str = "figure.png", **kwargs) -> Dict[str, Any]:
|
|
418
|
+
"""
|
|
419
|
+
Save matplotlib figure to a file.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
fig: Matplotlib figure object. If None, uses plt.gcf() (current figure)
|
|
423
|
+
file_name: Name of file to create (extension determines format: .png, .pdf, .svg, .jpg)
|
|
424
|
+
**kwargs: Additional arguments passed to fig.savefig():
|
|
425
|
+
- dpi: Resolution (int or 'figure')
|
|
426
|
+
- transparent: Make background transparent (bool)
|
|
427
|
+
- bbox_inches: 'tight' to auto-crop (str or Bbox)
|
|
428
|
+
- quality: JPEG quality 0-100 (int)
|
|
429
|
+
- format: Override format detection (str)
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
File metadata dict with id, path, filename, checksum, etc.
|
|
433
|
+
|
|
434
|
+
Raises:
|
|
435
|
+
RuntimeError: If experiment not open or write-protected
|
|
436
|
+
ImportError: If matplotlib not installed
|
|
437
|
+
|
|
438
|
+
Examples:
|
|
439
|
+
import matplotlib.pyplot as plt
|
|
440
|
+
|
|
441
|
+
# Use current figure
|
|
442
|
+
plt.plot([1, 2, 3], [1, 4, 9])
|
|
443
|
+
result = experiment.files(prefix="/plots").save_fig(file_name="plot.png")
|
|
444
|
+
|
|
445
|
+
# Specify figure explicitly
|
|
446
|
+
fig, ax = plt.subplots()
|
|
447
|
+
ax.plot([1, 2, 3])
|
|
448
|
+
result = experiment.files(prefix="/plots").save_fig(fig=fig, file_name="plot.pdf", dpi=150)
|
|
449
|
+
"""
|
|
450
|
+
import tempfile
|
|
451
|
+
import os
|
|
452
|
+
|
|
453
|
+
try:
|
|
454
|
+
import matplotlib.pyplot as plt
|
|
455
|
+
except ImportError:
|
|
456
|
+
raise ImportError("Matplotlib is not installed. Install it with: pip install matplotlib")
|
|
457
|
+
|
|
458
|
+
if not self._experiment._is_open:
|
|
459
|
+
raise RuntimeError("Experiment not open. Use experiment.run.start() or context manager.")
|
|
460
|
+
|
|
461
|
+
if self._experiment._write_protected:
|
|
462
|
+
raise RuntimeError("Experiment is write-protected and cannot be modified.")
|
|
463
|
+
|
|
464
|
+
# Get figure
|
|
465
|
+
if fig is None:
|
|
466
|
+
fig = plt.gcf()
|
|
467
|
+
|
|
468
|
+
# Create temporary file with desired filename
|
|
469
|
+
temp_dir = tempfile.mkdtemp()
|
|
470
|
+
temp_path = os.path.join(temp_dir, file_name)
|
|
471
|
+
|
|
472
|
+
try:
|
|
473
|
+
# Save figure to temp file
|
|
474
|
+
fig.savefig(temp_path, **kwargs)
|
|
475
|
+
|
|
476
|
+
# Close figure to prevent memory leaks
|
|
477
|
+
plt.close(fig)
|
|
478
|
+
|
|
479
|
+
# Save using existing save() method
|
|
480
|
+
original_file_path = self._file_path
|
|
481
|
+
self._file_path = temp_path
|
|
482
|
+
|
|
483
|
+
# Upload and get result
|
|
484
|
+
result = self.save()
|
|
485
|
+
|
|
486
|
+
# Restore original file_path
|
|
487
|
+
self._file_path = original_file_path
|
|
488
|
+
|
|
489
|
+
return result
|
|
490
|
+
finally:
|
|
491
|
+
# Clean up temp file and directory
|
|
492
|
+
try:
|
|
493
|
+
os.unlink(temp_path)
|
|
494
|
+
os.rmdir(temp_dir)
|
|
495
|
+
except Exception:
|
|
496
|
+
pass
|
|
497
|
+
|
|
498
|
+
def save_video(
|
|
499
|
+
self,
|
|
500
|
+
frame_stack: Union[List, Any],
|
|
501
|
+
file_name: str,
|
|
502
|
+
fps: int = 20,
|
|
503
|
+
**imageio_kwargs
|
|
504
|
+
) -> Dict[str, Any]:
|
|
505
|
+
"""
|
|
506
|
+
Save video frame stack to a file.
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
frame_stack: List of numpy arrays or stacked array (shape: [N, H, W] or [N, H, W, C])
|
|
510
|
+
file_name: Name of file to create (extension determines format: .mp4, .gif, .avi, .webm)
|
|
511
|
+
fps: Frames per second (default: 20)
|
|
512
|
+
**imageio_kwargs: Additional arguments passed to imageio.v3.imwrite():
|
|
513
|
+
- codec: Video codec (e.g., 'libx264', 'h264')
|
|
514
|
+
- quality: Quality level (int, higher is better)
|
|
515
|
+
- pixelformat: Pixel format (e.g., 'yuv420p')
|
|
516
|
+
- macro_block_size: Macro block size for encoding
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
File metadata dict with id, path, filename, checksum, etc.
|
|
520
|
+
|
|
521
|
+
Raises:
|
|
522
|
+
RuntimeError: If experiment not open or write-protected
|
|
523
|
+
ImportError: If imageio or scikit-image not installed
|
|
524
|
+
ValueError: If frame_stack is empty or invalid format
|
|
525
|
+
|
|
526
|
+
Examples:
|
|
527
|
+
import numpy as np
|
|
528
|
+
|
|
529
|
+
# Grayscale frames (float values 0-1)
|
|
530
|
+
frames = [np.random.rand(480, 640) for _ in range(30)]
|
|
531
|
+
result = experiment.files(prefix="/videos").save_video(frames, "output.mp4")
|
|
532
|
+
|
|
533
|
+
# RGB frames with custom FPS
|
|
534
|
+
frames = [np.random.rand(480, 640, 3) for _ in range(60)]
|
|
535
|
+
result = experiment.files(prefix="/videos").save_video(frames, "output.mp4", fps=30)
|
|
536
|
+
|
|
537
|
+
# Save as GIF
|
|
538
|
+
frames = [np.random.rand(200, 200) for _ in range(20)]
|
|
539
|
+
result = experiment.files(prefix="/videos").save_video(frames, "animation.gif")
|
|
540
|
+
|
|
541
|
+
# With custom codec and quality
|
|
542
|
+
result = experiment.files(prefix="/videos").save_video(
|
|
543
|
+
frames, "output.mp4", fps=30, codec='libx264', quality=8
|
|
544
|
+
)
|
|
545
|
+
"""
|
|
546
|
+
import tempfile
|
|
547
|
+
import os
|
|
548
|
+
|
|
549
|
+
try:
|
|
550
|
+
import imageio.v3 as iio
|
|
551
|
+
except ImportError:
|
|
552
|
+
raise ImportError("imageio is not installed. Install it with: pip install imageio imageio-ffmpeg")
|
|
553
|
+
|
|
554
|
+
try:
|
|
555
|
+
from skimage import img_as_ubyte
|
|
556
|
+
except ImportError:
|
|
557
|
+
raise ImportError("scikit-image is not installed. Install it with: pip install scikit-image")
|
|
558
|
+
|
|
559
|
+
if not self._experiment._is_open:
|
|
560
|
+
raise RuntimeError("Experiment not open. Use experiment.run.start() or context manager.")
|
|
561
|
+
|
|
562
|
+
if self._experiment._write_protected:
|
|
563
|
+
raise RuntimeError("Experiment is write-protected and cannot be modified.")
|
|
564
|
+
|
|
565
|
+
# Validate frame_stack
|
|
566
|
+
try:
|
|
567
|
+
# Handle both list and numpy array
|
|
568
|
+
if len(frame_stack) == 0:
|
|
569
|
+
raise ValueError("frame_stack is empty")
|
|
570
|
+
except TypeError:
|
|
571
|
+
raise ValueError("frame_stack must be a list or numpy array")
|
|
572
|
+
|
|
573
|
+
# Create temporary file with desired filename
|
|
574
|
+
temp_dir = tempfile.mkdtemp()
|
|
575
|
+
temp_path = os.path.join(temp_dir, file_name)
|
|
576
|
+
|
|
577
|
+
try:
|
|
578
|
+
# Convert frames to uint8 format (handles float32/64, grayscale, RGB, etc.)
|
|
579
|
+
# img_as_ubyte automatically scales [0.0, 1.0] floats to [0, 255] uint8
|
|
580
|
+
frames_ubyte = img_as_ubyte(frame_stack)
|
|
581
|
+
|
|
582
|
+
# Encode video to temp file
|
|
583
|
+
try:
|
|
584
|
+
iio.imwrite(temp_path, frames_ubyte, fps=fps, **imageio_kwargs)
|
|
585
|
+
except iio.core.NeedDownloadError:
|
|
586
|
+
# Auto-download FFmpeg if not available
|
|
587
|
+
import imageio.plugins.ffmpeg
|
|
588
|
+
imageio.plugins.ffmpeg.download()
|
|
589
|
+
iio.imwrite(temp_path, frames_ubyte, fps=fps, **imageio_kwargs)
|
|
590
|
+
|
|
591
|
+
# Save using existing save() method
|
|
592
|
+
original_file_path = self._file_path
|
|
593
|
+
self._file_path = temp_path
|
|
594
|
+
|
|
595
|
+
# Upload and get result
|
|
596
|
+
result = self.save()
|
|
597
|
+
|
|
598
|
+
# Restore original file_path
|
|
599
|
+
self._file_path = original_file_path
|
|
600
|
+
|
|
601
|
+
return result
|
|
602
|
+
finally:
|
|
603
|
+
# Clean up temp file and directory
|
|
604
|
+
try:
|
|
605
|
+
os.unlink(temp_path)
|
|
606
|
+
os.rmdir(temp_dir)
|
|
607
|
+
except Exception:
|
|
608
|
+
pass
|
|
609
|
+
|
|
417
610
|
|
|
418
611
|
def compute_sha256(file_path: str) -> str:
|
|
419
612
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|