Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ANN-760][external] nifti import to multi slot item #549

Merged
merged 31 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e7a3d49
removed pixdim and added original affine logic
tomvars Dec 19, 2022
ad6cadf
removed pixdims
tomvars Dec 20, 2022
3ed9827
removed print
tomvars Dec 20, 2022
f60c1fb
Merge branch 'master' into ann-572-non-ras-logic-darwin-py
tomvars Feb 21, 2023
3be5fa0
fixed issue locally, haven't done full testing
tomvars Feb 23, 2023
8ef7209
working version
tomvars Feb 28, 2023
0115efc
Merge branch 'master' into ann-760-nifti-import-to-multi-slot-item
tomvars Mar 1, 2023
f5d5df8
PR changes
tomvars Mar 1, 2023
bf8fef3
merged upstream changes and resolved conflict with importer.py and cl…
tomvars Mar 1, 2023
126a60a
updated darwin-py string to reflect new pip install mechanism
tomvars Mar 2, 2023
e136f9b
fix for handle_video bug
tomvars Mar 3, 2023
bf33103
Merge branch 'master' into ann-760-nifti-import-to-multi-slot-item
tomvars Mar 3, 2023
b08d384
removed print statement
tomvars Mar 3, 2023
c44918b
merged master and resolved conflicts
tomvars Apr 3, 2023
af1f954
settled merge conflict
tomvars May 2, 2023
b952791
fixed bad merge
tomvars May 2, 2023
b5da729
introduced slot_names and is_mpr flag allowing users to upload a nift…
tomvars May 3, 2023
c19733e
fixed for anisotropic files uploaded
tomvars May 4, 2023
55e8fe9
added pixdim logic
tomvars May 4, 2023
fcfd6fb
changes made to nifty multi-slot
May 4, 2023
fb4232e
removed image mode for nifti imports
tomvars May 4, 2023
b2aa7cc
added a multi-slot test
tomvars May 4, 2023
bb22521
fixed import that was commented out
tomvars May 4, 2023
066ae87
Merge branch 'master' into ann-760-nifti-import-to-multi-slot-item
tomvars May 4, 2023
4851e19
changed data.zip
tomvars May 4, 2023
cf1e0dc
points to the right file
tomvars May 4, 2023
7d0efa2
changes made to add new error unit test
May 5, 2023
66de122
changes made to add new error unit test
May 5, 2023
122ad05
removed name to local directory
tomvars May 6, 2023
8aa8fd6
nathan's requested change
tomvars May 9, 2023
3544c6e
reverted nathan's comment
tomvars May 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion darwin/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def main() -> None:
fd.write("Darwin CLI error log")
fd.write(f"Version: {__version__}")
fd.write(f"OS: {platform.platform()}")
fd.write(f"Command: {dumps(args, check_circular=True)}")
fd.write(f"Command: {dumps(vars(args), check_circular=True)}")
tomvars marked this conversation as resolved.
Show resolved Hide resolved
fd.write(f"Error: {dumps(e, check_circular=True)}")
fd.close()

Expand Down
8 changes: 3 additions & 5 deletions darwin/exporter/formats/nifti.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ast
import json as native_json
from asyncore import loop
from pathlib import Path
Expand Down Expand Up @@ -161,9 +162,6 @@ def get_view_idx(frame_idx, groups):


def get_view_idx_from_slot_name(slot_name):
# if mpr:
# #do this correct treatment volumetrically.
# pass
slot_names = {"0.1": 0, "0.2": 1, "0.3": 2}
slot_names.get(slot_name, 0)
return slot_names.get(slot_name, 0)
Expand All @@ -176,7 +174,7 @@ def process_metadata(metadata):
original_affine = process_affine(metadata.get("original_affine"))
# If the original affine is in the medical payload of metadata then use it
if isinstance(pixdim, str):
pixdim = eval(pixdim)
pixdim = ast.literal_eval(pixdim)
tomvars marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(pixdim, tuple) or isinstance(pixdim, list):
if len(pixdim) == 4:
pixdim = pixdim[1:]
Expand All @@ -195,7 +193,7 @@ def process_metadata(metadata):

