ml-dash 0.6.6__py3-none-any.whl → 0.6.9__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
@@ -56,6 +56,7 @@ class FileBuilder:
56
56
  experiment.files.save_blob(b"xxx", to="data.bin")
57
57
  experiment.files.save_torch(model, to="model.pt")
58
58
  experiment.files.save_pkl(data, to="data.pkl")
59
+ experiment.files.save_image(array, to="frame.png")
59
60
  experiment.files.save_fig(fig, to="plot.png")
60
61
  experiment.files.save_video(frames, to="video.mp4")
61
62
  """
@@ -184,8 +185,9 @@ class FileBuilder:
184
185
  - str (file path): Uploads existing file
185
186
  - bytes: Saves as binary blob (requires 'to' parameter)
186
187
  - dict/list: Saves as JSON (requires 'to' parameter)
188
+ - numpy.ndarray: Saves as image (requires 'to' parameter with image extension)
187
189
  - None: Uses file_path from constructor (backwards compatibility)
188
- to: Target filename (required for bytes/dict/list, optional for file paths)
190
+ to: Target filename (required for bytes/dict/list/arrays, optional for file paths)
189
191
  description: Optional description
190
192
  tags: Optional list of tags
191
193
  metadata: Optional metadata dict
@@ -224,7 +226,17 @@ class FileBuilder:
224
226
  raise ValueError("'to' parameter is required when saving dict/list")
225
227
  return self.save_json(content, to=to)
226
228
 
227
- raise ValueError(f"Unsupported content type: {type(content)}. Expected str (file path), bytes, dict, or list.")
229
+ # Check if content is a numpy array (save as image)
230
+ try:
231
+ import numpy as np
232
+ if isinstance(content, np.ndarray):
233
+ if not to:
234
+ raise ValueError("'to' parameter is required when saving numpy arrays")
235
+ return self.save_image(content, to=to)
236
+ except ImportError:
237
+ pass # numpy not available
238
+
239
+ raise ValueError(f"Unsupported content type: {type(content)}. Expected str (file path), bytes, dict, list, or numpy.ndarray.")
228
240
 
229
241
  def _save_file(
230
242
  self,
@@ -288,23 +300,27 @@ class FileBuilder:
288
300
  temp_path = os.path.join(temp_dir, filename)
289
301
  # Create parent directories if filename contains path
290
302
  os.makedirs(os.path.dirname(temp_path), exist_ok=True)
291
- try:
292
- with open(temp_path, 'wb') as f:
293
- f.write(data)
294
- return self._save_file(
295
- fpath=temp_path,
296
- prefix=prefix,
297
- description=description,
298
- tags=tags,
299
- metadata=metadata
300
- )
301
- finally:
303
+
304
+ with open(temp_path, 'wb') as f:
305
+ f.write(data)
306
+ result = self._save_file(
307
+ fpath=temp_path,
308
+ prefix=prefix,
309
+ description=description,
310
+ tags=tags,
311
+ metadata=metadata
312
+ )
313
+
314
+ # Only clean up if NOT queued for buffered upload
315
+ if result.get("status") != "queued":
302
316
  try:
303
317
  os.unlink(temp_path)
304
318
  os.rmdir(temp_dir)
305
319
  except Exception:
306
320
  pass
307
321
 
322
+ return result
323
+
308
324
  def _save_json(
309
325
  self,
310
326
  content: Any,
@@ -323,23 +339,27 @@ class FileBuilder:
323
339
  temp_path = os.path.join(temp_dir, filename)
324
340
  # Create parent directories if filename contains path
325
341
  os.makedirs(os.path.dirname(temp_path), exist_ok=True)
326
- try:
327
- with open(temp_path, 'w') as f:
328
- json.dump(content, f, indent=2)
329
- return self._save_file(
330
- fpath=temp_path,
331
- prefix=prefix,
332
- description=description,
333
- tags=tags,
334
- metadata=metadata
335
- )
336
- finally:
342
+
343
+ with open(temp_path, 'w') as f:
344
+ json.dump(content, f, indent=2)
345
+ result = self._save_file(
346
+ fpath=temp_path,
347
+ prefix=prefix,
348
+ description=description,
349
+ tags=tags,
350
+ metadata=metadata
351
+ )
352
+
353
+ # Only clean up if NOT queued for buffered upload
354
+ if result.get("status") != "queued":
337
355
  try:
338
356
  os.unlink(temp_path)
339
357
  os.rmdir(temp_dir)
340
358
  except Exception:
341
359
  pass
342
360
 
361
+ return result
362
+
343
363
  def _save_torch(
344
364
  self,
345
365
  model: Any,
@@ -358,22 +378,26 @@ class FileBuilder:
358
378
  temp_path = os.path.join(temp_dir, filename)
359
379
  # Create parent directories if filename contains path
360
380
  os.makedirs(os.path.dirname(temp_path), exist_ok=True)
361
- try:
362
- torch.save(model, temp_path)
363
- return self._save_file(
364
- fpath=temp_path,
365
- prefix=prefix,
366
- description=description,
367
- tags=tags,
368
- metadata=metadata
369
- )
370
- finally:
381
+
382
+ torch.save(model, temp_path)
383
+ result = self._save_file(
384
+ fpath=temp_path,
385
+ prefix=prefix,
386
+ description=description,
387
+ tags=tags,
388
+ metadata=metadata
389
+ )
390
+
391
+ # Only clean up if NOT queued for buffered upload
392
+ if result.get("status") != "queued":
371
393
  try:
372
394
  os.unlink(temp_path)
373
395
  os.rmdir(temp_dir)
374
396
  except Exception:
375
397
  pass
376
398
 
399
+ return result
400
+
377
401
  def _save_fig(
378
402
  self,
379
403
  fig: Any,
@@ -393,23 +417,27 @@ class FileBuilder:
393
417
  temp_path = os.path.join(temp_dir, filename)
394
418
  # Create parent directories if filename contains path
395
419
  os.makedirs(os.path.dirname(temp_path), exist_ok=True)
396
- try:
397
- fig.savefig(temp_path, **kwargs)
398
- plt.close(fig)
399
- return self._save_file(
400
- fpath=temp_path,
401
- prefix=prefix,
402
- description=description,
403
- tags=tags,
404
- metadata=metadata
405
- )
406
- finally:
420
+
421
+ fig.savefig(temp_path, **kwargs)
422
+ plt.close(fig)
423
+ result = self._save_file(
424
+ fpath=temp_path,
425
+ prefix=prefix,
426
+ description=description,
427
+ tags=tags,
428
+ metadata=metadata
429
+ )
430
+
431
+ # Only clean up if NOT queued for buffered upload
432
+ if result.get("status") != "queued":
407
433
  try:
408
434
  os.unlink(temp_path)
409
435
  os.rmdir(temp_dir)
410
436
  except Exception:
411
437
  pass
412
438
 
439
+ return result
440
+
413
441
  def _save_pickle(
414
442
  self,
415
443
  content: Any,
@@ -428,23 +456,107 @@ class FileBuilder:
428
456
  temp_path = os.path.join(temp_dir, filename)
429
457
  # Create parent directories if filename contains path
430
458
  os.makedirs(os.path.dirname(temp_path), exist_ok=True)
459
+
460
+ with open(temp_path, 'wb') as f:
461
+ pickle.dump(content, f)
462
+ result = self._save_file(
463
+ fpath=temp_path,
464
+ prefix=prefix,
465
+ description=description,
466
+ tags=tags,
467
+ metadata=metadata
468
+ )
469
+
470
+ # Only clean up if NOT queued for buffered upload
471
+ if result.get("status") != "queued":
472
+ try:
473
+ os.unlink(temp_path)
474
+ os.rmdir(temp_dir)
475
+ except Exception:
476
+ pass
477
+
478
+ return result
479
+
480
+ def _save_image(
481
+ self,
482
+ array: Any,
483
+ filename: str,
484
+ prefix: str,
485
+ description: Optional[str],
486
+ tags: Optional[List[str]],
487
+ metadata: Optional[Dict[str, Any]],
488
+ quality: int = 95
489
+ ) -> Dict[str, Any]:
490
+ """Save numpy array as image file."""
491
+ import tempfile
492
+ import os
493
+
431
494
  try:
432
- with open(temp_path, 'wb') as f:
433
- pickle.dump(content, f)
434
- return self._save_file(
435
- fpath=temp_path,
436
- prefix=prefix,
437
- description=description,
438
- tags=tags,
439
- metadata=metadata
440
- )
441
- finally:
495
+ from PIL import Image
496
+ import numpy as np
497
+ except ImportError:
498
+ raise ImportError("PIL/Pillow is required for saving images. Install it with: pip install Pillow")
499
+
500
+ temp_dir = tempfile.mkdtemp()
501
+ temp_path = os.path.join(temp_dir, filename)
502
+ # Create parent directories if filename contains path
503
+ os.makedirs(os.path.dirname(temp_path), exist_ok=True)
504
+
505
+ # Convert numpy array to PIL Image
506
+ # Handle different array shapes and dtypes
507
+ if array.dtype == np.uint8:
508
+ img = Image.fromarray(array)
509
+ else:
510
+ # Normalize to 0-255 range for non-uint8 arrays
511
+ if array.max() <= 1.0:
512
+ # Assume normalized float in [0, 1]
513
+ array_uint8 = (array * 255).astype(np.uint8)
514
+ else:
515
+ # Scale to 0-255
516
+ array_uint8 = ((array - array.min()) / (array.max() - array.min()) * 255).astype(np.uint8)
517
+ img = Image.fromarray(array_uint8)
518
+
519
+ # Handle JPEG-specific requirements
520
+ file_ext = os.path.splitext(filename)[1].lower()
521
+ save_kwargs = {}
522
+ if file_ext in ['.jpg', '.jpeg']:
523
+ # JPEG doesn't support alpha channel, convert RGBA to RGB
524
+ if img.mode in ('RGBA', 'LA', 'P'):
525
+ # Create white background
526
+ background = Image.new('RGB', img.size, (255, 255, 255))
527
+ if img.mode == 'P':
528
+ img = img.convert('RGBA')
529
+ background.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)
530
+ img = background
531
+ elif img.mode not in ('RGB', 'L'):
532
+ # Convert other modes to RGB
533
+ img = img.convert('RGB')
534
+ # Set JPEG quality
535
+ save_kwargs['quality'] = quality
536
+ save_kwargs['optimize'] = True
537
+
538
+ # Save image to temp file
539
+ img.save(temp_path, **save_kwargs)
540
+
541
+ result = self._save_file(
542
+ fpath=temp_path,
543
+ prefix=prefix,
544
+ description=description,
545
+ tags=tags,
546
+ metadata=metadata
547
+ )
548
+
549
+ # Only clean up if NOT queued for buffered upload
550
+ # If queued, the buffer manager will clean up after upload
551
+ if result.get("status") != "queued":
442
552
  try:
443
553
  os.unlink(temp_path)
444
554
  os.rmdir(temp_dir)
445
555
  except Exception:
446
556
  pass
447
557
 
558
+ return result
559
+
448
560
  def list(self, pattern: Optional[str] = None) -> List[Dict[str, Any]]:
449
561
  """
