This commit is contained in:
Caleb Robinson 2020-01-20 23:49:28 +00:00
Родитель daf94bf9aa
Коммит 4369d4a2b5
10 изменённых файлов: 681 добавлений и 598 удалений

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

@ -44,8 +44,8 @@ class Session():
def __init__(self, session_id, run_local, model):
LOGGER.info("Instantiating a new session object with id: %s" % (session_id))
self.storage_type = None # this will be "table" or "file"
self.storage_path = None # this will be a file path
self.storage_type = "file" # this will be "table" or "file"
self.storage_path = "data/" # this will be a file path
self.table_service = None # this will be an instance of TableService
self.run_local = run_local
@ -123,7 +123,7 @@ class Session():
os.makedirs(base_dir, exist_ok=False)
model_fn = os.path.join(base_dir, "%s_model.p" % (snapshot_id))
joblib.dump(self.model, model_fn, protocol=pickle.HIGHEST_PROTOCOL)
#joblib.dump(self.model, model_fn, protocol=pickle.HIGHEST_PROTOCOL)
if self.storage_type == "file":
request_list_fn = os.path.join(base_dir, "%s_request_list.p" % (snapshot_id))

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

@ -212,6 +212,24 @@ var addZonePickerControl = function(zonemaps){
};
var addUploadControl = function(){
L.Control.FileLayerLoad.LABEL = '<span class="fa fa-upload"></span>';
var uploadControl = L.Control.fileLayerLoad({
fitBounds: true,
addToMap: false,
formats: [
'.geojson'
],
layerOptions: {
style: DEFAULT_ZONE_STYLE(2),
onEachFeature: forEachFeatureOnClick,
pane: "polygons"
}
});
uploadControl.addTo(gMap);
return uploadControl;
}
var getESRILayer = function(){
return L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
maxZoom: 20,

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

@ -88,7 +88,6 @@ var addInferenceMouseHandlers = function(){
};
var addDrawControlHandlers = function(){
gMap.on("draw:created", function (e) {
var layer = e.layer;
@ -113,3 +112,60 @@ var addDrawControlHandlers = function(){
}
});
};
var addRetrainKeyHandler = function(){
$(document).keydown(function(e) {
if(document.activeElement == document.body){
if((e.which == 114 || e.which == 82) && !gCtrlKeyDown){ // "r" - retrain
doRetrain();
}
}
});
};
var addOpacityKeyHandlers = function(opacitySlider){
$(document).keydown(function(e) {
if(document.activeElement == document.body){ // only register if we are in document.body so that we don't fire events when typing in text boxes
if(e.which == 97 || e.which == 65) { // "a" - set invisible
gVisible = false;
gMap.getPane('labels').style.opacity = 0.0;
opacitySlider.slider.value = 0;
opacitySlider._updateValue();
} else if(e.which == 115 || e.which == 83) { // "s" - toggle between visibile and invisible
if(gVisible){
gVisible = false;
gMap.getPane('labels').style.opacity = 0.0;
opacitySlider.slider.value = 0;
opacitySlider._updateValue();
}else{
gVisible = true;
gMap.getPane('labels').style.opacity = 1.0;
opacitySlider.slider.value = 100;
opacitySlider._updateValue();
}
} else if(e.which == 100 || e.which == 68) { // "d" - set visible
gVisible = true;
gMap.getPane('labels').style.opacity = 1.0;
opacitySlider.slider.value = 100
opacitySlider._updateValue();
}
}
});
};
var addGUIKeyHandlers = function(){
$(document).keydown(function(e){
gShiftKeyDown = e.shiftKey;
gCtrlKeyDown = e.ctrlKey;
});
$(document).keyup(function(e){
gShiftKeyDown = e.shiftKey;
gCtrlKeyDown = e.ctrlKey;
});
document.getElementById("map").onselectstart = function(){ return false;} // remove the default behavior of the shift key. TODO: should this be on "document" or "body" instead of "map"?
gMap.on('contextmenu',function(e){});
gMap.on('dblclick',function(e){});
gMap.doubleClickZoom.disable();
gMap.boxZoom.disable();
};

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

@ -0,0 +1,309 @@
/*
This is a modified version of Leaflet.FileLayer v.1.2.0 from https://github.com/makinacorpus/Leaflet.FileLayer/
Changes include:
- Remove features depending on togeoson.js
- Changed component tag from <a> to <button> for styling puprposes
- Removed leaflet-zoom css for styling purposes
*/
/*
* Load files *locally* (GeoJSON, KML, GPX) into the map
* using the HTML5 File API.
*
* Requires Mapbox's togeojson.js to be in global scope
* https://github.com/mapbox/togeojson
*/
(function (factory, window) {
if (typeof window !== 'undefined' && window.L) {
factory(window.L);
}
}(function fileLoaderFactory(L) {
var FileLoader = L.Layer.extend({
options: {
layer: L.geoJson,
layerOptions: {},
fileSizeLimit: 1024
},
initialize: function (map, options) {
this._map = map;
L.Util.setOptions(this, options);
this._parsers = {
geojson: this._loadGeoJSON,
json: this._loadGeoJSON
};
},
load: function (file, ext) {
var parser,
reader;
// Check file is defined
if (this._isParameterMissing(file, 'file')) {
return false;
}
// Check file size
if (!this._isFileSizeOk(file.size)) {
return false;
}
// Get parser for this data type
parser = this._getParser(file.name, ext);
if (!parser) {
return false;
}
// Read selected file using HTML5 File API
reader = new FileReader();
reader.onload = L.Util.bind(function (e) {
var layer;
try {
this.fire('data:loading', { filename: file.name, format: parser.ext });
layer = parser.processor.call(this, e.target.result, parser.ext);
this.fire('data:loaded', {
layer: layer,
filename: file.name,
format: parser.ext
});
} catch (err) {
this.fire('data:error', { error: err });
}
}, this);
// Testing trick: tests don't pass a real file,
// but an object with file.testing set to true.
// This object cannot be read by reader, just skip it.
if (!file.testing) {
reader.readAsText(file);
}
// We return this to ease testing
return reader;
},
loadMultiple: function (files, ext) {
var readers = [];
if (files[0]) {
files = Array.prototype.slice.apply(files);
while (files.length > 0) {
readers.push(this.load(files.shift(), ext));
}
}
// return first reader (or false if no file),
// which is also used for subsequent loadings
return readers;
},
loadData: function (data, name, ext) {
var parser;
var layer;
// Check required parameters
if ((this._isParameterMissing(data, 'data'))
|| (this._isParameterMissing(name, 'name'))) {
return;
}
// Check file size
if (!this._isFileSizeOk(data.length)) {
return;
}
// Get parser for this data type
parser = this._getParser(name, ext);
if (!parser) {
return;
}
// Process data
try {
this.fire('data:loading', { filename: name, format: parser.ext });
layer = parser.processor.call(this, data, parser.ext);
this.fire('data:loaded', {
layer: layer,
filename: name,
format: parser.ext
});
} catch (err) {
this.fire('data:error', { error: err });
}
},
_isParameterMissing: function (v, vname) {
if (typeof v === 'undefined') {
this.fire('data:error', {
error: new Error('Missing parameter: ' + vname)
});
return true;
}
return false;
},
_getParser: function (name, ext) {
var parser;
ext = ext || name.split('.').pop();
parser = this._parsers[ext];
if (!parser) {
this.fire('data:error', {
error: new Error('Unsupported file type (' + ext + ')')
});
return undefined;
}
return {
processor: parser,
ext: ext
};
},
_isFileSizeOk: function (size) {
var fileSize = (size / 1024).toFixed(4);
if (fileSize > this.options.fileSizeLimit) {
this.fire('data:error', {
error: new Error(
'File size exceeds limit (' +
fileSize + ' > ' +
this.options.fileSizeLimit + 'kb)'
)
});
return false;
}
return true;
},
_loadGeoJSON: function _loadGeoJSON(content) {
var layer;
if (typeof content === 'string') {
content = JSON.parse(content);
}
layer = this.options.layer(content, this.options.layerOptions);
if (layer.getLayers().length === 0) {
throw new Error('GeoJSON has no valid layers.');
}
if (this.options.addToMap) {
layer.addTo(this._map);
}
return layer;
},
});
var FileLayerLoad = L.Control.extend({
statics: {
TITLE: 'Load local file (GeoJSON)',
LABEL: '&#8965;'
},
options: {
position: 'topleft',
fitBounds: true,
layerOptions: {},
addToMap: true,
fileSizeLimit: 1024
},
initialize: function (options) {
L.Util.setOptions(this, options);
this.loader = null;
},
onAdd: function (map) {
this.loader = L.FileLayer.fileLoader(map, this.options);
this.loader.on('data:loaded', function (e) {
// Fit bounds after loading
if (this.options.fitBounds) {
window.setTimeout(function () {
map.fitBounds(e.layer.getBounds());
}, 500);
}
}, this);
// Initialize Drag-and-drop
this._initDragAndDrop(map);
// Initialize map control
return this._initContainer();
},
_initDragAndDrop: function (map) {
var callbackName;
var thisLoader = this.loader;
var dropbox = map._container;
var callbacks = {
dragenter: function () {
map.scrollWheelZoom.disable();
},
dragleave: function () {
map.scrollWheelZoom.enable();
},
dragover: function (e) {
e.stopPropagation();
e.preventDefault();
},
drop: function (e) {
e.stopPropagation();
e.preventDefault();
thisLoader.loadMultiple(e.dataTransfer.files);
map.scrollWheelZoom.enable();
}
};
for (callbackName in callbacks) {
if (callbacks.hasOwnProperty(callbackName)) {
dropbox.addEventListener(callbackName, callbacks[callbackName], false);
}
}
},
_initContainer: function () {
var thisLoader = this.loader;
// Create a button, and bind click on hidden file input
var fileInput;
var zoomName = 'leaflet-control-filelayer';
var barName = 'leaflet-bar';
var partName = barName + '-part';
var container = L.DomUtil.create('div', zoomName + ' ' + barName);
var link = L.DomUtil.create('button', zoomName + '-in ' + partName, container);
link.innerHTML = L.Control.FileLayerLoad.LABEL;
link.title = L.Control.FileLayerLoad.TITLE;
// Create an invisible file input
fileInput = L.DomUtil.create('input', 'hidden', container);
fileInput.type = 'file';
fileInput.multiple = 'multiple';
if (!this.options.formats) {
fileInput.accept = '.json,.geojson';
} else {
fileInput.accept = this.options.formats.join(',');
}
fileInput.style.display = 'none';
// Load on file change
fileInput.addEventListener('change', function () {
thisLoader.loadMultiple(this.files);
// reset so that the user can upload the same file again if they want to
this.value = '';
}, false);
L.DomEvent.disableClickPropagation(link);
L.DomEvent.on(link, 'click', function (e) {
fileInput.click();
e.preventDefault();
});
return container;
}
});
L.FileLayer = {};
L.FileLayer.FileLoader = FileLoader;
L.FileLayer.fileLoader = function (map, options) {
return new L.FileLayer.FileLoader(map, options);
};
L.Control.FileLayerLoad = FileLayerLoad;
L.Control.fileLayerLoad = function (options) {
return new L.Control.FileLayerLoad(options);
};
}, window));

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

@ -90,81 +90,68 @@ var doReset = function(notify=true, initialReset=false){
//-----------------------------------------------------------------
var doDownloadTile = function(){
if(gCurrentZoneLayerName !== null){
var polygon = null;
if(gCurrentCustomPolygon !== null){
polygon = gCurrentCustomPolygon;
}else if(gCurrentZone !== null){
polygon = gCurrentZone;
}else{
new Noty({
type: "info",
text: "You must select a zone or draw a polygon to download data",
layout: 'topCenter',
timeout: 3000,
theme: 'metroui'
}).show();
return
}
var request = {
"type": "download",
"dataset": gCurrentDataset,
"experiment": EXP_NAME,
"polygon": polygon.toGeoJSON(),
"classes": CLASSES,
"zoneLayerName": gCurrentZoneLayerName,
"SESSION": SESSION_ID
};
var outputLayer = L.imageOverlay("", polygon.getBounds(), {pane: "labels"}).addTo(gMap);
$.ajax({
type: "POST",
url: gBackendURL + "predTile",
data: JSON.stringify(request),
timeout: 0,
success: function(data, textStatus, jqXHR){
new Noty({
type: "success",
text: "Tile download ready!",
layout: 'topCenter',
timeout: 5000,
theme: 'metroui'
}).show();
var pngURL = window.location.origin + "/" + data["downloadPNG"];
var tiffURL = window.location.origin + "/" + data["downloadTIFF"];
var statisticsURL = window.location.origin + "/" + data["downloadStatistics"];
$("#lblPNG").html("<a href='"+pngURL+"' target='_blank'>Download PNG</a>");
$("#lblTIFF").html("<a href='"+tiffURL+"' target='_blank'>Download TIFF</a>");
$("#lblStatistics").html("<a href='"+statisticsURL+"' target='_blank'>Download Class Statistics</a>");
outputLayer.setUrl(pngURL);
},
error: notifyFail,
dataType: "json",
contentType: "application/json"
});
new Noty({
type: "success",
text: "Sent tile download request, please wait for 2-3 minutes. When the request is complete the download links will appear underneath the 'Download' button.",
layout: 'topCenter',
timeout: 10000,
theme: 'metroui'
}).show();
var polygon = null;
if(gCurrentCustomPolygon !== null){
polygon = gCurrentCustomPolygon;
}else if(gCurrentZone !== null){
polygon = gCurrentZone;
}else{
new Noty({
type: "info",
text: "Downloads are not enabled for this dataset",
text: "You must select a zone or draw a polygon to download data",
layout: 'topCenter',
timeout: 3000,
theme: 'metroui'
}).show();
return
}
var request = {
"type": "download",
"dataset": gCurrentDataset,
"experiment": EXP_NAME,
"polygon": polygon.toGeoJSON(),
"classes": CLASSES,
"zoneLayerName": null,
"SESSION": SESSION_ID
};
var outputLayer = L.imageOverlay("", polygon.getBounds(), {pane: "labels"}).addTo(gMap);
$.ajax({
type: "POST",
url: gBackendURL + "predTile",
data: JSON.stringify(request),
timeout: 0,
success: function(data, textStatus, jqXHR){
new Noty({
type: "success",
text: "Tile download ready!",
layout: 'topCenter',
timeout: 5000,
theme: 'metroui'
}).show();
var pngURL = window.location.origin + "/" + data["downloadPNG"];
var tiffURL = window.location.origin + "/" + data["downloadTIFF"];
var statisticsURL = window.location.origin + "/" + data["downloadStatistics"];
$("#lblPNG").html("<a href='"+pngURL+"' target='_blank'>Download PNG</a>");
$("#lblTIFF").html("<a href='"+tiffURL+"' target='_blank'>Download TIFF</a>");
$("#lblStatistics").html("<a href='"+statisticsURL+"' target='_blank'>Download Class Statistics</a>");
outputLayer.setUrl(pngURL);
},
error: notifyFail,
dataType: "json",
contentType: "application/json"
});
new Noty({
type: "success",
text: "Sent tile download request, please wait for 2-3 minutes. When the request is complete the download links will appear underneath the 'Download' button.",
layout: 'topCenter',
timeout: 10000,
theme: 'metroui'
}).show();
};

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

@ -249,13 +249,15 @@ var getZoneMap = function(zoneSetId, name, url){
}
var forEachFeatureOnClick = function(feature, layer) {
console.debug("clicked")
layer.on('click', function (e) {
currentZone = layer;
gCurrentZone = layer;
for(k in gZonemaps){
gZonemaps[k].setStyle(DEFAULT_ZONE_STYLE(gZoneMapsWeight[k]));
}
layer.setStyle(HIGHLIGHTED_ZONE_STYLE);
layer.bringToFront();
var nameKey = e.target.feature.properties["KEY"];
if (nameKey !== null){
$("#lblZoneName").html(e.target.feature.properties[nameKey]);

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

@ -121,6 +121,7 @@
<script src="js/leaflet.js" type="text/javascript"></script>
<script src="js/leaflet-slider.js" type="text/javascript"></script>
<script src="js/leaflet-sidebar.min.js" type="text/javascript"></script>
<script src="js/leaflet.filelayer.js" type="text/javascript"></script>
<script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js" type="text/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/leaflet-easybutton@2/src/easy-button.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.14/leaflet.draw.js" type="text/javascript"></script>
@ -139,7 +140,7 @@
<script type="text/javascript">
var zonePickerControl;
//----------------------------------------------------------------------
// Runtime entrance
//----------------------------------------------------------------------
@ -253,113 +254,75 @@
addCustomLogoControl();
addZoomControls();
addDrawControls();
var basemapPickerControl = addBasemapPickerControl(gBasemaps);
var basemapPickerControlContainer = basemapPickerControl.getContainer();
var zonePickerControl = addZonePickerControl(gZonemaps);
zonePickerControl = addZonePickerControl(gZonemaps);
var zonePickerControlContainer = zonePickerControl.getContainer();
var opacitySlider = addOpacitySlider();
addInferenceWindowSizeSlider();
addCorrectionWindowSizeSlider();
addSharpnessToggleSlider();
// addInferenceWindowSizeSlider();
// addCorrectionWindowSizeSlider();
// addSharpnessToggleSlider();
addSideBar();
var uploadControl = addUploadControl();
uploadControl.loader.on('data:loaded', function (e) {
var zoneName = e.filename;
if(zoneName in gZonemaps){ // if there is an entry in gZonemaps with the same filename then we have already loaded this file
console.debug("Error, already loaded this file");
}else{
if(gCurrentZoneLayerName !== null){
console.debug("Removing existing zone layer");
gMap.removeLayer(gZonemaps[gCurrentZoneLayerName]);
}
gZoneMapsWeight[zoneName] = 3;
gZonemaps[zoneName] = e.layer;
e.layer.addTo(gMap);
zonePickerControl.addBaseLayer(e.layer, zoneName);
}
});
uploadControl.loader.on('data:error', function (error) {
console.error(error); //TODO: alert a reasonable error message
});
//----------------------------------------------------------------------
// Register handlers. These are all defined in `js/handlers.js`
//----------------------------------------------------------------------
addInferenceMouseHandlers();
addDrawControlHandlers();
addOpacityKeyHandlers(opacitySlider);
addRetrainKeyHandler();
addGUIKeyHandlers();
document.getElementById("map").onselectstart = function(){ return false;} // remove the default behavior of the shift key. TODO: should this be on "document" or "body" instead of "map"?
gMap.on('contextmenu',function(e){}); // disables the context menu
gMap.on('dblclick',function(e){});
gMap.doubleClickZoom.disable();
gMap.boxZoom.disable();
$(zonePickerControlContainer).find('input[type=radio]').change(function() {
var name = $(this).siblings()[0].innerHTML.trim();
gCurrentZoneLayerName = name;
console.debug("Switched zone layer to: " + name);
// $(zonePickerControlContainer).find('input[type=radio]').change(function() {
// var name = $(this).siblings()[0].innerHTML.trim();
// gCurrentZoneLayerName = name;
// console.debug("Switched zone layer to: " + name);
// });
// $(basemapPickerControlContainer).find('input[type=radio]').change(function() {
// var name = $(this).siblings()[0].innerHTML.trim();
// gCurrentBasemapLayerName = name;
// console.debug("Switched basemap layer to: " + name);
// });
gMap.on('baselayerchange', function(e) {
//https://gis.stackexchange.com/questions/171911/getting-current-layer-in-control-event-of-leaflet
console.log(e);
});
$(basemapPickerControlContainer).find('input[type=radio]').change(function() {
var name = $(this).siblings()[0].innerHTML.trim();
gCurrentBasemapLayerName = name;
console.debug("Switched basemap layer to: " + name);
});
$(document).keydown(function(e){
gShiftKeyDown = e.shiftKey;
gCtrlKeyDown = e.ctrlKey;
});
$(document).keyup(function(e){
gShiftKeyDown = e.shiftKey;
gCtrlKeyDown = e.ctrlKey;
});
$(document).keydown(function(e) {
if(document.activeElement == document.body){
if(e.which == 97 || e.which == 65) { // "a"
gVisible = false;
gMap.getPane('labels').style.opacity = 0.0;
opacitySlider.slider.value = 0;
opacitySlider._updateValue();
} else if(e.which == 115 || e.which == 83) { // "s"
if(gVisible){
gVisible = false;
gMap.getPane('labels').style.opacity = 0.0;
opacitySlider.slider.value = 0;
opacitySlider._updateValue();
}else{
gVisible = true;
gMap.getPane('labels').style.opacity = 1.0;
opacitySlider.slider.value = 100;
opacitySlider._updateValue();
}
} else if(e.which == 100 || e.which == 68) { "d"
gVisible = true;
gMap.getPane('labels').style.opacity = 1.0;
opacitySlider.slider.value = 100
opacitySlider._updateValue();
} else if((e.which == 114 || e.which == 82) && !gCtrlKeyDown){ "r"
doRetrain();
} else if(e.which == 37){ "left arrow"
var currentOpacity = parseFloat(gMap.getPane('labels').style.opacity);
if (currentOpacity >= 0.05){
currentOpacity -= 0.05;
gMap.getPane('labels').style.opacity = currentOpacity;
opacitySlider.slider.value = currentOpacity*100;
opacitySlider._updateValue();
}
e.preventDefault()
} else if(e.which == 39){ "right arrow"
var currentOpacity = parseFloat(gMap.getPane('labels').style.opacity);
if (currentOpacity <= 0.95){
currentOpacity += 0.05;
gMap.getPane('labels').style.opacity = currentOpacity;
opacitySlider.slider.value = currentOpacity*100;
opacitySlider._updateValue();
}
e.preventDefault()
} else if(e.which == 38 || e.which == 40){
e.preventDefault()
} else if(gCtrlKeyDown && e.which==90){
doUndo();
}
}
});
$("#btnRetrain").click(doRetrain);
$("#btnUndo").click(doUndo);
$("#btnReset").click(function(){doReset(true, initialReset=false)});
$("#btnDownload").click(doDownloadTile);
$("#btnNewClass").click(function(){
var newClassIdx = CLASSES.length + 1;

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

@ -8,7 +8,7 @@
<meta name="author" content="">
<link rel="icon" href="favicon.ico">
<title>Microsoft AI for Earth</title>
<title>Microsoft AI for Earth - Land cover mapping platform</title>
<!-- Core CSS -->
<link href="css/leaflet.css" rel="stylesheet" />
@ -16,6 +16,7 @@
<link href="css/leaflet-sidebar.css" rel="stylesheet" />
<link href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-easybutton@2/src/easy-button.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.14/leaflet.draw.css">
<link href="css/noty.css" rel="stylesheet">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
@ -120,11 +121,15 @@
<script src="js/leaflet.js" type="text/javascript"></script>
<script src="js/leaflet-slider.js" type="text/javascript"></script>
<script src="js/leaflet-sidebar.min.js" type="text/javascript"></script>
<script src="js/leaflet.filelayer.js" type="text/javascript"></script>
<script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js" type="text/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/leaflet-easybutton@2/src/easy-button.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.14/leaflet.draw.js" type="text/javascript"></script>
<!-- Application JavaScript
================================================== -->
<script src="js/components.js" type="text/javascript"></script>
<script src="js/handlers.js" type="text/javascript"></script>
<script src="js/main.js" type="text/javascript"></script>
<script src="js/utils.js" type="text/javascript"></script>
<script src="js/globals.js" type="text/javascript"></script>
@ -135,41 +140,18 @@
<script type="text/javascript">
//----------------------------------------------------------------------
// Load potential datasets
//----------------------------------------------------------------------
var DATASETS = (function () {
var json = null;
$.ajax({
'async': false,
'url': 'datasets.json',
'dataType': "json",
'success': function(data){
json = data;
}
});
return json;
})();
var zonePickerControl;
//----------------------------------------------------------------------
// Runtime entrance
//----------------------------------------------------------------------
$(document).ready(function(){
//----------------------------------------------------------------------
// Remove the default behavior of the shift key
//----------------------------------------------------------------------
document.getElementById("map").onselectstart = function() {
return false;
}
//----------------------------------------------------------------------
// Parse URL arguments
//----------------------------------------------------------------------
let args = getURLArguments()
EXP_NAME = args.userID
BACKEND_URL = BACKEND_ENDPOINTS[args.backendID]["url"];
gBackendURL = SERVERS[args.backendID]["url"];
if(!(args.dataset in DATASETS) && (args.dataset !== null)){
args.dataset = null;
@ -184,19 +166,14 @@
if(args.dataset === null){
args.dataset = "esri_world_imagery";
}
DATASET = args.dataset;
gCurrentDataset = "atimur";
if(args.cachedModel !== null){
doLoad(args.cachedModel); // load a cached version of the model
}else{
doReset(false, true); // reset the backend server
//doReset(false, true); // reset the backend server
}
//----------------------------------------------------------------------
// Get a session id to include in all API requests
//----------------------------------------------------------------------
SESSION_ID = getRandomString(10);
//----------------------------------------------------------------------
// Start user study if necessary
//----------------------------------------------------------------------
@ -209,19 +186,20 @@
//----------------------------------------------------------------------
// Load the dataset specific shapes
//----------------------------------------------------------------------
// TODO: This needs to be refactored out of main() somehow. e.g. If I want to create a new interface that handles zone clicks differently then it should be easy to do, while currently I would have to break everything about how zones are currently handled.
if(DATASETS[gCurrentDataset]["shapeLayers"] !== null){
for(zoneSetId in DATASETS[gCurrentDataset]["shapeLayers"]){
// for each shapeLayer in the current dataset we need to load the GeoJSON in `zoneMaps`
if(DATASETS[DATASET]["shapeLayers"] !== null){
for(zoneSetId in DATASETS[DATASET]["shapeLayers"]){
console.debug("Zonesetid", zoneSetId);
var zoneName = DATASETS[DATASET]["shapeLayers"][zoneSetId]["name"];
zoneMapsWeight[zoneName] = DEFAULT_ZONE_LINE_WEIGHTS[zoneSetId];
zoneMaps[zoneName] = L.geoJSON(null, {
style: DEFAULT_ZONE_STYLE(zoneMapsWeight[zoneName]),
var zoneName = DATASETS[gCurrentDataset]["shapeLayers"][zoneSetId]["name"];
gZoneMapsWeight[zoneName] = DEFAULT_ZONE_LINE_WEIGHTS[zoneSetId];
gZonemaps[zoneName] = L.geoJSON(null, {
style: DEFAULT_ZONE_STYLE(gZoneMapsWeight[zoneName]),
onEachFeature: forEachFeatureOnClick,
pane: "polygons"
});
getZoneMap(zoneSetId, zoneName, DATASETS[DATASET]["shapeLayers"][zoneSetId]["shapesFn"]);
getZoneMap(zoneSetId, zoneName, DATASETS[gCurrentDataset]["shapeLayers"][zoneSetId]["shapesFn"]);
}
}
@ -229,12 +207,8 @@
// Setup map layers
//----------------------------------------------------------------------
var esriLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
maxZoom: 20,
maxNativeZoom: 17,
attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
})
// Create the basemap for the current dataset
var baseLayer = L.tileLayer(DATASETS[gCurrentDataset]["basemapLayer"]["url"], DATASETS[gCurrentDataset]["basemapLayer"]["args"]);
var custom1 = L.tileLayer('tiles/atimur_healthy_veg/{z}/{x}/{y}.png', {
tms: true,
minZoom: 8,
@ -243,380 +217,124 @@
attribution: 'Georeferenced Image'
});
var osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 20,
maxNativeZoom: 17,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
})
// This is the list of basemaps that will be displayed on the control in bottom left of GUI
gCurrentBasemapLayerName = DATASETS[gCurrentDataset]["metadata"]["imageryName"];
gBasemaps[DATASETS[gCurrentDataset]["metadata"]["imageryName"]] = baseLayer;
gBasemaps["Healthy Vegetation"] = custom1;
gBasemaps["OpenStreetMap"] = getOSMLayer();
gBasemaps["ESRI World Imagery"] = getESRILayer();
var baseLayer = L.tileLayer(DATASETS[DATASET]["basemapLayer"]["url"], DATASETS[DATASET]["basemapLayer"]["args"]);
START_CENTER = DATASETS[DATASET]["basemapLayer"]["initialLocation"];
START_ZOOM = DATASETS[DATASET]["basemapLayer"]["initialZoom"];
interestingLocations = []
var cities = L.layerGroup(interestingLocations);
// Setup the initial layers to be displayed on the map. This consists of the basemap from the dataset and, if defined, the first shape layer.
var initialLayers = [baseLayer];
if(DATASETS[DATASET]["shapeLayers"] !== null){
initialLayers.push(zoneMaps[DATASETS[DATASET]["shapeLayers"][0]["name"]]);
currentZoneLayerName = DATASETS[DATASET]["shapeLayers"][0]["name"];
if(DATASETS[gCurrentDataset]["shapeLayers"] !== null){
initialLayers.push(gZonemaps[DATASETS[gCurrentDataset]["shapeLayers"][0]["name"]]);
gCurrentZoneLayerName = DATASETS[gCurrentDataset]["shapeLayers"][0]["name"];
}
map = L.map('map', {
// Setup the global map object
gMap = L.map('map', {
zoomControl: false,
crs: L.CRS.EPSG3857, // this is a default, but I'm setting it to be explicit about what CRS we are in
center: START_CENTER,
zoom: START_ZOOM,
crs: L.CRS.EPSG3857, // this is the projection CRS (EPSG:3857), but it is different than the data CRS (EPSG:4326). See https://gis.stackexchange.com/questions/225765/leaflet-map-crs-is-3857-but-coordinates-4326/225786.
center: DATASETS[gCurrentDataset]["basemapLayer"]["initialLocation"],
zoom: DATASETS[gCurrentDataset]["basemapLayer"]["initialZoom"],
keyboard: false,
minZoom: baseLayer.options.minZoom,
layers: initialLayers
});
map.createPane('polygons');
map.getPane('polygons').style.zIndex = 2000;
gMap.createPane('customPolygons');
gMap.getPane('customPolygons').style.zIndex = 2001;
map.createPane('labels');
map.getPane('labels').style.zIndex = 201;
gMap.createPane('polygons');
gMap.getPane('polygons').style.zIndex = 2000;
gMap.createPane('labels');
gMap.getPane('labels').style.zIndex = 201;
gMap.createPane('downloadOutput');
gMap.getPane('downloadOutput').style.zIndex = 200;
map.createPane('downloadOutput');
map.getPane('downloadOutput').style.zIndex = 200;
for(var i=0;i<interestingLocations.length;i++){
interestingLocations[i].on('click', function(e){
map.setView(e.latlng, 13);
});
}
//----------------------------------------------------------------------
// Setup layer picker
// Create UI components. These are all defined in `js/components.js`.
//----------------------------------------------------------------------
addCustomLogoControl();
addZoomControls();
addDrawControls();
var basemapPickerControl = addBasemapPickerControl(gBasemaps);
var basemapPickerControlContainer = basemapPickerControl.getContainer();
var baseMapString = DATASETS[DATASET]["metadata"]["imageryName"];
currentBasemapLayerName = baseMapString;
zonePickerControl = addZonePickerControl(gZonemaps);
var zonePickerControlContainer = zonePickerControl.getContainer();
var baseMaps = {};
baseMaps[baseMapString] = baseLayer;
baseMaps["Healthy Vegetation"] = custom1;
baseMaps["OpenStreetMap"] = osmLayer;
baseMaps["'Google Earth' like Imagery"] = esriLayer;
var opacitySlider = addOpacitySlider();
// addInferenceWindowSizeSlider();
// addCorrectionWindowSizeSlider();
// addSharpnessToggleSlider();
var basemapLayerControl = L.control.layers(
baseMaps, null, {
collapsed:false,
position:"bottomleft"
}
).addTo(map);
addSideBar();
var basemapLayerControlContainer = basemapLayerControl.getContainer();
var zoneControl = L.control.layers(
zoneMaps, null, {
collapsed:false,
position:"bottomleft"
}
).addTo(map);
var uploadControl = addUploadControl();
uploadControl.loader.on('data:loaded', function (e) {
var zoneName = e.filename;
var zoneControlContainer = zoneControl.getContainer();
//---------------------------------------------------------------------
// Setup AI4E Branding
//----------------------------------------------------------------------
var logoControl = $("<div class='leaflet-control logo-area'></div>");
logoControl.append("<span class='logo-text'>Microsoft AI for Earth</span>");
logoControl.append("<br/>");
logoControl.append("<span class='logo-text-small'>Version: 0.9</span>");
logoControl.append("<br/>");
logoControl.append("<span class='logo-text-small'>Location: "+DATASETS[DATASET]["metadata"]["displayName"]+"</span>");
$(".leaflet-top.leaflet-left").append(logoControl)
$("#lblModelInput").html(DATASETS[DATASET]["metadata"]["locationName"]);
//----------------------------------------------------------------------
// Custom initialization of the map zoom controls so that we can
// position it where we want
//----------------------------------------------------------------------
L.control.zoom({
position:'topleft'
}).addTo(map);
//----------------------------------------------------------------------
// Setup the leaflet-easybutton plugin to reset the map to its initial
// position
//----------------------------------------------------------------------
L.easyButton(
'fa-undo', function(btn, map) {
map.closePopup();
map.setView(START_CENTER, START_ZOOM);
},
).addTo(map);
//----------------------------------------------------------------------
// Setup leaflet-slider plugin
// First slider is to control map opacity
//----------------------------------------------------------------------
opacitySlider = L.control.slider( // opacity slider
function(value){
map.getPane('labels').style.opacity = value / 100.0;
}, {
position: 'bottomleft',
id: 'opacity_slider',
orientation: 'horizontal',
collapsed: true,
syncSlider: true,
min: 0,
max: 100,
value: 100,
logo: "Opacity",
size: "171px"
}
);
opacitySlider.addTo(map);
//----------------------------------------------------------------------
// Setup the selection window size slider
//----------------------------------------------------------------------
windowSizeSlider = L.control.slider( // opacity slider
function(value){
SELECTION_SIZE = value;
}, {
position: 'bottomleft',
id: 'window_size_slider',
orientation: 'horizontal',
collapsed: true,
syncSlider: true,
min: 10,
max: 700,
value: 300,
logo: "Window Size",
size: "171px"
}
);
windowSizeSlider.addTo(map);
//----------------------------------------------------------------------
// Setup the sharpness slider to control which type of image is shown
//----------------------------------------------------------------------
L.control.slider( // sharpness slider
function(value){
soft0_hard1 = value;
for(idx=0; idx<currentPatches.length; idx++){
var tActiveImgIdx = currentPatches[idx]["activeImgIdx"];
var srcs = currentPatches[idx]["patches"][tActiveImgIdx]["srcs"];
currentPatches[idx]["imageLayer"].setUrl(srcs[soft0_hard1]);
}
if(currentPatches.length>0){
var idx = currentPatches.length - 1;
for(var tActiveImgIdx=0; tActiveImgIdx<currentPatches[idx]["patches"].length; tActiveImgIdx++){
var srcs = currentPatches[idx]["patches"][tActiveImgIdx]["srcs"];
$("#exampleImage_"+tActiveImgIdx).attr("src", srcs[soft0_hard1]);
}
}
}, {
position: 'bottomleft',
id: 'soft_hard_slider',
orientation: 'horizontal',
collapsed: true,
syncSlider: true,
min: 0,
max: 1,
value: 1,
logo: "Sharpness",
size: "171px"
}
).addTo(map);
//----------------------------------------------------------------------
// Setup leaflet-sidebar-v2 and open the "#home" tab
//----------------------------------------------------------------------
var sidebar = L.control.sidebar(
'sidebar', {
position: 'right'
}
).addTo(map);
sidebar.open("home")
//----------------------------------------------------------------------
// Setup map selection handlers
//----------------------------------------------------------------------
map.addEventListener('mousemove', function(e){
// Choose style
var curSelPoly = null;
if(!shiftKeyDown){
curSelPoly = getPolyAround(e.latlng, CORRECTION_SIZE);
if(zoneName in gZonemaps){ // if there is an entry in gZonemaps with the same filename then we have already loaded this file
console.debug("Error, already loaded this file");
}else{
curSelPoly = getPolyAround(e.latlng, SELECTION_SIZE);
}
if(selectionBox === null){
selectionBox = L.polygon(curSelPoly, {
color: "#000000",
fillColor: "#ffffff",
weight: 2
});
selectionBox.addTo(map);
}else{
if(!animating){
selectionBox.setStyle({
color: "#000000",
fillColor: "#ffffff",
weight: 2
});
if(gCurrentZoneLayerName !== null){
console.debug("Removing existing zone layer");
gMap.removeLayer(gZonemaps[gCurrentZoneLayerName]);
}
selectionBox.setLatLngs(curSelPoly);
gZoneMapsWeight[zoneName] = 3;
gZonemaps[zoneName] = e.layer;
e.layer.addTo(gMap);
zonePickerControl.addBaseLayer(e.layer, zoneName);
}
});
map.addEventListener('click', function(e){
var curSelPoly = null;
if(shiftKeyDown){
// Run the inference path
curSelPoly = getPolyAround(e.latlng, SELECTION_SIZE);
if(currentSelection === null){ // This condition creates the red selection box on the first click
currentSelection = L.polygon(curSelPoly, {
color: "#ff0000",
fillColor: "#ffffff",
weight: 2
});
currentSelection.addTo(map);
}else{
currentSelection.setLatLngs(curSelPoly);
}
requestPatches(curSelPoly);
}else{
// Run the add sample path
if(currentSelection !== null){
if(isPointInsidePolygon(e.latlng, currentSelection)){
if(currentBasemapLayerName == DATASETS[DATASET]["location"][3]){
curSelPoly = getPolyAround(e.latlng, CORRECTION_SIZE);
var idx = currentPatches.length-1;
doSendCorrection(curSelPoly, idx);
var rect = L.rectangle(
[curSelPoly[0], curSelPoly[2]],
{
color: classes[selectedClassIdx]["color"],
weight: 1,
opacity: 1
//pane: "labels"
}
).addTo(map);
userPointList.push([rect, selectedClassIdx]);
map.dragging.disable();
numClicks += 1
window.setTimeout(function(){
numClicks -= 1;
if(numClicks == 0){
map.dragging.enable();
}
}, 700);
}else{
notifyFailMessage("Please add corrections using the '"+DATASETS[DATASET]["location"][3]+"' imagery layer.")
}
}else{
console.debug("Click not in selection");
}
}
}
uploadControl.loader.on('data:error', function (error) {
console.error(error); //TODO: alert a reasonable error message
});
map.on('contextmenu',function(e){}); // disables the context menu
map.on('dblclick',function(e){});
map.doubleClickZoom.disable();
map.boxZoom.disable();
//----------------------------------------------------------------------
// Register handlers. These are all defined in `js/handlers.js`
//----------------------------------------------------------------------
$(zoneControlContainer).find('input[type=radio]').change(function() {
var name = $(this).siblings()[0].innerHTML.trim();
currentZoneLayerName = name;
console.debug("Switched zone layer to: " + name);
addInferenceMouseHandlers();
addDrawControlHandlers();
addOpacityKeyHandlers(opacitySlider);
addRetrainKeyHandler();
addGUIKeyHandlers();
// $(zonePickerControlContainer).find('input[type=radio]').change(function() {
// var name = $(this).siblings()[0].innerHTML.trim();
// gCurrentZoneLayerName = name;
// console.debug("Switched zone layer to: " + name);
// });
// $(basemapPickerControlContainer).find('input[type=radio]').change(function() {
// var name = $(this).siblings()[0].innerHTML.trim();
// gCurrentBasemapLayerName = name;
// console.debug("Switched basemap layer to: " + name);
// });
gMap.on('baselayerchange', function(e) {
//https://gis.stackexchange.com/questions/171911/getting-current-layer-in-control-event-of-leaflet
console.log(e);
});
$(basemapLayerControlContainer).find('input[type=radio]').change(function() {
var name = $(this).siblings()[0].innerHTML.trim();
currentBasemapLayerName = name;
console.debug("Switched basemap layer to: " + name);
});
$(document).keydown(function(e){
shiftKeyDown = e.shiftKey;
ctrlKeyDown = e.ctrlKey;
});
$(document).keyup(function(e){
shiftKeyDown = e.shiftKey;
ctrlKeyDown = e.ctrlKey;
});
$(document).keydown(function(e) {
if(document.activeElement == document.body){
if(e.which == 97 || e.which == 65) { // "a"
visible = false;
map.getPane('labels').style.opacity = 0.0;
opacitySlider.slider.value = 0;
opacitySlider._updateValue();
} else if(e.which == 115 || e.which == 83) { // "s"
if(visible){
visible = false;
map.getPane('labels').style.opacity = 0.0;
opacitySlider.slider.value = 0;
opacitySlider._updateValue();
}else{
visible = true;
map.getPane('labels').style.opacity = 1.0;
opacitySlider.slider.value = 100;
opacitySlider._updateValue();
}
} else if(e.which == 100 || e.which == 68) { "d"
visible = true;
map.getPane('labels').style.opacity = 1.0;
opacitySlider.slider.value = 100
opacitySlider._updateValue();
} else if((e.which == 114 || e.which == 82) && !ctrlKeyDown){ "r"
doRetrain();
} else if(e.which == 37){ "left arrow"
var currentOpacity = parseFloat(map.getPane('labels').style.opacity);
if (currentOpacity >= 0.05){
currentOpacity -= 0.05;
map.getPane('labels').style.opacity = currentOpacity;
opacitySlider.slider.value = currentOpacity*100;
opacitySlider._updateValue();
}
e.preventDefault()
} else if(e.which == 39){ "right arrow"
var currentOpacity = parseFloat(map.getPane('labels').style.opacity);
if (currentOpacity <= 0.95){
currentOpacity += 0.05;
map.getPane('labels').style.opacity = currentOpacity;
opacitySlider.slider.value = currentOpacity*100;
opacitySlider._updateValue();
}
e.preventDefault()
} else if(e.which == 38 || e.which == 40){
e.preventDefault()
} else if(ctrlKeyDown && e.which==90){
doUndo();
}
}
});
$("#btnRetrain").click(doRetrain);
$("#btnUndo").click(doUndo);
$("#btnReset").click(function(){doReset(true, initialReset=false)});
$("#btnDownload").click(doDownloadTile);
$("#btnNewClass").click(function(){
var newClassIdx = classes.length + 1;
var newClassIdx = CLASSES.length + 1;
var newColor = getRandomColor();
var newClassElement = $("<div class='radio'>");
@ -641,7 +359,7 @@
$("#classList").append(newClassElement);
classes.push({
CLASSES.push({
"name": "Class " + newClassIdx,
"color": newColor,
"count": 0
@ -657,8 +375,8 @@
$('.radio label').removeClass("selected");
$(this).parent().addClass("selected");
selectedClassIdx = findClassByName(this.value);
console.debug(this.value + " " + selectedClassIdx);
gSelectedClassIdx = findClassByName(this.value);
console.debug(this.value + " " + gSelectedClassIdx);
});
$(document).on('click', '.classNameEdit', function(){
@ -670,7 +388,7 @@
$(this).parent().siblings(".jscolor").attr("data-class-name", newName);
var classIdx = findClassByName(oldName)
classes[classIdx]["name"] = newName;
CLASSES[classIdx]["name"] = newName;
});
@ -678,7 +396,7 @@
//----------------------------------------------------------------------
// Setup the example images list
//----------------------------------------------------------------------
for(var i=0; i<ENDPOINTS.length; i++){
for(var i=0; i<SERVERS.length; i++){
var img = $("<img class='exampleImage'>");
img.attr("im-id", i);
img.attr("id", "exampleImage_"+i);
@ -689,13 +407,14 @@
$(".exampleImage").removeClass("active");
$(this).addClass("active");
var idx = currentPatches.length-1;
activeImgIdx = $(this).attr("im-id");
var idx = gCurrentPatches.length-1;
gActiveImgIdx = $(this).attr("im-id");
var srcs = currentPatches[idx]["patches"][activeImgIdx]["srcs"];
currentPatches[idx]["imageLayer"].setUrl(srcs[soft0_hard1]);
currentPatches[idx]["activeImgIdx"] = activeImgIdx;
$(this).attr("src", srcs[soft0_hard1]);
var srcs = gCurrentPatches[idx]["patches"][gActiveImgIdx]["srcs"];
let tSelection = gDisplayHard ? "hard" : "soft";
gCurrentPatches[idx]["imageLayer"].setUrl(srcs[tSelection]);
gCurrentPatches[idx]["activeImgIdx"] = gActiveImgIdx;
$(this).attr("src", srcs[tSelection]);
});
});

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

@ -18,6 +18,7 @@ import bottle
SESSION_BASE_PATH = './data/session'
SESSION_FOLDER = SESSION_BASE_PATH + "/" + datetime.datetime.now().strftime('%Y-%m-%d')
def manage_session_folders():
if not os.path.exists(SESSION_BASE_PATH):
os.makedirs(SESSION_BASE_PATH)
@ -26,17 +27,20 @@ def manage_session_folders():
shutil.rmtree(SESSION_BASE_PATH)
os.makedirs(SESSION_FOLDER)
def authenticated(func):
'''Based on suggestion from https://stackoverflow.com/questions/11698473/bottle-hooks-with-beaker-session-middleware-and-checking-logins
'''
def wrapped(*args, **kwargs):
if 'logged_in' in bottle.request.session:
return func(*args, **kwargs)
else:
LOGGER.info("User not logged in")
bottle.abort(401, "Sorry, access denied.")
#bottle.redirect('/login')
return wrapped
def authenticated(LOCAL_MANAGER):
def authenticated_inner(func):
'''Based on suggestion from https://stackoverflow.com/questions/11698473/bottle-hooks-with-beaker-session-middleware-and-checking-logins
'''
def wrapped(*args, **kwargs):
if ('logged_in' in bottle.request.session) or LOCAL_MANAGER.run_local:
return func(*args, **kwargs)
else:
LOGGER.info("User not logged in")
bottle.abort(401, "Sorry, access denied.")
#bottle.redirect('/login')
return wrapped
return authenticated_inner
def load_authorized():
return bottle.template('redirecting.tpl')

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

@ -47,20 +47,28 @@ import login
import login_config
from log import setup_logging, LOGGER
#---------------------------------------------------------------------------------------
# Session handling code
#---------------------------------------------------------------------------------------
from Session import Session, SessionFactory
SESSION_MAP = dict() # keys are bottle.request.session.id and values are corresponding Session() objects
EXPIRED_SESSION_SET = set()
class LocalManager():
def __init__(self, run_local):
self.run_local = run_local
LOCAL_MANAGER = LocalManager(False)
DEFAULT_SESSION = None
def setup_sessions():
'''This method is called before every request. Adds the beaker SessionMiddleware on as request.session.
'''
bottle.request.session = bottle.request.environ['beaker.session']
bottle.request.client_ip = bottle.request.environ.get('HTTP_X_FORWARDED_FOR') or bottle.request.environ.get('REMOTE_ADDR')
def manage_sessions():
'''This method is called before every request. Checks to see if there a session assosciated with the current request.
If there is then update the last interaction time on that session.
@ -78,6 +86,7 @@ def manage_sessions():
else:
pass # not logged in, we don't care about this
def session_monitor(session_timeout_seconds=900):
''' This is a `Thread()` that is starting when the program is run. It is responsible for finding which of the `Session()` objects
in `SESSION_MAP` haven't been used recently and killing them.
@ -98,9 +107,11 @@ def session_monitor(session_timeout_seconds=900):
del SESSION_MAP[session_id]
time.sleep(5)
#---------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------
def enable_cors():
'''From https://gist.github.com/richard-flosi/3789163
@ -110,24 +121,27 @@ def enable_cors():
bottle.response.headers['Access-Control-Allow-Methods'] = 'PUT, GET, POST, DELETE, OPTIONS'
bottle.response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
def do_options():
'''This method is necessary for CORS to work (I think --Caleb)
'''
bottle.response.status = 204
return
#---------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------
@login.authenticated
@login.authenticated(LOCAL_MANAGER)
def do_load():
bottle.response.content_type = 'application/json'
data = bottle.request.json
cached_model = data["cachedModel"]
SESSION_MAP[bottle.request.session.id].reset(False, from_cached=cached_model)
SESSION_MAP[bottle.request.session.id].load(cached_model)
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).reset(False, from_cached=cached_model)
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).load(cached_model)
data["message"] = "Loaded new model from %s" % (cached_model)
data["success"] = True
@ -136,7 +150,7 @@ def do_load():
return json.dumps(data)
@login.authenticated
@login.authenticated(LOCAL_MANAGER)
def reset_model():
bottle.response.content_type = 'application/json'
data = bottle.request.json
@ -144,10 +158,10 @@ def reset_model():
initial_reset = data.get("initialReset", False)
if not initial_reset:
SESSION_MAP[bottle.request.session.id].add_entry(data) # record this interaction
SESSION_MAP[bottle.request.session.id].save(data["experiment"])
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).add_entry(data) # record this interaction
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).save(data["experiment"])
SESSION_MAP[bottle.request.session.id].reset()
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).reset()
data["message"] = "Reset model"
data["success"] = True
@ -156,19 +170,19 @@ def reset_model():
return json.dumps(data)
@login.authenticated
@login.authenticated(LOCAL_MANAGER)
def retrain_model():
bottle.response.content_type = 'application/json'
data = bottle.request.json
data["remote_address"] = bottle.request.client_ip
success, message = SESSION_MAP[bottle.request.session.id].model.retrain(**data["retrainArgs"])
success, message = SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).model.retrain(**data["retrainArgs"])
if success:
bottle.response.status = 200
encoded_model_fn = SESSION_MAP[bottle.request.session.id].save(data["experiment"])
encoded_model_fn = SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).save(data["experiment"])
data["cached_model"] = encoded_model_fn
SESSION_MAP[bottle.request.session.id].add_entry(data) # record this interaction
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).add_entry(data) # record this interaction
else:
data["error"] = message
bottle.response.status = 500
@ -179,13 +193,13 @@ def retrain_model():
return json.dumps(data)
@login.authenticated
@login.authenticated(LOCAL_MANAGER)
def record_correction():
bottle.response.content_type = 'application/json'
data = bottle.request.json
data["remote_address"] = bottle.request.client_ip
SESSION_MAP[bottle.request.session.id].add_entry(data) # record this interaction
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).add_entry(data) # record this interaction
#
tlat, tlon = data["extent"]["ymax"], data["extent"]["xmin"]
@ -200,7 +214,7 @@ def record_correction():
xs, ys = fiona.transform.transform(origin_crs, "epsg:4326", [tlon], [tlat])
#
naip_crs, naip_transform, naip_index = SESSION_MAP[bottle.request.session.id].current_transform
naip_crs, naip_transform, naip_index = SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).current_transform
xs, ys = fiona.transform.transform(origin_crs, naip_crs.to_dict(), [tlon,blon], [tlat,blat])
@ -219,7 +233,7 @@ def record_correction():
tdst_row, bdst_row = min(tdst_row, bdst_row), max(tdst_row, bdst_row)
tdst_col, bdst_col = min(tdst_col, bdst_col), max(tdst_col, bdst_col)
SESSION_MAP[bottle.request.session.id].model.add_sample(tdst_row, bdst_row, tdst_col, bdst_col, class_idx)
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).model.add_sample(tdst_row, bdst_row, tdst_col, bdst_col, class_idx)
num_corrected = (bdst_row-tdst_row) * (bdst_col-tdst_col)
data["message"] = "Successfully submitted correction"
@ -230,7 +244,7 @@ def record_correction():
return json.dumps(data)
@login.authenticated
@login.authenticated(LOCAL_MANAGER)
def do_undo():
''' Method called for POST `/doUndo`
'''
@ -238,10 +252,10 @@ def do_undo():
data = bottle.request.json
data["remote_address"] = bottle.request.client_ip
SESSION_MAP[bottle.request.session.id].add_entry(data) # record this interaction
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).add_entry(data) # record this interaction
# Forward the undo command to the backend model
success, message, num_undone = SESSION_MAP[bottle.request.session.id].model.undo()
success, message, num_undone = SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).model.undo()
data["message"] = message
data["success"] = success
data["count"] = num_undone
@ -250,14 +264,14 @@ def do_undo():
return json.dumps(data)
@login.authenticated
@login.authenticated(LOCAL_MANAGER)
def pred_patch():
''' Method called for POST `/predPatch`'''
bottle.response.content_type = 'application/json'
data = bottle.request.json
data["remote_address"] = bottle.request.client_ip
SESSION_MAP[bottle.request.session.id].add_entry(data) # record this interaction
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).add_entry(data) # record this interaction
# Inputs
extent = data["extent"]
@ -282,7 +296,7 @@ def pred_patch():
naip_data, naip_crs, naip_transform, naip_bounds, naip_index = DATASETS[dataset]["data_loader"].get_data_from_extent(extent)
naip_data = np.rollaxis(naip_data, 0, 3) # we do this here instead of get_data_by_extent because not all GeoDataTypes will have a channel dimension
SESSION_MAP[bottle.request.session.id].current_transform = (naip_crs, naip_transform, naip_index)
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).current_transform = (naip_crs, naip_transform, naip_index)
# ------------------------------------------------------
# Step 3
@ -290,7 +304,7 @@ def pred_patch():
# Apply reweighting
# Fix padding
# ------------------------------------------------------
output = SESSION_MAP[bottle.request.session.id].model.run(naip_data, extent, False)
output = SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).model.run(naip_data, extent, False)
assert len(output.shape) == 3, "The model function should return an image shaped as (height, width, num_classes)"
assert (output.shape[2] < output.shape[0] and output.shape[2] < output.shape[1]), "The model function should return an image shaped as (height, width, num_classes)" # assume that num channels is less than img dimensions
@ -319,14 +333,14 @@ def pred_patch():
return json.dumps(data)
@login.authenticated
@login.authenticated(LOCAL_MANAGER)
def pred_tile():
''' Method called for POST `/predTile`'''
bottle.response.content_type = 'application/json'
data = bottle.request.json
data["remote_address"] = bottle.request.client_ip
SESSION_MAP[bottle.request.session.id].add_entry(data) # record this interaction
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).add_entry(data) # record this interaction
# Inputs
geom = data["polygon"]
@ -347,7 +361,7 @@ def pred_tile():
bottle.response.status = 400
return json.dumps({"error": "Cannot currently download imagery with 'Basemap' based datasets"})
output = SESSION_MAP[bottle.request.session.id].model.run(naip_data, geom, True)
output = SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).model.run(naip_data, geom, True)
output_hard = output.argmax(axis=2)
print("Finished, output dimensions:", output.shape)
@ -400,7 +414,7 @@ def pred_tile():
return json.dumps(data)
@login.authenticated
@login.authenticated(LOCAL_MANAGER)
def get_input():
''' Method called for POST `/getInput`
'''
@ -408,7 +422,7 @@ def get_input():
data = bottle.request.json
data["remote_address"] = bottle.request.client_ip
SESSION_MAP[bottle.request.session.id].add_entry(data) # record this interaction
SESSION_MAP.get(bottle.request.session.id, DEFAULT_SESSION).add_entry(data) # record this interaction
# Inputs
extent = data["extent"]
@ -433,13 +447,15 @@ def get_input():
return json.dumps(data)
@login.authenticated
@login.authenticated(LOCAL_MANAGER)
def whoami():
return str(bottle.request.session) + " " + str(bottle.request.session.id)
#---------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------
def get_landing_page():
if 'logged_in' in bottle.request.session:
return bottle.static_file("landing_page.html", root="./" + ROOT_DIR + "/")
@ -458,10 +474,13 @@ def get_favicon():
def get_everything_else(filepath):
return bottle.static_file(filepath, root="./" + ROOT_DIR + "/")
#---------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------
def main():
global DEFAULT_SESSION, LOCAL_MANAGER
parser = argparse.ArgumentParser(description="AI for Earth Land Cover")
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose debugging", default=False)
@ -511,8 +530,9 @@ def main():
else:
print("Must specify 'local' or 'remote' on command line")
return
LOCAL_MANAGER.run_local = run_local
session_factory = SessionFactory(run_local, args)
DEFAULT_SESSION = session_factory.get_session(0)
# Setup logging
log_path = os.getcwd() + "/logs"
@ -524,7 +544,9 @@ def main():
app.add_hook("after_request", enable_cors)
app.add_hook("before_request", setup_sessions)
app.add_hook("before_request", manage_sessions) # before every request we want to check to make sure there are no session issues
if not run_local:
app.add_hook("before_request", manage_sessions) # before every request we want to check to make sure there are no session issues
# Login paths
app.route("/authorized", method="GET", callback=login.load_authorized)
@ -577,9 +599,11 @@ def main():
}
app = SessionMiddleware(app, session_opts)
session_monitor_thread = threading.Thread(target=session_monitor)
session_monitor_thread.setDaemon(True)
session_monitor_thread.start()
if not run_local:
session_monitor_thread = threading.Thread(target=session_monitor)
session_monitor_thread.setDaemon(True)
session_monitor_thread.start()
server = wsgi.Server(
(args.host, args.port), # bind_addr
@ -594,5 +618,6 @@ def main():
finally:
server.stop()
if __name__ == "__main__":
main()