def process_affine(affine):
if isinstance(affine, str):
affine = np.squeeze(np.array([eval(l) for l in affine.split("\n")]))
affine = np.squeeze(np.array([ast.literal_eval(l) for l in affine.split("\n")]))
elif isinstance(affine, list):
affine = np.array(affine).astype(np.float)
else:
Expand Down
19 changes: 11 additions & 8 deletions darwin/importer/formats/nifti.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
import warnings
import zipfile
from collections import OrderedDict, defaultdict
Expand All @@ -17,7 +18,7 @@
in order to import with using nifti format
"""
console.print(import_fail_string)
exit()
sys.exit(1)
import numpy as np
from jsonschema import validate
from upolygon import find_contours
Expand Down Expand Up @@ -69,12 +70,14 @@ def parse_path(path: Path) -> Optional[List[dt.AnnotationFile]]:
path,
class_map=nifti_annotation.get("class_map"),
mode=nifti_annotation.get("mode", "image"),
slot_name=nifti_annotation.get("slot_name"),
)
annotation_files.append(annotation_file)
return annotation_files


def _parse_nifti(nifti_path: Path, filename: Path, json_path: Path, class_map: Dict, mode: str) -> dt.AnnotationFile:
def _parse_nifti(nifti_path: Path, filename: Path, json_path: Path,
class_map: Dict, mode: str, slot_name: Optional[str] = None) -> dt.AnnotationFile:

img: np.ndarray = process_nifti(nib.load(nifti_path))

Expand All @@ -88,7 +91,7 @@ def _parse_nifti(nifti_path: Path, filename: Path, json_path: Path, class_map: D
class_img = np.isin(img, class_idxs).astype(np.uint8)
cc_img, num_labels = cc3d.connected_components(class_img, return_N=True)
for instance_id in range(1, num_labels):
video_annotation = get_video_annotation(cc_img, class_idxs=[instance_id], class_name=class_name)
video_annotation = get_video_annotation(cc_img, class_idxs=[instance_id], class_name=class_name, slot_name=slot_name)
if video_annotation:
video_annotations.append(video_annotation)
elif mode == "image": # For each frame and each class produce a single frame video annotation
Expand All @@ -108,14 +111,14 @@ def _parse_nifti(nifti_path: Path, filename: Path, json_path: Path, class_map: D
keyframes={i: True, i + 1: True},
segments=[[i, i + 1]],
interpolated=False,
slot_names=[],
slot_names=[] if slot_name is None else [slot_name],
)
video_annotations.append(video_annotation)
elif mode == "video": # For each class produce a single video annotation
for class_name, class_idxs in processed_class_map.items():
if class_name == "background":
continue
video_annotation = get_video_annotation(img, class_idxs=class_idxs, class_name=class_name)
video_annotation = get_video_annotation(img, class_idxs=class_idxs, class_name=class_name, slot_name=slot_name)
if video_annotation is None:
continue
video_annotations.append(video_annotation)
Expand All @@ -128,11 +131,11 @@ def _parse_nifti(nifti_path: Path, filename: Path, json_path: Path, class_map: D
remote_path="/",
annotation_classes=annotation_classes,
annotations=video_annotations,
slots=[dt.Slot(name=None, type="dicom", source_files=[{"url": None, "file_name": str(filename)}])],
slots=[dt.Slot(name=slot_name, type="dicom", source_files=[{"url": None, "file_name": str(filename)}])],
)


def get_video_annotation(volume: np.ndarray, class_name: str, class_idxs: List[int]) -> Optional[dt.VideoAnnotation]:
def get_video_annotation(volume: np.ndarray, class_name: str, class_idxs: List[int], slot_name: Optional[str]) -> Optional[dt.VideoAnnotation]:
frame_annotations = OrderedDict()
for i in range(volume.shape[-1]):
slice_mask = volume[:, :, i].astype(np.uint8)
Expand All @@ -156,7 +159,7 @@ def get_video_annotation(volume: np.ndarray, class_name: str, class_idxs: List[i
keyframes={f_id: True for f_id in all_frame_ids},
segments=segments,
interpolated=False,
slot_names=[],
slot_names=[] if slot_name is None else [slot_name],
)
return video_annotation

Expand Down
1 change: 1 addition & 0 deletions darwin/importer/formats/nifti_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"label": {"type": "string"},
"class_map": class_map,
"mode": {"type": "string", "enum": ["image", "video", "instances"]},
"slot_name": {"type": "string"},
},
"required": ["image", "label", "class_map"],
"additionalProperties": False,
Expand Down
12 changes: 8 additions & 4 deletions darwin/importer/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,12 @@ def maybe_console(*args: Union[str, int, float]) -> None:
parsed_files = list(map(importer, tqdm(files) if is_console else files))

maybe_console("Finished.")

if not isinstance(parsed_files, list):
# Sometimes we have a list of lists of AnnotationFile, sometimes we have a list of AnnotationFile
# We flatten the list of lists
if isinstance(parsed_files, list):
if isinstance(parsed_files[0], list):
Copy link
Contributor

@Nathanjp91 Nathanjp91 May 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a required change
if not doing anything else with the inner conditional, can flatten this, python supports conditional short circuiting

if isinstance(parsed_files, list) and isinstance(parsed_files[0], list):
    ...

This won't raise if parsed_files isn't indexable because the first condition will short circuit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change actually causes these tests to fail for some reason:

FAILED tests/darwin/importer/importer_mcpu_test.py::Test_find_and_parse::test_runs_single_threaded_if_use_multi_cpu_false - Asse...
FAILED tests/darwin/importer/importer_mcpu_test.py::Test_find_and_parse::test_uses_mpire_if_use_multi_cpu_true - AssertionError:...

Going to merge without it, can't spend any longer on this ticket.

parsed_files = [item for sublist in parsed_files for item in sublist]
else:
parsed_files = [parsed_files]

return parsed_files
Expand Down Expand Up @@ -467,7 +471,8 @@ def import_annotations(
_warn_unsupported_annotations(files_to_track)
for parsed_file in track(files_to_track):
image_id, default_slot_name = remote_files[parsed_file.full_path]

if parsed_file.slots:
default_slot_name = parsed_file.slots[0].name
_import_annotations(
dataset.client,
image_id,
Expand Down Expand Up @@ -577,7 +582,6 @@ def _import_annotations(
# Insert the default slot name if not available in the import source
if not annotation.slot_names and dataset.version > 1:
annotation.slot_names.extend([default_slot_name])

serialized_annotations.append(
{
"annotation_class_id": annotation_class_id,
Expand Down