This commit is contained in:
Caleb Robinson 2020-07-05 15:49:09 +00:00
Родитель 830e7020e5
Коммит ba064e29f5
8 изменённых файлов: 280 добавлений и 52 удалений

2
.gitignore поставляемый
Просмотреть файл

@ -7,7 +7,7 @@ __pycache__
data/
logs/
temp/
tmp/
web_tool/endpoints.mine.js

Просмотреть файл

@ -1,12 +1,6 @@
TODO microsoft/landcover repo
- Try to reproduce the initial setup
- Strip Lambda layers from pre-trained Keras models
- Clean up unused code (e.g. ServerModels*) and unused branches
- Create 2 example dataset / models
- Sentinel untrained model
- NAIP trained model
- Create sensible logging capability
- Debug the padding issue
- Debug the saving/restoring models workflow
- Figure out how the remote calls work when using other ServerModels besides the Keras one
- Figure out how to exclude web_tool/tiles/
- Figure out how the remote calls work when using other ServerModels besides the Keras one

Просмотреть файл

@ -8,17 +8,16 @@ channels:
dependencies:
- python>=3.6
- gdal>=2.0
- rasterio>=1.0
- rasterio
- shapely
- rtree
- fiona
- opencv
- proj4
- numpy
- pandas
- matplotlib
- scikit-learn
- scikit-image
- scipy
- pytorch>=1.0
- torchvision
- utm
@ -27,8 +26,8 @@ dependencies:
- cheroot==8.3.0
- joblib
- mercantile
- Pillow
- pytz
- ipykernel
- pylint
- pip
- pip:

236
environment_precise.yml Normal file
Просмотреть файл

