Skip to content

Vis fuse2 #71

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

Open
wants to merge 32 commits into
base: fuse2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6cb1d5b
added itai visualization
Apr 18, 2022
8f8f165
removed files
Apr 18, 2022
7e34332
simplify code
Apr 18, 2022
ea9985f
represent polygon
Apr 18, 2022
ffc8cd2
convert RLE to map type
Apr 25, 2022
2a5cf1e
updated test
Apr 25, 2022
c3482c8
update
Apr 25, 2022
7b37c98
added save to nifti
Apr 27, 2022
4eac47f
added SaveVisual class
Apr 28, 2022
d00c1ef
moved test to jupyter
Apr 28, 2022
53b0e84
added run files
Apr 28, 2022
694b0ba
Delete .nfs00000000013866a0000003dc
itaijj Apr 28, 2022
fbf16f6
Delete .nfs00000000013866a3000003dd
itaijj Apr 28, 2022
c03d41b
updated jupyter
Apr 28, 2022
655519d
Merge branch 'itai_visualization' of https://github.com/IBM/fuse-med-…
Apr 28, 2022
351eb24
showing files
Apr 28, 2022
2561ce3
visualizer
May 2, 2022
80bd89c
Merge branch 'fuse2' into vis_fuse2
mosheraboh May 17, 2022
03a33bc
fixed conflicts
May 25, 2022
2b53c78
fixed PR comments on code
May 25, 2022
cf8d3c6
jupyter update
May 25, 2022
afa3425
Update README.md
itaijj May 26, 2022
823efde
Merge pull request #93 from IBM/itaijj-patch-1
itaijj May 26, 2022
0996384
Update README.md
itaijj May 26, 2022
01a2f87
Update README.md
itaijj May 26, 2022
5bffcd6
Update README.md
itaijj May 26, 2022
e5d219f
change file names
May 26, 2022
b85454f
Merge branch 'vis_fuse2' of https://github.com/IBM/fuse-med-ml into v…
May 26, 2022
d4fc12d
Update README.md
itaijj May 30, 2022
f57cfa3
Update README.md
itaijj May 30, 2022
416b019
removed type detector usage
May 31, 2022
8b5fdea
removed type detrctor
May 31, 2022
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
207 changes: 107 additions & 100 deletions fuse/data/ops/ops_visprobe.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,67 @@
from typing import Callable, Dict, List, Optional, OrderedDict, Sequence, Tuple, Union
import copy
from typing import List, Optional, Union , Dict , Any , Callable
import enum
import numpy as np

from pycocotools import mask as maskUtils
from fuse.utils.ndict import NDict
from fuse.data.visualizer.visualizer_base import VisualizerBase
from fuseimg.utils.visualization.visualizer_base import VisualizerBase
from fuseimg.utils.typing.key_types_imaging import DataTypeImaging
Copy link
Collaborator

Choose a reason for hiding this comment

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

you can't import fuseimg from here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I did not use, just forgot to remove the import

from torch import Tensor
from .op_base import OpBase
from fuse.data.key_types import TypeDetectorBase

class VisFlag(enum.IntFlag):
COLLECT = 1 #save current state for future comparison
SHOW_CURRENT = 2 #show current state
SHOW_COLLECTED = 4 #show comparison of all previuosly collected states
CLEAR = 8 #clear all collected states until this point in the pipeline
ONLINE = 16 #show operations will prompt the user with the releveant plot
OFFLINE = 32 #show operations will write to disk (using the caching mechanism) the relevant info (state or states for comparison)
FORWARD = 64 #visualization operation will be activated on forward pipeline execution flow
REVERSE = 128 #visualization operation will be activated on reverse pipeline execution flow
SHOW_ALL_COLLECTED = 256 #show comparison of all previuosly collected states
COLLECT = 1 #save current state for future comparison
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we don't support comparison yet, I would comment out those flags as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think we have same meaning for comparison ( btw Daniel wrote it )
here it mean you show it as a collection of images, the comparison is always manual.

