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
Segmentation: Chips and Training #337
Conversation
79ab472
to
f86c3af
Compare
|
101c91d
to
cc39673
Compare
start_sync) | ||
|
||
|
||
def numpy_to_png(array: np.ndarray) -> str: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps these are broadly useful enough to pull into a utility package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, will-do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -37,9 +39,17 @@ message ClassificationGeoJSONFile { | |||
optional Options options = 2; | |||
} | |||
|
|||
message SegmentationRasterFile { | |||
optional RasterSource src = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a comment for each of these fields would be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe comments would resolve my confusion, but more generally, I don't understand the notion of source and destination in this PR. Does source correspond to where the ground truth comes from and destination where the predictions go? If so, you should use ground_truth_label_store
for the ground truth and a separate prediction_label_store
for the predictions to follow the convention in the rest of RV.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, will-do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe comments would resolve my confusion, but more generally, I don't understand the notion of source and destination in this PR.
The nomenclature may be poorly chosen.
In the case of rasters, the "source" refers to the given label data (read from the filesystem or some remote location) and the "destination" is where internally generated labeling information is sent (written to the filesystem or some remote location).
In the case of classes, the "source" classes are those of the source rasters; the "destination" classes are those used internally (e.g. "cars" being class of 1
[from the configuration files] instead of class 0xffff00
[as they are in the given labels]).
The use of same two words in slightly different contexts is to emphasize the connection between the two: one translates from classes in the source raster to those in the destination raster by considering source classes versus destination classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Source classes changed from integers to hex numbers, comments added to .proto
file.
} | ||
}, | ||
"src_classes": [ 6, 1 ], | ||
"dst_classes": [ 1, 0 ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class_ids should start at 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing these are indices into the channel dimension. It would be good to clarify that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are not indices, please see the (rewritten) response above. All of these will be well documented.
In this particular case what you see is 0xffff00
-> 110b
-> 6 (0xffff00 is the labeling for a "car" in the source raster) being mapped to 1
(the classmap maps the class 1
to the word "car"
).
The class 0x0000ff
-> 001
-> 1 is being mapped to zero because 0x0000ff
is for buildings and we don't want to consider those.
xmin = window.xmin | ||
ymax = window.ymax | ||
xmax = window.xmax | ||
return np.zeros((self.channels, ymax - ymin, xmax - xmin)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chips are stored with channels in the last dimension elsewhere in RV.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also true of TF.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -17,6 +17,25 @@ message TrainConfig { | |||
optional string export_py = 3 [default="/opt/tf-models/object_detection/export_inference_graph.py"]; | |||
} | |||
|
|||
message SegmentationOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like these parameters should be in a backend config file which is referenced from the backend_config_uri field to be consistent with the rest of RV. This would involve moving the bulk of the fields in SegmentationOptions into a DeepLabBackendConfig proto or similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
args.append('--save_summaries_secs={}'.format( | ||
soptions.save_summaries_secs)) | ||
args.append('--save_summaries_images={}'.format( | ||
soptions.save_summaries_images)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Building up the args seems like a good thing to extract to another function.
5617426
to
4c3f895
Compare
... and other miscellaneous changes.
e8c8587
to
521df6d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got this to run locally, and will test remotely next.
chip_size = options.chip_size | ||
|
||
windows = [] | ||
for i in range(100): # XXX insensitive |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be an option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is in my current branch.
@@ -0,0 +1,38 @@ | |||
#!/usr/bin/env python | |||
|
|||
import os |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had trouble running this in the Docker container, I think due to a Python version mismatch. But, I've realized there's no need to add georeferencing to the label files. We can just use ImageFile
instead of GeoTiffFiles
as the RasterSource
to handle non-georeferenced imagery. Here is the relevant part of the workflow config I used to get it to work:
{
"train_scenes": [
{
"id": "2-10",
"raster_source": {
"geotiff_files": {
"uris": [
"{raw}/isprs-potsdam/4_Ortho_RGBIR/top_potsdam_2_10_RGBIR.tif"
]
}
},
"ground_truth_label_store": {
"segmentation_raster_file": {
"src": {
"image_file": {
"uri": "{raw}/isprs-potsdam/5_Labels_for_participants_no_Boundary/top_potsdam_2_10_label_noBoundary.tif"
}
},
"src_classes": [ "#ffff00", "#0000ff" ],
"dst_classes": [ 1, 0 ]
}
}
}
],
"test_scenes": [
{
"id": "2-11",
"raster_source": {
"geotiff_files": {
"uris": [
"{raw}/isprs-potsdam/4_Ortho_RGBIR/top_potsdam_2_11_RGBIR.tif"
]
}
},
"ground_truth_label_store": {
"segmentation_raster_file": {
"src": {
"image_file": {
"uri": "{raw}/isprs-potsdam/5_Labels_for_participants_no_Boundary/top_potsdam_2_11_label_noBoundary.tif"
}
},
"src_classes": [ "#ffff00", "#0000ff" ],
"dst_classes": [ 1, 0 ]
}
}
}
],
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am running in the container, as well. I am not sure how a version mis-match could occur. Edit: I see now that you are talking about the georeferencing script. I'll address that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does image_file
work when there is more than one label raster for the scene? If there is only one label raster per scene, does that imply that there is only one image raster per scene?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
image_file
does not work if there is more than one label raster for the scene. I'm guessing that case won't come up very frequently, but it's possible. For this dataset, it's not a problem.
Re: your second question, I think it's possible for there to be a single label raster, but multiple image rasters per scene.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is going to be a "getting started" example, we should probably use image_file
and avoid having to run the georeferencing script, although it could be useful for another application.
Here's what I got when I ran the script in the container after running update
:
root@e820085104e2:/opt/src# ./rastervision/contrib/cowc/transfer_georeference.py \
/opt/data/raw-data/isprs-potsdam/4_Ortho_RGBIR/top_potsdam_2_10_RGBIR.tif \ /opt/data/raw-data/isprs-potsdam/5_Labels_for_participants_no_Boundary/top_potsdam_2_10_label_noBoundary.tif \ /opt/data/raw-data/isprs-potsdam/labels/2_10.tif
Traceback (most recent call last):
File "./rastervision/contrib/cowc/transfer_georeference.py", line 27, in
ul = re.search(ul_re, ullr)
File "/usr/lib/python3.5/re.py", line 173, in search
return _compile(pattern, flags).search(string)
TypeError: cannot use a string pattern on a bytes-like object
"""Constructor. | ||
|
||
Args: | ||
src: A source of raster label data (either an object that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I understand the difference between src
and dst
now. I was confused because in the other LabelStores I was just using uri
for both reading and writing (but had separate readable
and writable
fields). But I still think it's confusing that the term dst
is being used in both dst
, and dst_classes
which seems like a different concept. Perhaps dst_classes
should be called rv_classes
since they are the class ids that RV is using internally, and they are used even when there is no dst
specified.
if isinstance(src, RasterSource): | ||
self.src = src | ||
elif isinstance(src, RasterSourceProto): | ||
self.src = raster_source_builder.build(src) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how this is smart enough to handle different types. Seems like a good API design pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
def make_debug_images(record_path: str, | ||
output_dir: str, | ||
class_map: ClassMap, | ||
p: float = 0.25) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea to only save a random subset.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
file. | ||
|
||
""" | ||
return join(base_uri, '{}-0.record'.format(split)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason for the -0
? Just wondering because the code for OD doesn't do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, for some reason deeplap wants files with names of the form *-[0-9]\+.record
.
src/rastervision/utils/files.py
Outdated
@@ -24,6 +27,40 @@ class ProtobufParseException(Exception): | |||
pass | |||
|
|||
|
|||
def numpy_to_png(array: np.ndarray) -> str: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these two functions should be in utils.misc
since they don't really deal with file handling.
backend_config_uri = get_local_path(options.backend_config_uri, | ||
self.temp_dir) | ||
with open(backend_config_uri) as f: | ||
be_options = json.load(f) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of just using raw JSON file for the backend options, I think we should use a protobuf file, like in the rest of RV. That way we can more easily document and validate the options.
} | ||
}, | ||
"src_classes": [ "#ffff00", "#0000ff" ], | ||
"dst_classes": [ 1, 0 ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are there two elements in dst_classes
but only one in class_items
? I think it has something to do with the fact that everything that's not in the class_map
counts as background and is assigned the id 0. But if that's true, then I still don't understand why 0 needs to be mapped to a hex value at all. Perhaps there should be more documentation about converting classes to the background value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#ffff00
is for cars, #0000ff
is for buildings. #ffff00
is mapped to 1
(cars) and #0000ff
is mapped to 0 (no label).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we are training a model to distinguish between car and building. But, then shouldn't there be an item in class_items
for building?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is only one class under consideration: cars. Let me make some changes to clarify the situation.
If you think there's a bug we should probably just make a separate issue for it. |
I do not. Changing the chip size to 513x513 seemed to reduce this, but I still see some oddly scaled images. The might be from edges or corners but I noticed that seem to be smaller in scale so I don't think that that is so.
So did I, still working on it. |
Comments addressed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the other changes look good to me!
] | ||
} | ||
}, | ||
"raster_class_map": [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The raster_class_map
is easier to understand. But, I still don't know why there is only an item for car, and not for building. Also, class_ids are expected to go from 1 to N for object detection and classification or things will break, and that should be fixed at some point. So I'm glad your implementation doesn't depend on it (since you've chosen 127 and 255 as ids), but that could be confusing, so maybe they should be changed to 1 and 2?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this sample only detecting cars? There are several classes inside the labels (building, low veg, trees, clutter, impervious surface) - bit confused why we would include buildings but not all of these, and I can see including just the target class, in this case, cars
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class_items
contains items for cars and buildings, but the raster_class_map
only has an item for car. The mismatch is what's confusing me. I think it makes sense to use a (consistent) subset though.
] | ||
} | ||
}, | ||
"raster_class_map": [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, what do you think about switch from geotiff_files
to image_file
so your script doesn't need to be run, esp. for new users?
9d0316a
to
ce178e0
Compare
} | ||
] | ||
}, | ||
"make_training_chips_options": { | ||
"segmentation_options": { | ||
"empty_survival_probability": 0.2, | ||
"empty_survival_probability": 0.1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also switch to using image_file
on this file too?
ce178e0
to
4019b02
Compare
Are there any further changes requested? |
Looks like the Travis CI failed because of code formatting? https://travis-ci.org/azavea/raster-vision/builds/415948141#L2128 |
Once the build passes this is good to merge. I think you did an awesome job on this, especially considering the lack of documentation and being new to the project. Thanks! |
Failed merge with working branch, trying to resolve. |
4019b02
to
825e900
Compare
Overview
This pull request adds the ability to generate Deeplab-compatible TFRecords and the ability to train a Deeplab model using the same. Preliminary to #321 .
Checklist
Notes
Optional. Ancillary topics, caveats, alternative strategies that didn't work out, anything else.
Testing Instructions
Step 1
Prepare the test data by using the
contrib/cowc/transfer_georeference.py
script. Typingwill apply the georeferencing information from
top_potsdam_2_10_RGBIR.tif
to the ungeoreferenced label filetop_potsdam_2_10_label_noBoundary.tif
to produce a new filetop_potsdam_2_10_label_georeferenced.tif
. The same must be done for2_11
.Step 2
Use one of the two workflow configuration files:
samples/workflow-configs/segmentation/deeplab-test.json
orsamples/workflow-configs/segmentation/deeplab-remote-test.json
.