Skip to content

Commit 17a0da9

Browse files
committed
Merge branch 'master' of github.com:developmentseed/label-maker
2 parents 8dcb7a9 + 406e10d commit 17a0da9

File tree

8 files changed

+108
-11
lines changed

8 files changed

+108
-11
lines changed

.circleci/config.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,19 @@ jobs:
2828
mkdir integration
2929
cp test/fixtures/integration/portugal-z17.mbtiles integration/portugal-z17.mbtiles
3030
cp -r test/fixtures/integration/tiles integration/tiles
31+
32+
# Test `label-maker labels --sparse`; must be run before `label-maker labels` so `verify-package.py` passes
33+
label-maker labels --dest integration --config test/fixtures/integration/config.integration_sparse.json --sparse > stdout_sparse
34+
python test/verify-sparse-labels.py
35+
# Test `label-maker labels`
3136
label-maker labels --dest integration --config test/fixtures/integration/config.integration.json > stdout
3237
python test/verify-labels.py
38+
# Test `label-maker package`
3339
label-maker package --dest integration --config test/fixtures/integration/config.integration.json
3440
python test/verify-package.py
3541
3642
# Retest that `label-maker labels` works, but from outside the module directory
3743
cd ~
3844
label-maker labels --dest label-maker/integration --config label-maker/test/fixtures/integration/config.integration.json > stdout
3945
cd ~/label-maker
40-
python test/verify-labels.py
46+
python test/verify-labels.py

.config/pylintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ confidence=
5050
# --enable=similarities". If you want to run only the classes checker, but have
5151
# no Warning level messages displayed, use"--disable=all --enable=classes
5252
# --disable=W"
53-
disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,invalid-name,line-too-long,global-statement,too-many-locals,too-many-arguments,too-many-branches
53+
disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,invalid-name,line-too-long,global-statement,too-many-locals,too-many-arguments,too-many-branches,too-many-statements
5454

5555
# Enable the message, report, category or checker with the given id(s). You can
5656
# either give multiple identifier separated by comma (,) or put this option

label_maker/label.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
from tilepie import tilereduce
1818

1919
import label_maker
20+
from label_maker.utils import class_match
2021
from label_maker.filter import create_filter
2122

2223
# declare a global accumulator so the workers will have access
2324
tile_results = dict()
2425

