Skip to content

Commit

Permalink
Merge pull request #160 from hotosm/enhance/post_processing
Browse files Browse the repository at this point in the history
Enhance : Post Processing on Predictions
  • Loading branch information
kshitijrajsharma authored Aug 24, 2023
2 parents 55923b8 + dd0e8b4 commit ed563e3
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 48 deletions.
59 changes: 58 additions & 1 deletion backend/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,48 @@ class PredictionParamSerializer(serializers.Serializer):
bbox = serializers.ListField(child=serializers.FloatField(), required=True)
model_id = serializers.IntegerField(required=True)
zoom_level = serializers.IntegerField(required=True)
confidence = serializers.IntegerField(required=False)
use_josm_q = serializers.BooleanField(required=False)
source = serializers.URLField(required=False)
# configs
confidence = serializers.IntegerField(required=False)
# for josm q
max_angle_change = serializers.IntegerField(required=False)
skew_tolerance = serializers.IntegerField(required=False)
# for vectorization
tolerance = serializers.FloatField(required=False)
area_threshold = serializers.FloatField(required=False)

def validate_max_angle_change(self, value):
if value is not None:
if value < 0 or value > 45:
raise serializers.ValidationError(
f"Invalid Max Angle Change: {value}, Should be between 0 and 45"
)
return value

def validate_skew_tolerance(self, value):
if value is not None:
if value < 0 or value > 45:
raise serializers.ValidationError(
f"Invalid Skew Tolerance: {value}, Should be between 0 and 45"
)
return value

def validate_tolerance(self, value):
if value is not None:
if value < 0 or value > 10:
raise serializers.ValidationError(
f"Invalid Tolerance: {value}, Should be between 0 and 10"
)
return value

def validate_area_threshold(self, value):
if value is not None:
if value < 0 or value > 20:
raise serializers.ValidationError(
f"Invalid Area Threshold: {value}, Should be between 0 and 20"
)
return value

