diff --git a/.dockerignore b/.dockerignore index cb0b86430..34e80a172 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,6 +14,38 @@ __pycache__ .vscode Dockerfile -sample-apps/**/model -node_modules .webpack + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +.venv/ +ubuntu_venv/ + +# apps +sample-apps/*/logs +sample-apps/*/train +sample-apps/*/model + +# docs build +docs/build +docs/source/apidocs + +# MONAI-label +*.nii.gz +.DS_Store +*.DS_Store +bin +junit +test-output.xml +studies +tests/data + +# Packages +node_modules +yarn.lock diff --git a/.gitignore b/.gitignore index 69f5d3f66..f7646785a 100644 --- a/.gitignore +++ b/.gitignore @@ -120,6 +120,8 @@ venv/ ENV/ env.bak/ venv.bak/ +.venv/ +ubuntu_venv/ # Spyder project settings .spyderproject @@ -139,12 +141,6 @@ dmypy.json # Pyre type checker .pyre/ -# virtualenv -.venv/ -venv/ -ENV/ -ubuntu_venv/ - # IDE .idea/ .vscode/ @@ -166,7 +162,7 @@ junit test-output.xml studies tests/data -yarn.lock + # Packages node_modules yarn.lock diff --git a/monailabel/client/client.py b/monailabel/client/client.py index 37082fe69..91d172abc 100644 --- a/monailabel/client/client.py +++ b/monailabel/client/client.py @@ -336,6 +336,7 @@ def infer(self, model, image_id, params, label_in=None, file=None, session_id=No fields = {"params": json.dumps(params) if params else "{}"} files = {"label": label_in} if label_in else {} files.update({"file": file} if file and not session_id else {}) + logger.info(f"Files: {files}") status, form, files, _ = MONAILabelUtils.http_multipart( "POST", self._server_url, selector, fields, files, headers=self._headers @@ -584,6 +585,9 @@ def send_response(conn, content_type="application/json"): @staticmethod def save_result(files, tmpdir): + if not files: + return None + for name in files: data = files[name] result_file = os.path.join(tmpdir, name) diff --git a/monailabel/endpoints/infer.py b/monailabel/endpoints/infer.py index cecb4d7dd..abf2cc4cc 100644 --- a/monailabel/endpoints/infer.py +++ b/monailabel/endpoints/infer.py @@ -91,10 +91,8 @@ def send_response(datastore, result, output, background_tasks): if output == "json": return res_json - m_type = get_mime_type(res_img) - if output == "image": - return FileResponse(res_img, media_type=m_type, filename=os.path.basename(res_img)) + return FileResponse(res_img, media_type=get_mime_type(res_img), filename=os.path.basename(res_img)) if output == "dicom_seg": res_dicom_seg = result.get("dicom_seg") @@ -106,7 +104,7 @@ def send_response(datastore, result, output, background_tasks): res_fields = dict() res_fields["params"] = (None, json.dumps(res_json), "application/json") if res_img and os.path.exists(res_img): - res_fields["image"] = (os.path.basename(res_img), open(res_img, "rb"), m_type) + res_fields["image"] = (os.path.basename(res_img), open(res_img, "rb"), get_mime_type(res_img)) else: logger.info(f"Return only Result Json as Result Image is not available: {res_img}") return res_json diff --git a/monailabel/interfaces/tasks/infer_v2.py b/monailabel/interfaces/tasks/infer_v2.py index 234452822..f87b8db7b 100644 --- a/monailabel/interfaces/tasks/infer_v2.py +++ b/monailabel/interfaces/tasks/infer_v2.py @@ -92,5 +92,5 @@ def is_valid(self) -> bool: pass @abstractmethod - def __call__(self, request) -> Union[Dict, Tuple[str, Dict[str, Any]]]: + def __call__(self, request) -> Tuple[Union[str, None], Dict]: pass diff --git a/monailabel/tasks/infer/basic_infer.py b/monailabel/tasks/infer/basic_infer.py index 930bf6b85..7fef2d644 100644 --- a/monailabel/tasks/infer/basic_infer.py +++ b/monailabel/tasks/infer/basic_infer.py @@ -27,7 +27,7 @@ from monailabel.interfaces.utils.transform import dump_data, run_transforms from monailabel.transform.cache import CacheTransformDatad from monailabel.transform.writer import ClassificationWriter, DetectionWriter, Writer -from monailabel.utils.others.generic import device_list, device_map, name_to_device +from monailabel.utils.others.generic import device_list, device_map, name_to_device, strtobool logger = logging.getLogger(__name__) @@ -253,7 +253,7 @@ def detector(self, data=None) -> Optional[Callable]: def __call__( self, request, callbacks: Union[Dict[CallBackTypes, Any], None] = None - ) -> Union[Dict, Tuple[str, Dict[str, Any]]]: + ) -> Tuple[Union[str, None], Dict]: """ It provides basic implementation to run the following in order - Run Pre Transforms @@ -322,8 +322,8 @@ def __call__( data = callback_run_post_transforms(data) latency_post = time.time() - start - if self.skip_writer: - return dict(data) + if self.skip_writer or strtobool(data.get("skip_writer")): + return None, dict(data) start = time.time() result_file_name, result_json = self.writer(data) diff --git a/monailabel/tasks/infer/bundle.py b/monailabel/tasks/infer/bundle.py index 120426bd5..3023f0fc5 100644 --- a/monailabel/tasks/infer/bundle.py +++ b/monailabel/tasks/infer/bundle.py @@ -137,15 +137,14 @@ def __init__( # labels = ({v.lower(): int(k) for k, v in pred.get("channel_def", {}).items() if v.lower() != "background"}) labels = {} + type = self._get_type(os.path.basename(path), type) for k, v in pred.get("channel_def", {}).items(): - if (not type.lower() == "deepedit") and (v.lower() != "background"): - labels[v.lower()] = int(k) - else: + logger.info(f"Model: {os.path.basename(path)}; Type: {type}; Label: {v} => {k}") + if v.lower() != "background" or type.lower() == "deepedit": labels[v.lower()] = int(k) description = metadata.get("description") spatial_shape = image.get("spatial_shape") dimension = len(spatial_shape) if spatial_shape else 3 - type = self._get_type(os.path.basename(path), type) # if detection task, set post restore to False by default. self.add_post_restore = False if type == "detection" else add_post_restore @@ -273,28 +272,21 @@ def post_transforms(self, data=None) -> Sequence[Callable]: return post def _get_type(self, name, type): + if type: + return type + name = name.lower() if name else "" - return ( - ( - InferType.DEEPEDIT - if "deepedit" in name - else ( - InferType.DEEPGROW - if "deepgrow" in name - else ( - InferType.DETECTION - if "detection" in name - else ( - InferType.SEGMENTATION - if "segmentation" in name - else InferType.CLASSIFICATION if "classification" in name else InferType.SEGMENTATION - ) - ) - ) - ) - if not type - else type - ) + if "deepedit" in name: + return InferType.DEEPEDIT + if "deepgrow" in name: + return InferType.DEEPGROW + if "detection" in name: + return InferType.DETECTION + if "segmentation" in name: + return InferType.SEGMENTATION + if "classification" in name: + return InferType.CLASSIFICATION + return InferType.SEGMENTATION def _filter_transforms(self, transforms, filters): if not filters or not transforms: diff --git a/monailabel/tasks/scoring/epistemic_v2.py b/monailabel/tasks/scoring/epistemic_v2.py index ee1849033..c040b844b 100644 --- a/monailabel/tasks/scoring/epistemic_v2.py +++ b/monailabel/tasks/scoring/epistemic_v2.py @@ -173,7 +173,7 @@ def run_scoring(self, image_id, simulation_size, model_ts, datastore): accum_unl_outputs = [] for i in range(simulation_size): - data = self.infer_task(request=request) + _, data = self.infer_task(request=request) pred = data[self.infer_task.output_label_key] if isinstance(data, dict) else None if pred is not None: logger.debug(f"EPISTEMIC:: {image_id} => {i} => pred: {pred.shape}; sum: {np.sum(pred)}") diff --git a/monailabel/transform/post.py b/monailabel/transform/post.py index 615040474..cf4abdf96 100644 --- a/monailabel/transform/post.py +++ b/monailabel/transform/post.py @@ -129,7 +129,7 @@ def __call__(self, data): spatial_size = spatial_shape[-len(current_size) :] # Undo Spacing - if torch.any(torch.Tensor(np.not_equal(current_size, spatial_size))): + if np.any(np.not_equal(current_size, spatial_size)): resizer = Resize(spatial_size=spatial_size, mode=self.mode[idx]) result = resizer(result, mode=self.mode[idx], align_corners=self.align_corners[idx]) diff --git a/monailabel/utils/others/generic.py b/monailabel/utils/others/generic.py index 0d741598e..a26edec63 100644 --- a/monailabel/utils/others/generic.py +++ b/monailabel/utils/others/generic.py @@ -241,8 +241,8 @@ def _list_files(d, ext): ] -def strtobool(str): - return bool(distutils.util.strtobool(str)) +def strtobool(s): + return False if s is None else s if isinstance(s, bool) else bool(distutils.util.strtobool(s)) def is_openslide_supported(name): @@ -336,7 +336,7 @@ def get_bundle_models(app_dir, conf, conf_key="models"): zoo_source = conf.get("zoo_source", settings.MONAI_ZOO_SOURCE) models = conf.get(conf_key) - models = models.split(",") + models = models.split(",") if models else [] models = [m.strip() for m in models] if zoo_source == "monaihosting": # if in github env, access model zoo diff --git a/monailabel/utils/others/pathology.py b/monailabel/utils/others/pathology.py index 78ecb922d..03e6317eb 100644 --- a/monailabel/utils/others/pathology.py +++ b/monailabel/utils/others/pathology.py @@ -129,7 +129,7 @@ def create_asap_annotations_xml(json_data, loglevel="INFO"): label = element["label"] color = to_hex(color_map.get(label)) - logger.info(f"Adding Contours for label: {label}; color: {color}; color_map: {color_map}") + logger.debug(f"Adding Contours for label: {label}; color: {color}; color_map: {color_map}") labels[label] = color contours = element["contours"] diff --git a/sample-apps/endoscopy/lib/infers/deepedit.py b/sample-apps/endoscopy/lib/infers/deepedit.py index 56c732a7f..ca40e18d6 100644 --- a/sample-apps/endoscopy/lib/infers/deepedit.py +++ b/sample-apps/endoscopy/lib/infers/deepedit.py @@ -75,16 +75,21 @@ def inferer(self, data=None) -> Inferer: return SimpleInferer() def post_transforms(self, data=None) -> Sequence[Callable]: - return [ + t = [ EnsureTyped(keys="pred", device=data.get("device") if data else None), Activationsd(keys="pred", sigmoid=True), AsDiscreted(keys="pred", threshold=0.5), Restored(keys="pred", ref_image="image"), SqueezeDimd(keys="pred"), ToNumpyd(keys="pred", dtype=np.uint8), - FindContoursd(keys="pred", labels=self.labels, key_foreground_points="foreground"), ] + if data and data.get("output") == "mask": + return t + t.append(FindContoursd(keys="pred", labels=self.labels, key_foreground_points="foreground")) + return t def writer(self, data, extension=None, dtype=None): + if data and data.get("output") == "mask": + return super().writer(data, extension=extension, dtype=dtype) writer = PolygonWriter(label=self.output_label_key, json=self.output_json_key) return writer(data) diff --git a/sample-apps/radiology/main.py b/sample-apps/radiology/main.py index 5d5ab348b..abc3aaaa7 100644 --- a/sample-apps/radiology/main.py +++ b/sample-apps/radiology/main.py @@ -27,7 +27,6 @@ from monailabel.interfaces.tasks.scoring import ScoringMethod from monailabel.interfaces.tasks.strategy import Strategy from monailabel.interfaces.tasks.train import TrainTask -from monailabel.scribbles.infer import GMMBasedGraphCut, HistogramBasedGraphCut from monailabel.tasks.activelearning.first import First from monailabel.tasks.activelearning.random import Random @@ -141,6 +140,8 @@ def init_infers(self) -> Dict[str, InferTask]: # Scribbles ################################################# if self.scribbles: + from monailabel.scribbles.infer import GMMBasedGraphCut, HistogramBasedGraphCut + infers.update( { "Histogram+GraphCut": HistogramBasedGraphCut(