1
1
import json
2
2
from abc import ABC
3
3
from dataclasses import dataclass , field
4
- from enum import Enum
5
4
from typing import Any , Dict , List , Optional , Union
6
5
7
6
from nucleus .constants import (
13
12
NUM_SENSORS_KEY ,
14
13
POINTCLOUD_LOCATION_KEY ,
15
14
REFERENCE_ID_KEY ,
15
+ UPLOAD_TO_SCALE_KEY ,
16
16
VIDEO_LOCATION_KEY ,
17
- VIDEO_UPLOAD_TYPE_KEY ,
18
17
VIDEO_URL_KEY ,
19
18
)
20
19
@@ -414,11 +413,6 @@ def flatten(t):
414
413
return [item for sublist in t for item in sublist ]
415
414
416
415
417
- class _VideoUploadType (Enum ):
418
- IMAGE = "image"
419
- VIDEO = "video"
420
-
421
-
422
416
@dataclass
423
417
class VideoScene (ABC ):
424
418
"""Video or sequence of images over time.
@@ -440,29 +434,33 @@ class VideoScene(ABC):
440
434
441
435
Parameters:
442
436
reference_id (str): User-specified identifier to reference the scene.
443
- attachment_type (str): The type of attachments being uploaded as a string literal.
444
- If the video is uploaded as an array of frames, the attachment_type is "image".
445
- If the video is uploaded as an MP4, the attachment_type is "video".
446
- frame_rate (Optional[int]): Required if attachment_type is "image". Frame rate of the video.
447
- video_location (Optional[str]): Required if attachment_type is "video". The remote URL
437
+ frame_rate (Optional[int]): Required if uploading items. Frame rate of the video.
438
+ video_location (Optional[str]): Required if not uploading items. The remote URL
448
439
containing the video MP4. Remote formats supported include any URL (``http://``
449
440
or ``https://``) or URIs for AWS S3, Azure, or GCS (i.e. ``s3://``, ``gcs://``).
450
- items (Optional[List[:class:`DatasetItem`]]): Required if attachment_type is "image" .
441
+ items (Optional[List[:class:`DatasetItem`]]): Required if not uploading video_location .
451
442
List of items representing frames, to be a part of the scene. A scene can be created
452
443
before items have been added to it, but must be non-empty when uploading to
453
444
a :class:`Dataset`. A video scene can contain a maximum of 3000 items.
454
445
metadata (Optional[Dict]): Optional metadata to include with the scene.
446
+ upload_to_scale (Optional[bool]): Set this to false in order to use
447
+ `privacy mode <https://nucleus.scale.com/docs/privacy-mode>`_. If using privacy mode
448
+ you must upload both a video_location and items to the VideoScene.
449
+
450
+ Setting this to false means the actual data within the video scene will not be
451
+ uploaded to scale meaning that you can send in links that are only accessible
452
+ to certain users, and not to Scale.
455
453
456
454
Refer to our `guide to uploading video data
457
455
<https://nucleus.scale.com/docs/uploading-video-data>`_ for more info!
458
456
"""
459
457
460
458
reference_id : str
461
- attachment_type : _VideoUploadType
462
459
frame_rate : Optional [int ] = None
463
460
video_location : Optional [str ] = None
464
461
items : List [DatasetItem ] = field (default_factory = list )
465
462
metadata : Optional [dict ] = field (default_factory = dict )
463
+ upload_to_scale : Optional [bool ] = True
466
464
467
465
def __post_init__ (self ):
468
466
if self .metadata is None :
@@ -480,25 +478,44 @@ def __eq__(self, other):
480
478
481
479
@property
482
480
def length (self ) -> int :
483
- """Gets number of items in the scene for videos uploaded as an array of images."""
481
+ """Gets number of items in the scene for videos uploaded with an array of images."""
484
482
assert (
485
- self .video_location is None
486
- ), "Videos uploaded as an mp4 have no length"
483
+ not self .upload_to_scale or not self . video_location
484
+ ), "Only videos with items have a length"
487
485
return len (self .items )
488
486
489
487
def validate (self ):
490
488
# TODO: make private
491
- assert self .attachment_type in ("image" , "video" )
492
- if self .attachment_type == "image" :
489
+ assert (
490
+ self .items or self .video_location
491
+ ), "Please upload either a video_location or an array of dataset items representing frames"
492
+ if self .upload_to_scale is False :
493
493
assert (
494
494
self .frame_rate > 0
495
- ), "When attachment_type='image' frame rate must be at least 1"
495
+ ), "When using privacy mode frame rate must be at least 1"
496
496
assert (
497
497
self .items and self .length > 0
498
- ), "When attachment_type='image' scene must have a list of items of length at least 1"
498
+ ), "When using privacy mode scene must have a list of items of length at least 1"
499
+ for item in self .items :
500
+ assert isinstance (
501
+ item , DatasetItem
502
+ ), "Each item in a scene must be a DatasetItem object"
503
+ assert (
504
+ item .image_location is not None
505
+ ), "Each item in a video scene must have an image_location"
506
+ assert (
507
+ item .upload_to_scale is not False
508
+ ), "Please specify whether to upload to scale in the VideoScene for videos"
509
+ elif self .items :
510
+ assert (
511
+ self .frame_rate > 0
512
+ ), "When uploading an array of items frame rate must be at least 1"
513
+ assert (
514
+ self .length > 0
515
+ ), "When uploading an array of items scene must have a list of items of length at least 1"
499
516
assert (
500
517
not self .video_location
501
- ), "No video location is accepted when attachment_type='image' "
518
+ ), "No video location is accepted when uploading an array of items unless you are using privacy mode "
502
519
for item in self .items :
503
520
assert isinstance (
504
521
item , DatasetItem
@@ -508,17 +525,14 @@ def validate(self):
508
525
), "Each item in a video scene must have an image_location"
509
526
assert (
510
527
item .upload_to_scale is not False
511
- ), "Skipping upload to Scale is not currently implemented for videos"
512
- if self .attachment_type == "video" :
513
- assert (
514
- self .video_location
515
- ), "When attachment_type='video' a video_location is required"
528
+ ), "Please specify whether to upload to scale in the VideoScene for videos"
529
+ else :
516
530
assert (
517
531
not self .frame_rate
518
- ), "No frame rate is accepted when attachment_type='video' "
532
+ ), "No frame rate is accepted when uploading a video_location "
519
533
assert (
520
534
not self .items
521
- ), "No list of items is accepted when attachment_type='video' "
535
+ ), "No list of items is accepted when uploading a video_location unless you are using privacy mode "
522
536
523
537
def add_item (
524
538
self , item : DatasetItem , index : int = None , update : bool = False
@@ -532,8 +546,8 @@ def add_item(
532
546
exists. Default is False.
533
547
"""
534
548
assert (
535
- self .video_location is None
536
- ), "Cannot add item to a video uploaded as an mp4 "
549
+ not self .upload_to_scale or not self . video_location
550
+ ), "Cannot add item to a video without items "
537
551
if index is None :
538
552
index = len (self .items )
539
553
assert (
@@ -553,8 +567,8 @@ def get_item(self, index: int) -> DatasetItem:
553
567
Return:
554
568
:class:`DatasetItem`: DatasetItem at the specified index."""
555
569
assert (
556
- self .video_location is None
557
- ), "Cannot get item from a video uploaded as an mp4 "
570
+ not self .upload_to_scale or not self . video_location
571
+ ), "Cannot add item to a video without items "
558
572
if index < 0 or index > len (self .items ):
559
573
raise ValueError (
560
574
f"This scene does not have an item at index { index } "
@@ -568,8 +582,8 @@ def get_items(self) -> List[DatasetItem]:
568
582
List[:class:`DatasetItem`]: List of DatasetItems, sorted by index ascending.
569
583
"""
570
584
assert (
571
- self .video_location is None
572
- ), "Cannot get items from a video uploaded as an mp4 "
585
+ not self .upload_to_scale or not self . video_location
586
+ ), "Cannot add item to a video without items "
573
587
return self .items
574
588
575
589
def info (self ):
@@ -594,6 +608,8 @@ def info(self):
594
608
payload [VIDEO_URL_KEY ] = self .video_location
595
609
if self .items :
596
610
payload [LENGTH_KEY ] = self .length
611
+ if self .upload_to_scale :
612
+ payload [UPLOAD_TO_SCALE_KEY ] = self .upload_to_scale
597
613
598
614
return payload
599
615
@@ -605,18 +621,17 @@ def from_json(cls, payload: dict):
605
621
return cls (
606
622
reference_id = payload [REFERENCE_ID_KEY ],
607
623
frame_rate = payload .get (FRAME_RATE_KEY , None ),
608
- attachment_type = payload [VIDEO_UPLOAD_TYPE_KEY ],
609
624
items = items ,
610
625
metadata = payload .get (METADATA_KEY , {}),
611
626
video_location = payload .get (VIDEO_URL_KEY , None ),
627
+ upload_to_scale = payload .get (UPLOAD_TO_SCALE_KEY , True ),
612
628
)
613
629
614
630
def to_payload (self ) -> dict :
615
631
"""Serializes scene object to schematized JSON dict."""
616
632
self .validate ()
617
633
payload : Dict [str , Any ] = {
618
634
REFERENCE_ID_KEY : self .reference_id ,
619
- VIDEO_UPLOAD_TYPE_KEY : self .attachment_type ,
620
635
}
621
636
if self .frame_rate :
622
637
payload [FRAME_RATE_KEY ] = self .frame_rate
@@ -629,6 +644,8 @@ def to_payload(self) -> dict:
629
644
item .to_payload (is_scene = True ) for item in self .items
630
645
]
631
646
payload [FRAMES_KEY ] = items_payload
647
+ if self .upload_to_scale is not None :
648
+ payload [UPLOAD_TO_SCALE_KEY ] = self .upload_to_scale
632
649
return payload
633
650
634
651
def to_json (self ) -> str :
@@ -647,7 +664,7 @@ def check_all_scene_paths_remote(
647
664
f"All paths for videos must be remote, but { scene .video_location } is either "
648
665
"local, or a remote URL type that is not supported."
649
666
)
650
- else :
667
+ if isinstance ( scene , LidarScene ) or scene . items :
651
668
for item in scene .get_items ():
652
669
pointcloud_location = getattr (item , POINTCLOUD_LOCATION_KEY )
653
670
if pointcloud_location and is_local_path (pointcloud_location ):
0 commit comments