Closed spigo900 closed 2 years ago
I've been going through the semantic_0.png
image files and manually adding a mask of a contrasting color to distinct touching objects. I've attached the dataset, with the fixed files, to this comment.
multi_obj_stego_with_fixes.zip
For posterity:
I created this dataset by combining several images from the internet in Google Drawings, then downloading the folder. The images were first automatically converted to JPEG by the Google Drive downloader, then I used a Python script to convert those JPEG's back to PNG's of the correct dimensions. I then used GIMP (specifically, the intelligent scissors tool) to edit the masks in the semantic_0.png
files, and export the result to the same directory, naming the new file semantic_0_fixed.png
.
^This dataset is what will be the input to the preprocessing system, so I thought this comment thread would be a good place to story a copy.
So to implement this fix there are a few things in our way:
Stroke_Extraction
.
process_masks_independently
that controls the logic described in this issue.independent_masks_dir
parameter to Stroke_Extraction
. This is where the intermediate "single, separated-out mask" images should be saved. This is required (asserted?) when and only when process_masks_independently
is passed.singlefile_stroke_extraction
The API image tester is a nice-to-fix but we don't particularly need it.(0, 0, 0)
because we are using that color for the background.cv2
(see Snippet 1).segmentation_path
argument added, in particular stroke_extraction_from_matlab()
, and upstream of that remove_strokes()
.get_strokes()
needs to be refactored.
self.path
. The method calls at the top all assume that. It might make sense to refactor those out first: Create a new method, say get_strokes_for_file(segmentation_path)
, and move those top 3 lines of method calls into that one.get_strokes()
processes. That makes the serialization logic cleaner, I think. That is, return a dataclass ExtractionResult
containing num_objs
, reduced_strokes
, stroke_obj_ids
, adj
, and colors
. Then when we are in one-file-per-mask mode, we just have to glue together the single-file results in get_strokes()
.segmentation_img = cv2.imread(segmentation_img)
unique = np.unique(segmentation_img)
# Don't consider black background color if present
unique_to_process = unique[unique != np.zeros((1, 1, 3))]
# This is an array holding the non-background boolean masks as reconstituted from the image. Shape should be: (n_masks, width, height).
masks = np.all(segmentation_image[None, ...] == unique[:, None, None, :], axis=3)
for i in range(len(masks.shape[0])):
mask = masks[i, :, :].squeeze(0)
cv2.imwrite(str(self.independent_masks_dir / f"separated_{os.path.basename(self.path)}_{i}.png"))
Maybe I'm misunderstanding, but would it make more sense to make to put the independent masks for each situation in a sub-directory of the situation directory? E.g. the masks from situation_0/semantic_0.png
would go in the directory situation_0/independent_masks
as situation_0/independent_masks/mask_0.png
, situation_0/independent_masks/mask_1.png
, etc.
Also @spigo900 in this line:
cv2.imwrite(str(self.independent_masks_dir / f"separated_{os.path.basename(self.path)}_{i}.png"))
I believe you meant to include the mask
image, like so:
cv2.imwrite(str(self.independent_masks_dir / f"separated_{os.path.basename(self.path)}_{i}.png"), mask)
correct?
@boyleconnor Putting the independent masks in a subdirectory is also fine yes. I'm inclined not to nest things more than we already do, so I didn't. And yes, the imwrite
call should save an image. You'll want to save segmentation_image[mask]
rather than mask
because mask
is just booleans so I'm not sure imwrite knows how to save it.
@spigo900 if you don't want to nest directory's more I would not mind accommodating that, but I'm having a hard time visualizing what the directory structure would look like. Would there just be one directory in the curriculum root that would hold all of the independent masks? Or something else?
@boyleconnor I'm talking about putting the independent masks in each situation dir. So there's situation_0/separated_semantic_0_0.png
, situation_0/separated_semantic_0_1.png
, etc.
@spigo900 what is independent_masks_dir
for, then?
@boyleconnor It's there because we don't currently pass the situation dir to Stroke_Extraction
. We could call it situation_dir
but currently the stroke extraction class isn't aware of situation dirs so I'm inclined not to reference them.
@spigo900 ah, I see now. How about I get rid of the features_save_path
parameter (from Stroke_Extraction.__init__()
) and add a new parameter situation_dir
?
@boyleconnor I'm inclined to just add another parameter for consistency's sake, but the class already has a lot of parameters, so I'm okay with the solution of replacing features_save_path
. For the new parameter name I'd slightly prefer output_dir
over situation_dir
to make it clearer we will save things there.
Because of how our stroke extraction process works (by edge detection), touching objects in an image are currently ~guaranteed to have their strokes mingle and get treated as one mega-object. That means we cannot successfully recognize touching relationships (a specific type of the spatial relation features we are currently investigating) without addressing this problem.
To fix that, one simple hack is to separate out each unique mask in the segmentation image and treat it as its own input to the stroke extraction code (feed into Matlab and process results). We then concatenate the outputs for each such unique mask. This ensures that objects that are distinct in the segmentation image result in distinct stroke extraction objects. It also runs into some problems, namely:
This should be a flag because such behavior is not always appropriate. It's not appropriate when training the objects GNN with e.g. STEGO, which may produce multiple masks for just one object. It is probably counterproductive to train the GNN to classify "each weird shape STEGO recognized in the surface of a ball" as being "ball". For similar reasons it's also counterproductive to separate on each mask when using the color-refined segmentation, unless we retain the original instance segmentation and use that for separating out parts of the combined mask image. But that's complicated.