VISUALIZE_CURRENT = 2 #show current state
VISUALIZE_COLLECTED = 4 #show comparison of all previuosly collected states
CLEAR = 8 #clear all collected states until this point in the pipeline
ONLINE = 16 #show operations will prompt the user with the releveant plot
# REVERSE = 32 #visualization operation will be activated on reverse pipeline execution flow


def convert_uncompressed_RLE_COCO_type(element : Dict ,height : int ,width: int)-> np.ndarray:
"""
converts uncompressed RLE to COCO default type ( compressed RLE)
:param element: input in uncompressed Run Length Encoding (RLE - https://en.wikipedia.org/wiki/Run-length_encoding)
saved in map object example : {"size": [333, 500],"counts": [26454, 2, 651, 3, 13, 1]}
counts first element is how many bits are not the object, then how many bits are the object and so on
:param height: original image height in pixels
:param width: original image width in pixels
:return output mask
"""
p = maskUtils.frPyObjects(element, height, width)
return p

def convert_compressed_RLE_COCO_type(element : list ,height : int ,width: int)-> np.ndarray:
"""
converts polygon to COCO default type ( compressed RLE)
:param element: polygon - array of X,Y coordinates saves as X.Y , example: [[486.34, 239.01, 477.88, 244.78]]
:param height: original image height in pixels
:param width: original image width in pixels
:return output mask
"""
rles = maskUtils.frPyObjects(element, height, width)
p = maskUtils.merge(rles)
p = maskUtils.decode(p)
return p



def convert_COCO_to_mask(elements : Any,height : int ,width: int, segmentation_type : DataTypeImaging )-> Dict:
"""
converts COCO type to mask
:param elements: input in any COCO format
:param height: original image height in pixels
:param width: original image width in pixels
:param segmentation_type: DataTypeImaging
:return output mask
"""
if segmentation_type == DataTypeImaging.UCRLE:
elements = [convert_uncompressed_RLE_COCO_type(element,height,width) for element in elements]
elif segmentation_type == DataTypeImaging.CRLE:
elements = [convert_compressed_RLE_COCO_type(element,height,width) for element in elements]
return elements

class VisProbe(OpBase):
"""
Handle visualization, saves, shows and compares the sample with respect to the current state inside a pipeline
Expand All @@ -26,44 +70,50 @@ class VisProbe(OpBase):

Important notes:
- running in a cached environment is dangerous and is prohibited
- this Operation is not thread safe ans so multithreading is also discouraged
- this Operation is not thread safe and so multithreading is also discouraged

"
"""