450
562
  List files with optional glob pattern filtering.
@@ -881,6 +993,49 @@ class FileBuilder:
881
993
  metadata=self._metadata
882
994
  )
883
995
 
996
+ def save_image(
997
+ self,
998
+ array: Any,
999
+ *,
1000
+ to: str,
1001
+ quality: int = 95
1002
+ ) -> Dict[str, Any]:
1003
+ """
1004
+ Save numpy array as an image file.
1005
+
1006
+ Args:
1007
+ array: Numpy array representing the image (HxW or HxWxC)
1008
+ to: Target filename (must have image extension like .png, .jpg, .jpeg)
1009
+ quality: JPEG quality (1-100, default: 95). Only used for JPEG files.
1010
+
1011
+ Returns:
1012
+ File metadata dict with id, path, filename, checksum, etc.
1013
+
1014
+ Examples:
1015
+ # Save rendered frame from MuJoCo as PNG
1016
+ pixels = renderer.render()
1017
+ result = dxp.files("frames").save_image(pixels, to="frame_001.png")
1018
+
1019
+ # Save as JPEG with custom quality
1020
+ result = dxp.files("frames").save_image(pixels, to="frame_001.jpg", quality=85)
1021
+
1022
+ # Save numpy array as image
1023
+ import numpy as np
1024
+ img_array = np.random.rand(480, 640, 3) * 255
1025
+ result = dxp.files("images").save_image(img_array, to="random.png")
1026
+ """
1027
+ prefix = '/' + self._path.lstrip('/') if self._path else self._prefix
1028
+
1029
+ return self._save_image(
1030
+ array=array,
1031
+ filename=to,
1032
+ prefix=prefix,
1033
+ description=self._description,
1034
+ tags=self._tags,
1035
+ metadata=self._metadata,
1036
+ quality=quality
1037
+ )
1038
+
884
1039
  def save_fig(
885
1040
  self,
886
1041
  fig: Optional[Any] = None,
@@ -974,29 +1129,32 @@ class FileBuilder:
974
1129
  # Create parent directories if filename contains path
975
1130
  os.makedirs(os.path.dirname(temp_path), exist_ok=True)
976
1131
 
1132
+ frames_ubyte = img_as_ubyte(frame_stack)
977
1133
  try:
978
- frames_ubyte = img_as_ubyte(frame_stack)
979
- try:
980
- iio.imwrite(temp_path, frames_ubyte, fps=fps, **imageio_kwargs)
981
- except iio.core.NeedDownloadError:
982
- import imageio.plugins.ffmpeg
983
- imageio.plugins.ffmpeg.download()
984
- iio.imwrite(temp_path, frames_ubyte, fps=fps, **imageio_kwargs)
1134
+ iio.imwrite(temp_path, frames_ubyte, fps=fps, **imageio_kwargs)
1135
+ except iio.core.NeedDownloadError:
1136
+ import imageio.plugins.ffmpeg
1137
+ imageio.plugins.ffmpeg.download()
1138
+ iio.imwrite(temp_path, frames_ubyte, fps=fps, **imageio_kwargs)
1139
+
1140
+ result = self._save_file(
1141
+ fpath=temp_path,
1142
+ prefix=prefix,
1143
+ description=self._description,
1144
+ tags=self._tags,
1145
+ metadata=self._metadata
1146
+ )
985
1147
 
986
- return self._save_file(
987
- fpath=temp_path,
988
- prefix=prefix,
989
- description=self._description,
990
- tags=self._tags,
991
- metadata=self._metadata
992
- )
993
- finally:
1148
+ # Only clean up if NOT queued for buffered upload
1149
+ if result.get("status") != "queued":
994
1150
  try:
995
1151
  os.unlink(temp_path)
996
1152
  os.rmdir(temp_dir)
997
1153
  except Exception:
998
1154
  pass
999
1155
 
1156
+ return result
1157
+
1000
1158
  def duplicate(self, source: Union[str, Dict[str, Any]], to: str) -> Dict[str, Any]:
1001
1159
  """
1002
1160
  Duplicate an existing file to a new path within the same experiment.