@ -0,0 +1,236 @@
name: landcover
channels:
- pytorch
- conda-forge
- defaults
dependencies:
- _libgcc_mutex=0.1=conda_forge
- _openmp_mutex=4.5=1_llvm
- affine=2.3.0=py_0
- astroid=2.4.2=py37hc8dfbb8_0
- attrs=19.3.0=py_0
- backcall=0.2.0=pyh9f0ad1d_0
- beaker=1.11.0=py_0
- blas=2.16=mkl
- boost-cpp=1.72.0=h8e57a91_0
- bottle=0.12.18=py_0
- bzip2=1.0.8=h516909a_2
- ca-certificates=2020.6.20=hecda079_0
- cairo=1.16.0=hcf35c78_1003
- certifi=2020.6.20=py37hc8dfbb8_0
- cfitsio=3.470=hb60a0a2_2
- cheroot=8.3.0=py37hc8dfbb8_1
- click=7.1.2=pyh9f0ad1d_0
- click-plugins=1.1.1=py_0
- cligj=0.5.0=py_0
- cloudpickle=1.5.0=py_0
- cudatoolkit=10.2.89=hfd86e86_1
- curl=7.68.0=hf8cf82a_0
- cycler=0.10.0=py_2
- cytoolz=0.10.1=py37h516909a_0
- dask-core=2.20.0=py_0
- dbus=1.13.6=he372182_0
- decorator=4.4.2=py_0
- entrypoints=0.3=py37hc8dfbb8_1001
- expat=2.2.9=he1b5a44_2
- ffmpeg=4.2.3=h167e202_0
- fiona=1.8.9.post2=py37hdff7cfa_0
- fontconfig=2.13.1=h86ecdb6_1001
- freetype=2.10.2=he06d7ca_0
- freexl=1.0.5=h14c3975_1002
- funcsigs=1.0.2=py_3
- gdal=2.4.3=py37h5f563d9_8
- geos=3.7.2=he1b5a44_2
- geotiff=1.5.1=hcd53e25_3
- gettext=0.19.8.1=hc5be6a0_1002
- giflib=5.1.7=h516909a_1
- glib=2.65.0=h6f030ca_0
- gmp=6.2.0=he1b5a44_2
- gnutls=3.6.13=h79a8f9a_0
- graphite2=1.3.13=he1b5a44_1001
- gst-plugins-base=1.14.5=h0935bb2_2
- gstreamer=1.14.5=h36ae1b5_2
- harfbuzz=2.4.0=h9f30f68_3
- hdf4=4.2.13=hf30be14_1003
- hdf5=1.10.5=nompi_h3c11f04_1104
- icu=64.2=he1b5a44_1
- imagecodecs-lite=2019.12.3=py37h03ebfcd_1
- imageio=2.8.0=py_0
- ipykernel=5.3.0=py37h43977f1_0
- ipython=7.16.1=py37h43977f1_0
- ipython_genutils=0.2.0=py_1
- isort=4.3.21=py37hc8dfbb8_1
- jaraco.functools=3.0.1=py_0
- jasper=1.900.1=h07fcdf6_1006
- jedi=0.17.1=py37hc8dfbb8_0
- joblib=0.16.0=py_0
- jpeg=9d=h516909a_0
- json-c=0.13.1=hbfbb72e_1002
- jupyter_client=6.1.5=py_0
- jupyter_core=4.6.3=py37hc8dfbb8_1
- kealib=1.4.13=hec59c27_0
- kiwisolver=1.2.0=py37h99015e2_0
- krb5=1.16.4=h2fd8d38_0
- lame=3.100=h14c3975_1001
- lazy-object-proxy=1.4.3=py37h8f50634_2
- ld_impl_linux-64=2.34=h53a641e_5
- libblas=3.8.0=16_mkl
- libcblas=3.8.0=16_mkl
- libclang=9.0.1=default_hde54327_0
- libcurl=7.68.0=hda55be3_0
- libdap4=3.20.4=hd3bb157_0
- libedit=3.1.20191231=h46ee950_0
- libffi=3.2.1=he1b5a44_1007
- libgcc-ng=9.2.0=h24d8f2e_2
- libgdal=2.4.3=hd53ac37_8
- libgfortran-ng=7.5.0=hdf63c60_6
- libiconv=1.15=h516909a_1006
- libkml=1.3.0=hb574062_1011
- liblapack=3.8.0=16_mkl
- liblapacke=3.8.0=16_mkl
- libllvm9=9.0.1=he513fc3_1
- libnetcdf=4.7.1=nompi_h94020b1_102
- libopencv=4.2.0=py37_5
- libpng=1.6.37=hed695b0_1
- libpq=11.5=hd9ab2ff_2
- libsodium=1.0.17=h516909a_0
- libspatialindex=1.9.3=he1b5a44_3
- libspatialite=4.3.0a=h57ae47a_1030
- libssh2=1.9.0=hab1572f_2
- libstdcxx-ng=9.2.0=hdf63c60_2
- libtiff=4.1.0=hc3755c2_3
- libuuid=2.32.1=h14c3975_1000
- libwebp=1.0.2=hf4e8a37_4
- libxcb=1.13=h14c3975_1002
- libxkbcommon=0.10.0=he1b5a44_0
- libxml2=2.9.10=hee79883_0
- llvm-openmp=10.0.0=hc9558a2_0
- lz4-c=1.9.2=he1b5a44_1
- matplotlib=3.2.2=0
- matplotlib-base=3.2.2=py37h30547a4_0
- mccabe=0.6.1=py_1
- mercantile=1.1.5=pyh9f0ad1d_0
- mkl=2020.1=219
- more-itertools=8.4.0=py_0
- munch=2.5.0=py_0
- ncurses=6.1=hf484d3e_1002
- nettle=3.4.1=h1bed415_1002
- networkx=2.4=py_1
- ninja=1.10.0=hc9558a2_0
- nspr=4.26=he1b5a44_0
- nss=3.47=he751ad9_0
- numpy=1.18.5=py37h8960a57_0
- olefile=0.46=py_0
- opencv=4.2.0=py37_5
- openh264=2.1.1=h8b12597_0
- openjpeg=2.3.1=h981e76c_3
- openssl=1.1.1g=h516909a_0
- parso=0.7.0=pyh9f0ad1d_0
- pcre=8.44=he1b5a44_0
- pexpect=4.8.0=py37hc8dfbb8_1
- pickleshare=0.7.5=py37hc8dfbb8_1001
- pillow=7.2.0=py37h718be6c_0
- pip=20.1.1=py_1
- pixman=0.38.0=h516909a_1003
- poppler=0.67.0=h14e79db_8
- poppler-data=0.4.9=1
- postgresql=11.5=hc63931a_2
- proj4=6.1.1=hc80f0dc_1
- prompt-toolkit=3.0.5=py_1
- pthread-stubs=0.4=h14c3975_1001
- ptyprocess=0.6.0=py_1001
- py-opencv=4.2.0=py37h43977f1_5
- pygments=2.6.1=py_0
- pylint=2.5.3=py37hc8dfbb8_0
- pyparsing=2.4.7=pyh9f0ad1d_0
- pyqt=5.12.3=py37h8685d9f_3
- python=3.7.6=cpython_h8356626_6
- python-dateutil=2.8.1=py_0
- python_abi=3.7=1_cp37m
- pytorch=1.5.1=py3.7_cuda10.2.89_cudnn7.6.5_0
- pytz=2020.1=pyh9f0ad1d_0
- pywavelets=1.1.1=py37h03ebfcd_1
- pyyaml=5.3.1=py37h8f50634_0
- pyzmq=19.0.1=py37hac76be4_0
- qt=5.12.5=hd8c4c69_1
- rasterio=1.1.2=py37hdff7cfa_0
- readline=8.0=hf8c457e_0
- rtree=0.9.4=py37h8526d28_1
- scikit-image=0.17.2=py37h0da4684_1
- scikit-learn=0.23.1=py37h8a51577_0
- setuptools=49.1.0=py37hc8dfbb8_0
- shapely=1.6.4=py37hec07ddf_1006
- six=1.15.0=pyh9f0ad1d_0
- snuggs=1.4.7=py_0
- sqlite=3.32.3=hcee41ef_0
- threadpoolctl=2.1.0=pyh5ca1d4c_0
- tifffile=2020.6.3=py_0
- tk=8.6.10=hed695b0_0
- toml=0.10.1=pyh9f0ad1d_0
- toolz=0.10.0=py_0
- torchvision=0.6.1=py37_cu102
- tornado=6.0.4=py37h8f50634_1
- traitlets=4.3.3=py37hc8dfbb8_1
- typed-ast=1.4.1=py37h516909a_0
- tzcode=2020a=h516909a_0
- utm=0.5.0=py_0
- wcwidth=0.2.5=pyh9f0ad1d_0
- wheel=0.34.2=py_1
- wrapt=1.11.2=py37h8f50634_0
- x264=1!152.20180806=h14c3975_0
- xerces-c=3.2.2=h8412b87_1004
- xorg-kbproto=1.0.7=h14c3975_1002
- xorg-libice=1.0.10=h516909a_0
- xorg-libsm=1.2.3=h84519dc_1000
- xorg-libx11=1.6.9=h516909a_0
- xorg-libxau=1.0.9=h14c3975_0
- xorg-libxdmcp=1.1.3=h516909a_0
- xorg-libxext=1.3.4=h516909a_0
- xorg-libxrender=0.9.10=h516909a_1002
- xorg-renderproto=0.11.1=h14c3975_1002
- xorg-xextproto=7.3.0=h14c3975_1002
- xorg-xproto=7.0.31=h14c3975_1007
- xz=5.2.5=h516909a_0
- yaml=0.2.5=h516909a_0
- zeromq=4.3.2=he1b5a44_2
- zlib=1.2.11=h516909a_1006
- zstd=1.4.4=h6597ccf_3
- pip:
- absl-py==0.9.0
- astunparse==1.6.3
- cachetools==4.1.1
- chardet==3.0.4
- gast==0.3.3
- google-auth==1.18.0
- google-auth-oauthlib==0.4.1
- google-pasta==0.2.0
- grpcio==1.30.0
- h5py==2.10.0
- idna==2.10
- importlib-metadata==1.7.0
- keras-preprocessing==1.1.2
- markdown==3.2.2
- oauthlib==3.1.0
- opt-einsum==3.2.1
- plumbum==1.6.9
- protobuf==3.12.2
- pyasn1==0.4.8
- pyasn1-modules==0.2.8
- pyqt5-sip==4.19.18
- pyqtchart==5.12
- pyqtwebengine==5.12.1
- requests==2.24.0
- requests-oauthlib==1.3.0
- rpyc==4.1.5
- rsa==4.6
- scipy==1.4.1
- tensorboard==2.2.2
- tensorboard-plugin-wit==1.7.0
- tensorflow==2.2.0
- tensorflow-estimator==2.2.0
- termcolor==1.1.0
- urllib3==1.25.9
- werkzeug==1.0.1
- zipp==3.1.0
prefix: /data/anaconda/envs/landcover

