@@ -219,14 +219,7 @@ def load(cls, annotation_path: Union[str, PathLike]) -> "Annotation":
219219 return cls ._load_from_zip (annotation_path )
220220 elif annotation_path .suffix == ".nml" :
221221 with annotation_path .open (mode = "rb" ) as f :
222- annotation , nml = cls ._load_from_nml (annotation_path .stem , f )
223- if len (nml .volumes ) > 0 :
224- warnings .warn (
225- "The loaded nml contains references to volume layer annotations. "
226- + "Those can only be loaded from a zip-file containing the nml and the volume annotation layer zips. "
227- + "Omitting the volume layer annotations."
228- )
229- return annotation
222+ return cls ._load_from_nml (annotation_path .stem , f )
230223 else :
231224 raise RuntimeError (
232225 "The loaded annotation must have the suffix .zip or .nml, but is {annotation_path.suffix}"
@@ -327,12 +320,9 @@ def download(
327320 _header_value , header_params = cgi .parse_header (content_disposition_header )
328321 filename = header_params .get ("filename" , "" )
329322 if filename .endswith (".nml" ):
330- annotation , nml = Annotation ._load_from_nml (
323+ annotation = Annotation ._load_from_nml (
331324 filename [:- 4 ], BytesIO (response .content )
332325 )
333- assert (
334- len (nml .volumes ) == 0
335- ), "The downloaded NML contains volume tags, it should have downloaded a zip instead."
336326 else :
337327 assert filename .endswith (
338328 ".zip"
@@ -367,54 +357,66 @@ def open_as_remote_dataset(
367357
368358 @classmethod
369359 def _load_from_nml (
370- cls , name : str , nml_content : BinaryIO
371- ) -> Tuple ["Annotation" , wknml .Nml ]:
360+ cls ,
361+ name : str ,
362+ nml_content : BinaryIO ,
363+ possible_volume_paths : Optional [List [ZipPath ]] = None ,
364+ ) -> "Annotation" :
372365 nml = wknml .Nml .parse (nml_content )
373366
374- return (
375- cls (
376- name = name ,
377- skeleton = nml_to_skeleton (nml ),
378- owner_name = nml .get_meta ("username" ),
379- annotation_id = nml .get_meta ("annotationId" ),
380- time = nml .parameters .time ,
381- edit_position = nml .parameters .editPosition ,
382- edit_rotation = nml .parameters .editRotation ,
383- zoom_level = nml .parameters .zoomLevel ,
384- task_bounding_box = nml .parameters .taskBoundingBox ,
385- user_bounding_boxes = nml .parameters .userBoundingBoxes or [],
386- metadata = {
387- i .name : i .content
388- for i in nml .meta
389- if i .name not in ["username" , "annotationId" ]
390- },
391- ),
392- nml ,
367+ annotation = cls (
368+ name = name ,
369+ skeleton = nml_to_skeleton (nml ),
370+ owner_name = nml .get_meta ("username" ),
371+ annotation_id = nml .get_meta ("annotationId" ),
372+ time = nml .parameters .time ,
373+ edit_position = nml .parameters .editPosition ,
374+ edit_rotation = nml .parameters .editRotation ,
375+ zoom_level = nml .parameters .zoomLevel ,
376+ task_bounding_box = nml .parameters .taskBoundingBox ,
377+ user_bounding_boxes = nml .parameters .userBoundingBoxes or [],
378+ metadata = {
379+ i .name : i .content
380+ for i in nml .meta
381+ if i .name not in ["username" , "annotationId" ]
382+ },
393383 )
384+ annotation ._volume_layers = cls ._parse_volumes (nml , possible_volume_paths )
385+ return annotation
394386
395- @classmethod
396- def _load_from_zip (cls , content : Union [str , PathLike , BinaryIO ]) -> "Annotation" :
397- zipfile = ZipFile (content )
398- paths = [ZipPath (zipfile , i .filename ) for i in zipfile .filelist ]
399- nml_paths = [i for i in paths if i .suffix == ".nml" ]
400- assert len (nml_paths ) > 0 , "Couldn't find an nml file in the supplied zip-file."
401- assert (
402- len (nml_paths ) == 1
403- ), f"There must be exactly one nml file in the zip-file, buf found { len (nml_paths )} ."
404- with nml_paths [0 ].open (mode = "rb" ) as f :
405- annotation , nml = cls ._load_from_nml (nml_paths [0 ].stem , f )
387+ @staticmethod
388+ def _parse_volumes (
389+ nml : wknml .Nml , possible_paths : Optional [List [ZipPath ]]
390+ ) -> List [_VolumeLayer ]:
406391 volume_layers = []
392+ layers_with_not_found_location = []
393+ layers_without_location = []
407394 for volume in nml .volumes :
408- fitting_volume_paths = [i for i in paths if str (i .at ) == volume .location ]
409- assert (
410- len (fitting_volume_paths ) == 1
411- ), f"Couldn't find the file { volume .location } for the volume annotation { volume .name or volume .id } "
412- with fitting_volume_paths [0 ].open (mode = "rb" ) as f :
413- with ZipFile (f ) as volume_layer_zipfile :
414- if len (volume_layer_zipfile .filelist ) == 0 :
415- volume_path = None
416- else :
417- volume_path = fitting_volume_paths [0 ]
395+ if possible_paths is None : # when parsing NML files
396+ volume_path = None
397+ if volume .location is not None :
398+ # This should only happen if a zipped nml
399+ # is unpacked and loaded directly.
400+ layers_with_not_found_location .append (volume )
401+ volume_path = None
402+ elif volume .location is None :
403+ volume_path = None
404+ layers_without_location .append (volume )
405+ else :
406+ fitting_volume_paths = [
407+ i for i in possible_paths if str (i .at ) == volume .location
408+ ]
409+ if len (fitting_volume_paths ) == 1 :
410+ with fitting_volume_paths [0 ].open (mode = "rb" ) as f :
411+ with ZipFile (f ) as volume_layer_zipfile :
412+ if len (volume_layer_zipfile .filelist ) == 0 :
413+ volume_path = None
414+ else :
415+ volume_path = fitting_volume_paths [0 ]
416+ else :
417+ layers_with_not_found_location .append (volume )
418+ volume_path = None
419+
418420 segments = {}
419421 if volume .segments is not None :
420422 for segment in volume .segments :
@@ -435,8 +437,32 @@ def _load_from_zip(cls, content: Union[str, PathLike, BinaryIO]) -> "Annotation"
435437 assert len (set (i .id for i in volume_layers )) == len (
436438 volume_layers
437439 ), "Some volume layers have the same id, this is not allowed."
438- annotation ._volume_layers = volume_layers
439- return annotation
440+ if len (layers_without_location ) > 0 :
441+ warnings .warn (
442+ "Omitting the volume layer annotation data for layers "
443+ + f"{ [v .name or v .id for v in layers_without_location ]} , "
444+ + "as their location is not referenced in the NML."
445+ )
446+ if len (layers_with_not_found_location ) > 0 :
447+ warnings .warn (
448+ "Omitting the volume layer annotation data for layers "
449+ + f"{ [v .name or v .id for v in layers_without_location ]} , "
450+ + f"as their referenced files { [v .location for v in layers_without_location ]} "
451+ + "cannot be found."
452+ )
453+ return volume_layers
454+
455+ @classmethod
456+ def _load_from_zip (cls , content : Union [str , PathLike , BinaryIO ]) -> "Annotation" :
457+ zipfile = ZipFile (content )
458+ paths = [ZipPath (zipfile , i .filename ) for i in zipfile .filelist ]
459+ nml_paths = [i for i in paths if i .suffix == ".nml" ]
460+ assert len (nml_paths ) > 0 , "Couldn't find an nml file in the supplied zip-file."
461+ assert (
462+ len (nml_paths ) == 1
463+ ), f"There must be exactly one nml file in the zip-file, buf found { len (nml_paths )} ."
464+ with nml_paths [0 ].open (mode = "rb" ) as f :
465+ return cls ._load_from_nml (nml_paths [0 ].stem , f , possible_volume_paths = paths )
440466
441467 def save (self , path : Union [str , PathLike ]) -> None :
442468 """
@@ -716,7 +742,7 @@ def export_volume_layer_to_dataset(
716742
717743 assert (
718744 volume_zip_path is not None
719- ), "The selected volume layer is empty and cannot be exported."
745+ ), "The selected volume layer data is not available and cannot be exported."
720746
721747 with volume_zip_path .open (mode = "rb" ) as f :
722748 data_zip = ZipFile (f )
@@ -739,8 +765,11 @@ def export_volume_layer_to_dataset(
739765
740766 if largest_segment_id is None :
741767 max_value = max (
742- view .read ().max ()
743- for view in best_mag_view .get_views_on_disk (read_only = True )
768+ (
769+ view .read ().max ()
770+ for view in best_mag_view .get_views_on_disk (read_only = True )
771+ ),
772+ default = 0 ,
744773 )
745774 layer .largest_segment_id = int (max_value )
746775 else :
0 commit comments