зеркало из https://github.com/microsoft/landcover.git
0. Adding local sessions
This commit is contained in:
Родитель
daf94bf9aa
Коммит
4369d4a2b5
|
@ -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: '⌅'
|
||||
},
|
||||
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 © Esri — 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: '© <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()
|
||||
|
|
Загрузка…
Ссылка в новой задаче