def __init__(self,flags: VisFlag,
keys: Union[List, dict] ,
type_detector: TypeDetectorBase,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's talk about explicit approach instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think VisOp should not be used with dataset at all,
only when you call the pipeline on specific example, so we don't have multithreading issues

id_filter: Union[None, List] = None,
coco_converter : Callable = None,
Copy link
Collaborator

Choose a reason for hiding this comment

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

coco is too specific, we might want to use different converters. You can just call it convertor

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

what I mean is that a user must use COCO format for segmentations or have a function that converts to COCO format.
that's what we talked about before..
we don't want to support more than 1 format for each seg type

Copy link
Collaborator

Choose a reason for hiding this comment

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

It's maybe true for imaging, but about the other domains?
Or if we decide to support more options in the future?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

coco converter moved to imaging visualizer

name : str ="",
sample_id: Union[None, List] = None,
visualizer: VisualizerBase = None,
cache_path: str = "~/"):
output_path: str = "~/"):
"""
:param flags: operation flags (or possible concatentation of flags using IntFlag), details:
COLLECT - save current state for future comparison
SHOW_CURRENT - show current state
SHOW_COllected - show comparison of all previuosly collected states
VISUALIZE_CURRENT - visualize current state
VISUALIZE_COLLECTED - visualize comparison of all previuosly collected states
CLEAR - clear all collected states until this point in the pipeline
ONLINE - show operations will prompt the user with the releveant plot
OFFLINE - show operations will write to disk (using the caching mechanism) the relevant info (state or states for comparison)
ONLINE - visualize operations will prompt the user with the releveant plot
OFFLINE - visualize operations will write to disk (using the caching mechanism) the relevant info (state or states for comparison)
FORWARD - visualization operation will be activated on forward pipeline execution flow
REVERSE - visualization operation will be activated on reverse pipeline execution flow
:param keys: for which sample keys to handle visualization, also can be grouped in a dictionary
:param id_filter: for which sample id's to be activated, if None, active for all samples
:param type_detector : class used to identify objects from the keys
:param coco_converter : function that convert the input format to COCO type format
Copy link
Collaborator

Choose a reason for hiding this comment

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

Didn't we agree that coco_convertor should not be used in the general (not necessarily images) case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, I just forgot to remove it in the param documentation

:param name : image name to represnt, if not given a number will be shown.
:param sample_id: for which sample id's to be activated, if None, active for all samples
:param visualizer: the actual visualization handler, depands on domain and use case, should implement Visualizer Base
:param cache_path: root dir to save the visualization outputs in offline mode
:param output_path: root dir to save the visualization outputs in offline mode

few issues to be aware of, detailed in github issues regarding static cached pipeline and multiprocessing
note - if both forward and reverse are on, then by default, on forward we do collect and on reverse we do show_collected to
compare reverse operations
for each domain we inherit for VisProbe like ImagingVisProbe,...
"""
super().__init__()
self._id_filter = id_filter
self._sample_id = sample_id
self._keys = keys
self._flags = flags
self._cacher = None
self._coco_converter = coco_converter
self._name = name
self._collected_prefix = "data.$vis"
self._cache_path = cache_path
self._output_path = output_path
self._visualizer = visualizer
self._type_detector = type_detector

Expand All @@ -76,111 +126,68 @@ def _extract_collected(self, sample_dict: NDict):
res.append(vdata)
return res

def _extract_data(self, sample_dict: NDict, keys, op_id):
if type(keys) is list:
# infer keys groups
keys.sort()
first_type = self._type_detector.get_type(sample_dict, keys[0])
num_of_groups = len([self._type_detector.get_type(sample_dict, k) for k in keys if self._type_detector.get_type(sample_dict, k) == first_type])
keys_per_group = len(keys) // num_of_groups
keys = {f"group{i}": keys[i:i + keys_per_group] for i in range(0, len(keys), keys_per_group)}

def _extract_data(self, sample_dict: NDict, keys : List , name : str):
res = NDict()
for group_id, group_keys in keys.items():
for key in group_keys:
prekey = f'groups.{group_id}.{key.replace(".", "_")}'
res[f'{prekey}.value'] = sample_dict[key]
res[f'{prekey}.type'] = self._type_detector.get_type(sample_dict, key)
res['$step_id'] = op_id
for key in keys:
prekey = key.replace(".", "_")
if isinstance(sample_dict[key] , Tensor ):
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you please try to use copy.deepcopy() instead.
I think that both tensors and numparrays support this operation (and also more types such as lists, dicts, ...)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

res[f'{prekey}.value'] = sample_dict[key].clone()
else :
res[f'{prekey}.value'] = sample_dict[key].copy()
res[f'{prekey}.type'] = self._type_detector.get_type(sample_dict, key)

if self._coco_converter != None :
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do you convert here? How do you know it's imaging?
VisProbe should be generic for all domains and specifically for imaging.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

moved it to fuseimg visualizer2D class

res[f'{prekey}.value'] = self._coco_converter(res[f'{prekey}.value'],res[f'{prekey}.type'])
if res[f'{prekey}.type'] in [DataTypeImaging.CRLE, DataTypeImaging.UCRLE ]:
res[f'{prekey}.value'] = convert_COCO_to_mask(res[f'{prekey}.value'],sample_dict['height'],sample_dict['width'],res[f'{prekey}.type'] )
res[f'{prekey}.type'] = DataTypeImaging.SEG
res[f'{prekey}.name'] = name
return res


def _save(self, vis_data: Union[List, dict]):
# use caching to save all relevant vis_data
print("saving vis_data", vis_data)