def validate(self, data):
"""
Expand All @@ -239,6 +278,24 @@ def validate(self, data):
raise serializers.ValidationError(
f"""Invalid Zoom level : {data["zoom_level"]}, Supported between 18-22"""
)

if "max_angle_change" in data:
data["max_angle_change"] = self.validate_max_angle_change(
data["max_angle_change"]
)

if "skew_tolerance" in data:
data["skew_tolerance"] = self.validate_skew_tolerance(
data["skew_tolerance"]
)

if "tolerance" in data:
data["tolerance"] = self.validate_tolerance(data["tolerance"])

if "area_threshold" in data:
data["area_threshold"] = self.validate_area_threshold(
data["area_threshold"]
)
return data


Expand Down
17 changes: 16 additions & 1 deletion backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ def ConflateGeojson(request):
geojson_data = json.loads(request.body)["geojson"]
except json.JSONDecodeError:
return HttpResponseBadRequest("Invalid input")
print(geojson_data)

conflated_geojson = conflate_geojson(geojson_data, remove_conflated=True)

Expand Down Expand Up @@ -584,6 +585,12 @@ def post(self, request, *args, **kwargs):
vectorize(
input_path=prediction_output,
output_path=geojson_output,
tolerance=deserialized_data["tolerance"]
if "tolerance" in deserialized_data
else 0.2, # in meters
area_threshold=deserialized_data["area_threshold"]
if "area_threshold" in deserialized_data
else 3, # in sqm
)
with open(geojson_output, "r") as f:
geojson_data = json.load(f)
Expand All @@ -592,7 +599,15 @@ def post(self, request, *args, **kwargs):
feature["properties"]["building"] = "yes"
feature["properties"]["source"] = "fAIr"
if use_josm_q is True:
feature["geometry"] = othogonalize_poly(feature["geometry"])
feature["geometry"] = othogonalize_poly(
feature["geometry"],
maxAngleChange=deserialized_data["max_angle_change"]
if "max_angle_change" in deserialized_data
else 15,
skewTolerance=deserialized_data["skew_tolerance"]
if "skew_tolerance" in deserialized_data
else 15,
)

shutil.rmtree(temp_path)

Expand Down
208 changes: 162 additions & 46 deletions frontend/src/components/Layout/Start/Prediction/Prediction.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
Tooltip,
MenuItem,
Link,
TextField,
Select,
} from "@mui/material";
import L from "leaflet";
Expand Down Expand Up @@ -77,7 +78,10 @@ const Prediction = () => {
const [josmEnabled, setJosmEnabled] = useState(false);
const [modelInfo, setModelInfo] = useState(null);
const [loading, setLoading] = useState(false);

const [maxAngleChange, setMaxAngleChange] = useState(15);
const [skewTolerance, setSkewTolerance] = useState(15);
const [tolerance, setTolerance] = useState(0.5);
const [areaThreshold, setAreaThreshold] = useState(3);
const fetchData = async () => {
setLoading(true);
try {
Expand Down Expand Up @@ -193,6 +197,23 @@ const Prediction = () => {

const { accessToken } = useContext(AuthContext);

const downloadPredictions = () => {
if (!predictions) {
return;
}

const content = JSON.stringify(predictions);
const blob = new Blob([content], { type: "application/json" });
const url = URL.createObjectURL(blob);

const link = document.createElement("a");
link.href = url;
link.download = "predictions.geojson";
link.click();

URL.revokeObjectURL(url);
};

const {
mutate: callPredict,
data: returnedpredictions,
Expand All @@ -216,6 +237,10 @@ const Prediction = () => {
source: dataset.source_imagery,
confidence: confidence,
use_josm_q: use_josm_q,
max_angle_change: maxAngleChange,
skew_tolerance: skewTolerance,
tolerance: tolerance,
area_threshold: areaThreshold,
};
const startTime = new Date().getTime(); // measure start time
const res = await axios.post(`/prediction/`, body, { headers });
Expand Down Expand Up @@ -496,10 +521,37 @@ const Prediction = () => {
{map && (
<Box>
<Paper elevation={1} sx={{ padding: 2, marginTop: 0 }}>
<Typography variant="body2">
<strong> Current Zoom:</strong> {JSON.stringify(zoom)}
</Typography>
{predictions && (
<Typography variant="body2">
<strong> Predicted on:</strong> {predictionZoomlevel} Zoom
</Typography>
)}
<Typography variant="body2">
<strong> Response: </strong> {responseTime} sec
</Typography>
</Paper>
<Paper elevation={1} sx={{ padding: 2, marginTop: 0.5 }}>
<Typography variant="h8" gutterBottom>
<strong>Config</strong>
</Typography>
<Box display="flex" alignItems="center">
<Typography variant="body2" style={{ marginRight: "10px" }}>
Use JOSM Q:
</Typography>
<Switch
size="small"
checked={use_josm_q}
onChange={handleUseJosmToggle}
color="primary"
/>
</Box>
<Box display="flex" alignItems="center">
<Tooltip title="Select confidence threshold probability for filtering out low-confidence predictions">
<Typography variant="body2" style={{ marginRight: "10px" }}>
<strong>Confidence: </strong>
Confidence:
</Typography>
</Tooltip>
<FormControl size="small">
Expand All @@ -518,30 +570,78 @@ const Prediction = () => {
</Select>
</FormControl>
</Box>
<Box display="flex" alignItems="center" mt={2}>
<Typography variant="body2" style={{ marginRight: "10px" }}>
<strong>Use JOSM Q: </strong>
</Typography>
<Switch
size="small"
checked={use_josm_q}
onChange={handleUseJosmToggle}
color="primary"
/>
<Typography variant="body2">Vectorization Config :</Typography>
<Box mt={2} display="flex" alignItems="center">
<Tooltip title="Tolerance distance for simplying feature in meter">
<TextField
label="Tolerance"
type="number"
value={tolerance}
onChange={(e) => setTolerance(e.target.value)}
InputProps={{
inputProps: { min: 0, step: 1 },
style: { width: "80px", fontSize: "12px" },
}}
variant="outlined"
size="small"
/>
</Tooltip>
<Tooltip title="Area threshold to remove small feature in Sqm">
<TextField
label="Area"
type="number"
value={areaThreshold}
onChange={(e) => setAreaThreshold(e.target.value)}
InputProps={{
inputProps: { min: 0, step: 1 },
style: {
width: "80px",
fontSize: "12px",
marginLeft: "10px",
},
}}
variant="outlined"
size="small"
/>
</Tooltip>
</Box>
<Typography variant="body2">
<strong> Current Zoom:</strong> {JSON.stringify(zoom)}
</Typography>
{predictions && (
<Typography variant="body2">
<strong> Predicted on:</strong> {predictionZoomlevel} Zoom
</Typography>
{use_josm_q && (
<Box mt={2} display="flex" alignItems="center">
<Tooltip title="Accepted maximum angle deviation in degree">
<TextField
label="Max Angle Change"
type="number"
value={maxAngleChange}
onChange={(e) => setMaxAngleChange(e.target.value)}
InputProps={{
inputProps: { min: 0, step: 1 },
style: { width: "80px", fontSize: "12px" },
}}
variant="outlined"
size="small"
/>
</Tooltip>
<Tooltip title="Tolerance angle in degree">
<TextField
label="Skew Tolerance"
type="number"
value={skewTolerance}
onChange={(e) => setSkewTolerance(e.target.value)}
InputProps={{
inputProps: { min: 0, step: 1 },
style: {
width: "80px",
fontSize: "12px",
marginLeft: "10px",
},
}}
variant="outlined"
size="small"
/>
</Tooltip>
</Box>
)}
<Typography variant="body2">
<strong> Response: </strong> {responseTime} sec
</Typography>
</Paper>

<Paper elevation={1} sx={{ padding: 2, marginTop: 0.5 }}>
<Typography variant="h8" gutterBottom>
<strong>Feedback</strong>
Expand Down Expand Up @@ -600,7 +700,7 @@ const Prediction = () => {
Zoom Level: {modelInfo.trainingZoomLevel}
</Typography>
<Typography variant="body2">
Accuracy: {modelInfo.trainingAccuracy} %
Accuracy: {modelInfo.trainingAccuracy.toFixed(2)} %
</Typography>
<Typography variant="body2">
Model Size: {modelInfo.modelSize} MB
Expand All @@ -612,28 +712,44 @@ const Prediction = () => {
)}
{error && <Alert severity="error">{error}</Alert>}
{predictions && (
<LoadingButton
variant="contained"
color="secondary"
onClick={conflateFeatures}
loading={conflateLoading}
size="small"
sx={{ mt: 1 }}
>
Conflate OSM Features
</LoadingButton>
)}
{predictions && (
<LoadingButton
variant="contained"
color="secondary"
onClick={openWithJosm}
loading={josmLoading}
size="small"
sx={{ mt: 1 }}
>
Open Results with JOSM
</LoadingButton>
<Paper elevation={2} sx={{ padding: 2, marginTop: 1 }}>
<Typography variant="h8" gutterBottom>
<strong>Options</strong>
<hr></hr>
</Typography>

<LoadingButton
variant="contained"
color="secondary"
onClick={conflateFeatures}
loading={conflateLoading}
size="small"
sx={{ mt: 1 }}
>
Remove OSM Features
</LoadingButton>

<LoadingButton
variant="contained"
color="secondary"
onClick={openWithJosm}
loading={josmLoading}
size="small"
sx={{ mt: 1, mr: 1 }}
>
Open with JOSM
</LoadingButton>

<LoadingButton
variant="contained"
onClick={downloadPredictions}
color="secondary"
size="small"
sx={{ mt: 1 }}
>
Download as Geojson
</LoadingButton>
</Paper>
)}
</Grid>
<Snackbar
Expand Down

0 comments on commit ed563e3

Please sign in to comment.