Просмотреть файл

@ -290,11 +290,9 @@ def pred_patch():
output, output_bounds = warp_data_to_3857(output, naip_crs, naip_transform, naip_bounds)
output = crop_data_by_extent(output, output_bounds, extent)
output = output[:,:,:len(color_list)]
#if len(color_list) > output.shape[2]:
# print("WARNING: Color list is smaller than the number of output channels, cropping output to number of colors (you probably don't want this to happen")
# output = output[:,:,:len(color_list)]
if output.shape[2] > len(color_list):
print("WARNING: Color list is smaller than the number of output channels, cropping output to number of colors (you probably don't want this to happen")
output = output[:,:,:len(color_list)]
# ------------------------------------------------------
# Step 5
@ -342,8 +340,16 @@ def pred_tile():
return json.dumps({"error": "Cannot currently download imagery with 'Basemap' based datasets"})
output = SESSION_HANDLER.get_session(bottle.request.session.id).model.run(naip_data, geom, True)
output_hard = output.argmax(axis=2)
print("Finished, output dimensions:", output.shape)
if output.shape[2] > len(color_list):
print("WARNING: Color list is smaller than the number of output channels, cropping output to number of colors (you probably don't want this to happen")
output = output[:,:,:len(color_list)]
output_hard = output.argmax(axis=2)
# apply nodata mask from naip_data
nodata_mask = np.sum(naip_data == 0, axis=2) == naip_data.shape[2]
@ -361,8 +367,8 @@ def pred_tile():
img_hard, img_hard_bounds = warp_data_to_3857(img_hard, raster_crs, raster_transform, raster_bounds, resolution=10)
cv2.imwrite(os.path.join(ROOT_DIR, "temp/%s.png" % (tmp_id)), img_hard)
data["downloadPNG"] = "downloads/%s.png" % (tmp_id)
cv2.imwrite("tmp/downloads/%s.png" % (tmp_id), img_hard)
data["downloadPNG"] = "tmp/downloads/%s.png" % (tmp_id)
new_profile = raster_profile.copy()
new_profile['driver'] = 'GTiff'
@ -373,12 +379,12 @@ def pred_tile():
new_profile['height'] = naip_data.shape[0]
new_profile['width'] = naip_data.shape[1]
new_profile['nodata'] = 255
f = rasterio.open(os.path.join(ROOT_DIR, "downloads/%s.tif" % (tmp_id)), 'w', **new_profile)
f = rasterio.open("tmp/downloads/%s.tif" % (tmp_id), 'w', **new_profile)
f.write(output_hard.astype(np.uint8), 1)
f.close()
data["downloadTIFF"] = "downloads/%s.tif" % (tmp_id)
data["downloadTIFF"] = "tmp/downloads/%s.tif" % (tmp_id)
f = open(os.path.join(ROOT_DIR, "downloads/%s.txt" % (tmp_id)), "w")
f = open("tmp/downloads/%s.txt" % (tmp_id), "w")
f.write("Class id\tClass name\tPercent area\tArea (km^2)\n")
for i in range(len(vals)):
pct_area = (counts[i] / np.sum(counts))
@ -388,7 +394,7 @@ def pred_tile():
real_area = -1
f.write("%d\t%s\t%0.4f%%\t%0.4f\n" % (vals[i], name_list[vals[i]], pct_area*100, real_area))
f.close()
data["downloadStatistics"] = "downloads/%s.txt" % (tmp_id)
data["downloadStatistics"] = "tmp/downloads/%s.txt" % (tmp_id)
bottle.response.status = 200
return json.dumps(data)
@ -443,6 +449,9 @@ def get_basemap_data(filepath):
def get_zone_data(filepath):
return bottle.static_file(filepath, root="./data/zones/")
def get_downloads(filepath):
return bottle.static_file(filepath, root="./tmp/downloads/")
def get_favicon():
return
@ -474,28 +483,10 @@ def main():
parser.add_argument("--port", action="store", dest="port", type=int, help="Port to listen on", default=8080)
subparsers = parser.add_subparsers(dest="subcommand", help='Help for subcommands') # TODO: If we use Python3.7 we can use the required keyword here
parser_a = subparsers.add_parser('local', help='For running models on the local server')
parser_b = subparsers.add_parser('remote', help='For running models with RPC calls')
parser.add_argument("--remote_host", action="store", dest="remote_host", type=str, help="RabbitMQ host", default="0.0.0.0")
parser.add_argument("--remote_port", action="store", dest="remote_port", type=int, help="RabbitMQ port", default=8080)
args = parser.parse_args(sys.argv[1:])
# create Session factory to use based on whether we are running locally or remotely
run_local = None
if args.subcommand == "local":
print("Sessions will be spawned on the local machine")
run_local = True
elif args.subcommand == "remote":
print("Sessions will be spawned remotely")
run_local = False
else:
print("Must specify 'local' or 'remote' on command line")
return
SESSION_HANDLER = SessionHandler(run_local, args)
# Create session factory to handle incoming requests
SESSION_HANDLER = SessionHandler(args)
SESSION_HANDLER.start_monitor()
# Setup logging
@ -547,6 +538,7 @@ def main():
app.route("/", method="GET", callback=get_landing_page)
app.route("/data/basemaps/<filepath:re:.*>", method="GET", callback=get_basemap_data)
app.route("/data/zones/<filepath:re:.*>", method="GET", callback=get_zone_data)
app.route("/tmp/downloads/<filepath:re:.*>", method="GET", callback=get_downloads)
app.route("/favicon.ico", method="GET", callback=get_favicon)
app.route("/<filepath:re:.*>", method="GET", callback=get_everything_else)
@ -567,6 +559,7 @@ def main():
server.max_request_header_size = 2**13
server.max_request_body_size = 2**27
LOGGER.info("Server initialized")
try:
server.start()
finally:

