Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance : Post Processing on Predictions #160

Merged
merged 3 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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