def _handle_flags(self, flow, sample_dict: NDict, op_id: Optional[str]):
def _handle_flags(self, sample_dict: NDict, op_id: Optional[str]):
"""
See super class
"""
# sample was filtered out by its id
if self._id_filter and self.get_idx(sample_dict) not in self._id_filter:
if self._sample_id and self.get_idx(sample_dict) not in self._sample_id:
return None
if flow not in self._flags:
return None

# grouped key dictionary with the following structure:
#vis_data = {"cc_group":
# {
# "key1": {
# "value": ndarray,
# "type": DataType.Image,
# "op_id": "test1"}
# "key2": {
# "value": ndarray,
# "type": DataType.BBox,
# "op_id": "test1"}
# },
# "mlo_goup":
# {
# "key3": {
# "value": ndarray,
# "type": DataType.Image,
# "op_id": "test1"}
# "key4": {
# "value": ndarray,
# "type": DataType.BBox,
# "op_id": "test1"}
# },
# }
vis_data = self._extract_data(sample_dict, self._keys, op_id)
both_fr = (VisFlag.REVERSE | VisFlag.FORWARD) in self._flags
dir_forward = flow == VisFlag.FORWARD
dir_reverse = flow == VisFlag.REVERSE
any_show_collected = VisFlag.SHOW_ALL_COLLECTED|VisFlag.SHOW_COLLECTED

if VisFlag.COLLECT in self._flags or (dir_forward and both_fr):
vis_data = self._extract_data(sample_dict, self._keys ,self._name)
name_prefix=""
if "name" in sample_dict.to_dict().keys():
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you please remind me what is this name and where we set it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it's something I added , you can see in the example.
I wanted to have an option to give specific identifier to each image

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can pass the name to OpProbe constructor instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it already have name there, but it's a step name.
I need also a sample unique name but i'l use id

name_prefix = sample_dict["name"]
name_prefix += "."+op_id
if VisFlag.COLLECT in self._flags:
if not self._collected_prefix in sample_dict:
sample_dict[self._collected_prefix] = []
sample_dict[self._collected_prefix].append(vis_data)


if VisFlag.SHOW_CURRENT in self._flags:
if VisFlag.VISUALIZE_CURRENT in self._flags:
if VisFlag.ONLINE in self._flags:
self._visualizer.show(vis_data)
if VisFlag.OFFLINE in self._flags:
self._save(vis_data)
else :
self._visualizer.save(vis_data , name_prefix , self._output_path)

if (VisFlag.SHOW_ALL_COLLECTED in self._flags or VisFlag.SHOW_COLLECTED in self._flags) and (
(both_fr and dir_reverse) or not both_fr):
if VisFlag.VISUALIZE_COLLECTED in self._flags:
vis_data = self._extract_collected(sample_dict) + [vis_data]
if both_fr:
if VisFlag.SHOW_COLLECTED in self._flags:
vis_data = vis_data[-2:]
if VisFlag.ONLINE in self._flags:
self._visualizer.show(vis_data)
if VisFlag.OFFLINE in self._flags:
self.save(vis_data)
else :
self._visualizer.save(vis_data , name_prefix, self._output_path)

if VisFlag.CLEAR in self._flags:
sample_dict[self._collected_prefix] = []

if VisFlag.SHOW_COLLECTED in self._flags and both_fr and dir_reverse:
sample_dict[self._collected_prefix].pop()
# TODO - support reverse?
# if VisFlag.SHOW_COLLECTED in self._flags and VisFlag.REVERSE in self._flags:
# sample_dict[self._collected_prefix].pop()

return sample_dict


def __call__(self, sample_dict: NDict, op_id: Optional[str], **kwargs) -> Union[None, dict, List[dict]]:
res = self._handle_flags(VisFlag.FORWARD, sample_dict, op_id)
res = self._handle_flags(sample_dict, op_id)
return res

def reverse(self, sample_dict: NDict, op_id: Optional[str], key_to_reverse: str, key_to_follow: str) -> dict:
"""
See super class
"""
res = self._handle_flags(VisFlag.REVERSE, sample_dict, op_id)
if res is None:
res = sample_dict
return res

Loading