25-
def make_labels(dest_folder, zoom, country, classes, ml_type, bounding_box, **kwargs):
26+
def make_labels(dest_folder, zoom, country, classes, ml_type, bounding_box, sparse, **kwargs):
2627
"""Create label data from OSM QA tiles for specified classes
2728
2829
Perform the following operations:
@@ -73,30 +74,49 @@ def make_labels(dest_folder, zoom, country, classes, ml_type, bounding_box, **kw
7374
# Call tilereduce
7475
print('Determining labels for each tile')
7576
mbtiles_to_reduce = mbtiles_file_zoomed
76-
tilereduce(dict(zoom=zoom, source=mbtiles_to_reduce, bbox=bounding_box, args=dict(ml_type=ml_type, classes=classes)),
77+
tilereduce(dict(zoom=zoom, source=mbtiles_to_reduce, bbox=bounding_box,
78+
args=dict(ml_type=ml_type, classes=classes)),
7779
_mapper, _callback, _done)
7880

7981
# Add empty labels to any tiles which didn't have data
8082
empty_label = _create_empty_label(ml_type, classes)
8183
for tile in tiles(*bounding_box, [zoom]):
8284
index = '-'.join([str(i) for i in tile])
85+
global tile_results
8386
if tile_results.get(index) is None:
8487
tile_results[index] = empty_label
8588

8689
# Print a summary of the labels
8790
_tile_results_summary(ml_type, classes)
8891

92+
# If the --sparse flag is provided, limit the total background tiles to write
93+
if sparse:
94+
pos_examples, neg_examples = [], []
95+
for k in tile_results.keys():
96+
if class_match(ml_type, tile_results[k], 0):
97+
neg_examples.append(k)
98+
else:
99+
pos_examples.append(k)
100+
101+
# Choose random subset of negative examples
102+
n_neg_ex = int(kwargs['background_ratio'] * len(pos_examples))
103+
neg_examples = np.random.choice(neg_examples, n_neg_ex, replace=False).tolist()
104+
105+
tile_results = {k: tile_results.get(k) for k in pos_examples + neg_examples}
106+
print('Using sparse mode; subselected {} background tiles'.format(n_neg_ex))
107+
89108
# write out labels as numpy arrays
90109
labels_file = op.join(dest_folder, 'labels.npz')
91-
print('Write out labels to {}'.format(labels_file))
110+
print('Writing out labels to {}'.format(labels_file))
92111
np.savez(labels_file, **tile_results)
93112

94113
# write out labels as GeoJSON or PNG
95114
if ml_type == 'classification':
96115
features = []
97116
for tile, label in tile_results.items():
98117
feat = feature(Tile(*[int(t) for t in tile.split('-')]))
99-
features.append(Feature(geometry=feat['geometry'], properties=dict(label=label.tolist())))
118+
features.append(Feature(geometry=feat['geometry'],
119+
properties=dict(label=label.tolist())))
100120
json.dump(fc(features), open(op.join(dest_folder, 'classification.geojson'), 'w'))
101121
elif ml_type == 'object-detection':
102122
label_folder = op.join(dest_folder, 'labels')

label_maker/main.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,14 @@ def parse_args(args):
3939
subparsers = parser.add_subparsers(dest='command')
4040

4141
subparsers.add_parser('download', parents=[pparser], help='', formatter_class=dhf)
42-
subparsers.add_parser('labels', parents=[pparser], help='', formatter_class=dhf)
42+
l = subparsers.add_parser('labels', parents=[pparser], help='', formatter_class=dhf)
4343
p = subparsers.add_parser('preview', parents=[pparser], help='', formatter_class=dhf)
4444
subparsers.add_parser('images', parents=[pparser], help='', formatter_class=dhf)
4545
subparsers.add_parser('package', parents=[pparser], help='', formatter_class=dhf)
4646

47+
# labels has an optional parameter
48+
l.add_argument('-s', '--sparse', action='store_true')
49+
4750
# preview has an optional parameter
4851
p.add_argument('-n', '--number', default=5, type=int,
4952
help='number of examples images to create per class')
@@ -77,7 +80,8 @@ def cli():
7780
if cmd == 'download':
7881
download_mbtiles(dest_folder=dest_folder, **config)
7982
elif cmd == 'labels':
80-
make_labels(dest_folder=dest_folder, **config)
83+
sparse = args.get('sparse', False)
84+
make_labels(dest_folder=dest_folder, sparse=sparse, **config)
8185
elif cmd == 'preview':
8286
number = args.get('number')
8387
preview(dest_folder=dest_folder, number=number, **config)

label_maker/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"""Library verison"""
2-
__version__ = '0.1.2'
2+
__version__ = '0.1.3'
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"country": "portugal",
3+
"bounding_box": [
4+
-9.4575,
5+
38.8467,
6+
-9.4510,
7+
38.8513
8+
],
9+
"zoom": 17,
10+
"classes": [
11+
{ "name": "Water Tower", "filter": ["==", "man_made", "water_tower"] },
12+
{ "name": "Building", "filter": ["has", "building"] },
13+
{ "name": "Farmland", "filter": ["==", "landuse", "farmland"] },
14+
{ "name": "Ruins", "filter": ["==", "historic", "ruins"] },
15+
{ "name": "Parking", "filter": ["==", "amenity", "parking"] },
16+
{ "name": "Roads", "filter": ["has", "highway"] }
17+
],
18+
"imagery": "https://api.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}.jpg?access_token=ACCESS_TOKEN",
19+
"background_ratio": 0,
20+
"ml_type": "classification",
21+
"seed": 19
22+
}

test/verify-labels.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
}
1717

1818
labels = np.load('integration/labels.npz')
19+
assert len(labels.files) == len(expected_labels.keys()) # First check number of tiles
1920
for tile in labels.files:
20-
assert np.array_equal(expected_labels[tile], labels[tile])
21+
assert np.array_equal(expected_labels[tile], labels[tile]) # Now, content
2122

2223
# our GeoJSON looks like the fixture
2324
expected_geojson = json.load(open('test/fixtures/integration/classification.geojson'))
@@ -36,7 +37,7 @@
3637
Parking: 1 tiles
3738
Roads: 8 tiles
3839
Total tiles: 9
39-
Write out labels to integration/labels.npz
40+
Writing out labels to integration/labels.npz
4041
"""
4142

4243
with open('stdout', 'r') as output:

test/verify-sparse-labels.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Validate that the output produced by integration testing on 'label-maker labels' matches our expectations"""
2+
import json
3+
import numpy as np
4+
5+
# our labels should look like this
6+
expected_labels = {
7+
'62092-50163-17': np.array([0, 0, 0, 0, 0, 0, 1]),
8+
'62092-50164-17': np.array([0, 0, 0, 0, 0, 0, 1]),
9+
'62093-50162-17': np.array([0, 0, 0, 0, 0, 0, 1]),
10+
'62093-50164-17': np.array([0, 0, 0, 0, 0, 0, 1]),
11+
'62094-50162-17': np.array([0, 0, 0, 0, 0, 0, 1]),
12+
'62094-50164-17': np.array([0, 0, 0, 0, 0, 0, 1]),
13+
'62094-50163-17': np.array([0, 1, 1, 0, 0, 0, 1]),
14+
'62093-50163-17': np.array([0, 0, 0, 0, 1, 1, 1])
15+
}
16+
17+
labels = np.load('integration/labels.npz')
18+
assert len(labels.files) == len(expected_labels.keys()) # First check number of tiles
19+
for tile in labels.files:
20+
assert np.array_equal(expected_labels[tile], labels[tile]) # Now, content
21+
22+
# our GeoJSON looks like the fixture
23+
expected_geojson = json.load(open('test/fixtures/integration/classification.geojson'))
24+
geojson = json.load(open('integration/classification.geojson'))
25+
26+
for feature in geojson['features']:
27+
assert feature in expected_geojson['features']
28+
29+
# our command line output should look like this
30+
expected_output = """Determining labels for each tile
31+
---
32+
Water Tower: 1 tiles
33+
Building: 1 tiles
34+
Farmland: 0 tiles
35+
Ruins: 1 tiles
36+
Parking: 1 tiles
37+
Roads: 8 tiles
38+
Total tiles: 9
39+
Using sparse mode; subselected 0 background tiles
40+
Writing out labels to integration/labels.npz
41+
"""
42+
43+
with open('stdout_sparse', 'r') as output:
44+
assert expected_output == output.read()

0 commit comments

Comments
 (0)