ml-dash 0.5.5__py3-none-any.whl → 0.5.6__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.
ml_dash/files.py CHANGED
@@ -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:
@@ -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
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ml-dash
3
- Version: 0.5.5
3
+ Version: 0.5.6
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'
@@ -2,12 +2,12 @@ ml_dash/__init__.py,sha256=o_LrWVJBY_VkUGhSBs5wdb_NqEsHD1AK9HGsjZGxHxQ,1414
2
2
  ml_dash/auto_start.py,sha256=c3XcXFpZdvjtWauEoK5043Gw9k0L_5IDq4fdiB2ha88,959
3
3
  ml_dash/client.py,sha256=vhWcS5o2n3o4apEjVeLmu7flCEzxBbBOoLSQNcAx_ew,17267
4
4
  ml_dash/experiment.py,sha256=K36HkHJb_O2-vdaPPOCq74_2nZtfiLaS0o7qhTntD8Q,30646
5
- ml_dash/files.py,sha256=mCaKoeeog9GqiTTt5hedQCHSp0YfxMFON4c2EuKTbmw,15843
5
+ ml_dash/files.py,sha256=ZY9BwPm_Fym-yg0I3lBtZwi1c7ioKRmhp_86f2u06ww,23014
6
6
  ml_dash/log.py,sha256=0yXaNnFwYeBI3tRLHX3kkqWRpg0MbSGwmgjnOfsElCk,5350
7
7
  ml_dash/metric.py,sha256=c0Zl0wEufmQuVfwIMvrORLwqe92Iaf0PfKRgmlgQWzQ,10343
8
8
  ml_dash/params.py,sha256=xaByDSVar4D1pZqxTANkMPeZTL5-V7ewJe5TXfPLhMQ,5980
9
9
  ml_dash/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  ml_dash/storage.py,sha256=8lyT5ZdvhS2nEyrEgMnFAT0LzV5ne1v8tkI3w1PUHJ4,30793
11
- ml_dash-0.5.5.dist-info/WHEEL,sha256=X16MKk8bp2DRsAuyteHJ-9qOjzmnY0x1aj0P1ftqqWA,78
12
- ml_dash-0.5.5.dist-info/METADATA,sha256=PHpldRX1K1duM-o5S5uu5dAE2h0ECplMLw_wDc-9t3I,6043
13
- ml_dash-0.5.5.dist-info/RECORD,,
11
+ ml_dash-0.5.6.dist-info/WHEEL,sha256=X16MKk8bp2DRsAuyteHJ-9qOjzmnY0x1aj0P1ftqqWA,78
12
+ ml_dash-0.5.6.dist-info/METADATA,sha256=l2nzY_SRqgzZFeS6_KKHtzgsOSdZ40Mc4XAaGkggEaU,6147
13
+ ml_dash-0.5.6.dist-info/RECORD,,