Skip to content

Commit 9643162

Browse files
committed
Merge branch 'main' of https://github.com/via-cs/birdmig
2 parents 4caf0bd + 98cf6af commit 9643162

File tree

16 files changed

+10834
-4288
lines changed

16 files changed

+10834
-4288
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,5 @@ model/outputs/models/Anser_albifrons_et_classifier_model.pkl
4747
# Ignore all TeX files except .tex and .pdf
4848
/techdoc/*
4949
!techdoc/tech-doc.pdf
50-
!techdoc/tech-doc.tex
50+
!techdoc/tech-doc.tex
51+
backend/app/base.py

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
# Usage
22

33
## To start the Flask Backend:
4-
- cd into /birdmig/backend/app
5-
- enter ```uvicorn base:app --reload``` in your terminal
4+
- ```cd``` into ```/birdmig/backend/app```
5+
- Run the backend
6+
- To run the backend for showcase and documentation purposes, enter ```fastapi dev base.py``` in your terminal.
7+
- Access docs by navigating to ```localhost:8000/docs``` on your web browser
8+
- To run the backend for debugging purposes, run ```uvicorn base:app --reload``` in your terminal.
9+
- Note that this only runs the backend. It can not be navigated to on the web browser, nor will documentation be available.
610

711
## To start the React Frontend:
812

backend/.flaskenv

Lines changed: 0 additions & 2 deletions
This file was deleted.

backend/Dockerfile

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,8 @@ COPY app/data/ /code/data/
2727

2828
COPY app/models/ /code/models/
2929

30-
# Set environment variables
31-
ENV FLASK_APP=app/base.py
32-
ENV FLASK_RUN_HOST=0.0.0.0
33-
3430
# Expose the port the app runs on
3531
EXPOSE 8000
3632

3733
# Command to run the application
38-
CMD ["flask", "run"]
34+
CMD ["fastapi", "dev", "base.py"]

backend/README.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
# Data format:
2-
- ```info```: string
3-
- ```sdmData```: x-y coordinate pair.
4-
- ```timeSeriesData```: array of time-series values for climate variables
5-
- ```precipiation```
6-
- ```climate```
7-
- ```temperature```
8-
91
# Running Tests:
102
Write all tests for the backend in the ```tests``` directory.
113

backend/app/base.py

Lines changed: 12 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import uvicorn
21
from fastapi import FastAPI, HTTPException
32
from fastapi.middleware.cors import CORSMiddleware
43
from fastapi.responses import FileResponse, JSONResponse
@@ -9,19 +8,12 @@
98

109
import os
1110

12-
#from .config import AppConfig
1311
import pandas as pd
1412
import numpy as np
15-
from shapely.geometry import LineString
16-
import rasterio
17-
from rasterio.warp import calculate_default_transform, reproject, Resampling
1813

1914
from PIL import Image
2015
from io import BytesIO
2116
import base64
22-
# DEBUGGING
23-
import sys
24-
from time import sleep
2517

2618
app = FastAPI()
2719
frontend_origin = "http://localhost:3000"
@@ -184,33 +176,26 @@ def get_precipitation_data(selectedYear: int):
184176
return monthly_avg.to_dict(orient='records')
185177

186178

187-
'''app.get('/bird-data/{bird_name}')
188-
def get_bird_data(bird_name):
189-
if bird_name in bird_data:
190-
return bird_data'''
191-
192-
193179
class PredictionInputs(BaseModel):
194180
bird: str
195181
year: int
196182
emissions: str
197183

198184
@app.put('/prediction')
199-
async def predict(prediction_input: PredictionInputs):
185+
async def get_predictions(prediction_input: PredictionInputs):
200186
selected_bird = prediction_input.bird
201187
selected_year = str(prediction_input.year)
202188
emission_Type = prediction_input.emissions
203-
204189
# For image data:
205190
output_path = f"../../model/outputs/png-images/{birdsModelDirs[selected_bird]}/{emission_Type}/{selected_year}.png"
206191
dataImg = Image.open(output_path)
207192
buffer = BytesIO()
208193
dataImg.save(buffer, format="png")
209194

210-
# Artificial delay to simulate model prediction time
211-
#sleep(2.5)
195+
# FastAPI requests can handle asynchronous predictions.
196+
# In practice, this would be waiting for a machine learning model to generate
197+
# predictions. In this version, waiting wasn't necessary, so it returns immediately.
212198

213-
#If we'll need to encapsulate a file, use this:
214199
return {
215200
"prediction": base64.b64encode(buffer.getvalue()).decode(),
216201
"resFormat": dataImg.format
@@ -230,8 +215,8 @@ def get_bird_info(bird_name):
230215
raise HTTPException(
231216
status_code= 404,
232217
detail= f"data for bird {bird_name} does not exist.")
233-
234-
218+
219+
235220
@app.get('/json/{filename}')
236221
def send_json(filename):
237222
climate_file_loc = os.path.join('climate_data/json_data', filename)
@@ -244,6 +229,7 @@ def send_json(filename):
244229

245230
@app.get('/get_trajectory_data')
246231
def get_trajectory_data(bird: str, birdID: str):
232+
247233
filename = f'./data/{bird}.csv'
248234
try:
249235
df = pd.read_csv(filename)
@@ -253,77 +239,29 @@ def get_trajectory_data(bird: str, birdID: str):
253239
if bird_data.empty:
254240
raise HTTPException(
255241
status_code= 404,
256-
details= 'No trajectory data found for given bird ID')
242+
detail= 'No trajectory data found for given bird ID')
257243

258244
# Convert data to dictionary format
259245
trajectory_data = bird_data[['LATITUDE', 'LONGITUDE', 'TIMESTAMP']].to_dict(orient='records')
260246
return trajectory_data
261247
except FileNotFoundError:
262248
raise HTTPException(
263249
status_code= 404,
264-
details= f'CSV file for {bird} not found')
250+
detail= f'CSV file for {filename} not found')
265251

266252

267253
@app.get('/get_bird_ids')
268254
def get_bird_ids(bird: str):
269255
filename = f'./data/{bird}.csv'
256+
270257
try:
271258
df = pd.read_csv(filename)
272259
bird_ids = df['ID'].unique().tolist()
273260
return bird_ids
274261
except FileNotFoundError:
275262
raise HTTPException(
276263
status_code=404,
277-
detail= f'CSV file for {bird} not found')
278-
279-
280-
@app.get('/get_general_migration')
281-
def get_general_migration(selected_bird: str):
282-
filename = f'./data/{selected_bird}.csv'
283-
284-
try:
285-
df = pd.read_csv(filename, low_memory=False)
286-
287-
# Convert TIMESTAMP column to datetime
288-
df['TIMESTAMP'] = pd.to_datetime(df['TIMESTAMP'])
289-
290-
# Group by ID and get the start and end months for each group
291-
grouped = df.groupby('ID')
292-
print(grouped)
293-
simplified_polylines = []
294-
for _, group in grouped:
295-
# Sort by timestamp
296-
group = group.sort_values(by='TIMESTAMP')
297-
298-
# Get coordinates
299-
coordinates = list(zip(group['LATITUDE'], group['LONGITUDE']))
300-
301-
# Apply Douglas-Peucker algorithm for simplification
302-
simplified_line = simplify_line(coordinates)
303-
304-
# Calculate direction for simplified line
305-
start_point = simplified_line[0]
306-
end_point = simplified_line[-1]
307-
delta_lat = end_point[0] - start_point[0]
308-
delta_lon = end_point[1] - start_point[1]
309-
direction = np.arctan2(delta_lat, delta_lon) * (180 / np.pi)
310-
311-
# Append simplified line with direction to simplified_polylines
312-
simplified_polylines.append({
313-
'coordinates': simplified_line,
314-
'direction': direction
315-
})
316-
317-
return {'segmented_polylines': simplified_polylines}
318-
319-
except Exception as e:
320-
raise HTTPException(status_code= 400, detail=str(e))
321-
322-
323-
def simplify_line(coordinates, tolerance=0.1):
324-
line = LineString(coordinates)
325-
simplified_line = line.simplify(tolerance, preserve_topology=False)
326-
return list(zip(*simplified_line.xy))
264+
detail= f'CSV file for {filename} not found')
327265

328266

329267
@app.get('/get_heatmap_data')
@@ -334,69 +272,4 @@ def get_heatmap_data(bird: str):
334272
heatmap_data = df[['LATITUDE', 'LONGITUDE']].values.tolist()
335273
return heatmap_data
336274
except Exception as e:
337-
raise HTTPException(status_code=400, detail= str(e))
338-
339-
source_epsg = 'epsg:4326' # Input coordinates in EPSG 4326 (WGS84)
340-
target_epsg = 'epsg:3587'
341-
342-
@app.get('/get_SDM_data')
343-
def get_SDM_data(prediction_input: PredictionInputs):
344-
selected_bird = prediction_input.bird
345-
selected_year = str(prediction_input.year)
346-
emission_Type = prediction_input.emissions
347-
348-
tiff_file_path = f"../model/outputs/tiff-images/{birdsModelDirs[selected_bird]}/{emission_Type}/{selected_year}/probability_1.0.tif"
349-
350-
with rasterio.open(tiff_file_path) as src:
351-
# Read the data from the TIFF file
352-
data = src.read(1) # Assuming a single band for simplicity
353-
354-
# Get the transformation parameters for the reprojected image
355-
transform, width, height = calculate_default_transform(src.crs, target_epsg, src.width, src.height, *src.bounds)
356-
357-
# Reproject the image
358-
data_reprojected = np.empty((height, width), dtype=data.dtype)
359-
reproject(
360-
source=data,
361-
destination=data_reprojected,
362-
src_transform=src.transform,
363-
src_crs=src.crs,
364-
dst_transform=transform,
365-
dst_crs=target_epsg,
366-
resampling=Resampling.nearest
367-
)
368-
369-
# Convert numpy array to list for JSON serialization
370-
data_list = data_reprojected.tolist()
371-
372-
# Prepare data to send to frontend
373-
converted_data = {'data': data_list}
374-
375-
# Convert data to JSON
376-
json_data = json.dumps(converted_data)
377-
378-
# Send converted data to frontend
379-
return JSONResponse(content=converted_data)
380-
381-
'''@api.route('/get_SDM_data')
382-
@cross_origin(supports_credentials=True)
383-
def get_SDM_data():
384-
tiff_file_path = f"../model/outputs/tiff-images/{birdsModelDirs[session['bird']]}/{session['emissions']}/{session['year']}/probability_1.0.tif"
385-
try:
386-
# Open the image using Pillow
387-
img = Image.open(tiff_file_path)
388-
dataset = rasterio.open(tiff_file_path)
389-
print(dataset.bounds)
390-
# Extract data from the image
391-
img_array = np.array(img)
392-
response_data = {'tiff_data': img_array.tolist()}
393-
# Send the TIFF data as JSON response
394-
return jsonify(response_data)
395-
396-
except Exception as e:
397-
return f'An error occurred: {e}'
398-
'''
399-
400-
if __name__ == '__main__':
401-
api.run(debug=True)
402-
socket_io.run(api, debug=True, port=5000)
275+
raise HTTPException(status_code=400, detail= str(e))

backend/app/config.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

backend/requirements.txt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
fastapi
2-
3-
python-dotenv
4-
jinja2
5-
pandas
6-
Shapely
7-
8-
pytest
2+
pydantic
93
requests
10-
pytest-cov
11-
flask_testing
4+
pandas
5+
numpy
6+
shapely
7+
rasterio
8+
pillow
9+
pytest

0 commit comments

Comments
 (0)