9
tests/test_fiona.py Normal file
Просмотреть файл

@ -0,0 +1,9 @@
import fiona
import fiona.transform
print(fiona.__version__)
polygon = {'type': 'Polygon', 'coordinates': [[[106.767738, 10.741927], [106.758781, 10.744711], [106.751907, 10.741889], [106.747963, 10.741885], [106.746193, 10.742742], [106.744606, 10.744854], [106.744186, 10.747693], [106.744781, 10.750982], [106.751587, 10.760911], [106.75264, 10.763586], [106.751907, 10.768116], [106.74765, 10.773662], [106.743225, 10.776812], [106.739616, 10.777557], [106.736816, 10.776791], [106.735237, 10.774683], [106.734772, 10.766784], [106.733765, 10.762663], [106.73008, 10.759851], [106.725098, 10.75899], [106.722046, 10.759261], [106.714409, 10.762514], [106.713478, 10.763097], [106.709991, 10.766509], [106.708252, 10.769729], [106.70816, 10.77059], [106.708405, 10.775246], [106.710396, 10.781879], [106.714394, 10.784905], [106.721992, 10.787327], [106.724091, 10.788966], [106.726776, 10.793646], [106.72731, 10.798936], [106.727013, 10.800393], [106.723145, 10.806143], [106.722313, 10.809803], [106.723427, 10.813807], [106.725563, 10.816747], [106.729965, 10.81936], [106.733452, 10.819189], [106.734848, 10.817568], [106.736412, 10.812769], [106.738136, 10.810225], [106.742424, 10.80855], [106.744438, 10.808448], [106.74823, 10.809682], [106.750984, 10.812878], [106.756981, 10.813123], [106.762329, 10.814506], [106.763954, 10.812859], [106.764664, 10.809789], [106.766129, 10.808674], [106.772972, 10.80761], [106.776802, 10.808998], [106.78109, 10.794502], [106.781616, 10.790422], [106.786751, 10.790675], [106.787277, 10.785187], [106.783836, 10.7819], [106.789925, 10.778492], [106.797104, 10.778723], [106.799065, 10.776022], [106.802345, 10.775158], [106.804298, 10.776154], [106.806473, 10.776011], [106.810089, 10.774617], [106.805573, 10.770088], [106.803299, 10.762519], [106.799156, 10.758993], [106.791252, 10.755604], [106.776222, 10.748719], [106.767738, 10.741927]]]}
# Caleb: This was giving me an error with Fiona 1.8.13 from conda-forge, however was totally fine with 1.8.13.post1 from pip (this is strange because there is no code difference between the two versions)
fiona.transform.transform_geom("epsg:4326", "epsg:3857", polygon)

Просмотреть файл

@ -56,9 +56,7 @@ def _load_dataset(dataset):
fn = shape_layer["shapesFn"]
if os.path.exists(fn):
shapes, areas, crs = _load_geojson_as_list(fn)
print("Loaded %d shapes" % (len(shapes)))
shape_layer["geoms"] = shapes
shape_layer["areas"] = areas
shape_layer["crs"] = crs["init"] # TODO: will this break with fiona version; I think `.crs` will turn into a PyProj object

Просмотреть файл

@ -51,11 +51,11 @@ def get_free_tcp_port():
class SessionHandler():
def __init__(self, local, args):
def __init__(self, args):
self._WORKERS = [ # TODO: I hardcode that there are 4 GPUs available on the local machine
{"type": "local", "gpu_id": None},
{"type": "local", "gpu_id": None},
{"type": "local", "gpu_id": None}
{"type": "local", "gpu_id": 0},
{"type": "local", "gpu_id": 1},
{"type": "local", "gpu_id": 2}
]
self._WORKER_POOL = Queue()
@ -66,7 +66,6 @@ class SessionHandler():
self._SESSION_MAP = dict()
self._SESSION_INFO = dict()
self.local = local
self.args = args