-
Notifications
You must be signed in to change notification settings - Fork 48
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
http_server first commit #90
Merged
Merged
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
7d83e09
http_server first commit
JanRiedelsheimer 43d8543
Toy example request and local server response working
JanRiedelsheimer a37a645
Server responding to MIT1003 stimulus
JanRiedelsheimer 0bafeb3
added version node to server and eveluation code
JanRiedelsheimer 2d18d29
added testing
JanRiedelsheimer caefa95
testing in for loop, check type
JanRiedelsheimer f264e25
added http_model in pysaliency
JanRiedelsheimer ef72699
created example folder
JanRiedelsheimer bb7cb64
typo
JanRiedelsheimer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Specify a base image depending on the project. | ||
FROM bitnami/python:3.8 | ||
# For more complex examples, might need to use a different base image. | ||
# FROM pytorch/pytorch:1.9.1-cuda11.1-cudnn8-runtime | ||
|
||
WORKDIR /app | ||
|
||
ENV HTTP_PORT=4000 | ||
|
||
RUN apt-get update \ | ||
&& apt-get -y install gcc | ||
|
||
COPY ./requirements.txt ./ | ||
RUN python -m pip install -U pip \ | ||
&& python -m pip install -r requirements.txt | ||
|
||
COPY . ./ | ||
|
||
# This is needed for Singularity builds. | ||
EXPOSE $HTTP_PORT | ||
|
||
# The entrypoint for a container, | ||
CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:4000", "--pythonpath", ".", "model_server:app"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Submission | ||
TODO: Add a description of the submission process here. | ||
|
||
|
||
|
||
## Launching the submission container | ||
TODO: Create a docker-compose file | ||
```bash | ||
cd ./http_submission | ||
docker build -t sample_pysaliency . | ||
``` | ||
|
||
```bash | ||
docker run --name sample_pysaliency -dp 4000:4000 sample_pysaliency | ||
``` | ||
The above command will launch a container named `sample_pysaliency` and expose the port `4000` to the host machine. The container will be running in the background. | ||
|
||
To test the model server, run the sample_evaluation script (Make sure to have the `pysaliency` package installed): | ||
```bash | ||
python ./http_evaluation/sample_evaluation.py | ||
``` | ||
|
||
|
||
To delete the container, run the following command: | ||
```bash | ||
docker stop sample_pysaliency && docker rm sample_pysaliency | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
from flask import Flask, request, jsonify | ||
import numpy as np | ||
import json | ||
from PIL import Image | ||
from io import BytesIO | ||
# import pickle | ||
|
||
# Import your model here | ||
from sample_submission import MySimpleScanpathModel | ||
|
||
app = Flask("saliency-model-server") | ||
app.logger.setLevel("DEBUG") | ||
|
||
# # TODO - replace this with your model | ||
model = MySimpleScanpathModel() | ||
|
||
|
||
@app.route('/conditional_log_density', methods=['POST']) | ||
def conditional_log_density(): | ||
data = json.loads(request.form['json_data']) | ||
x_hist = np.array(data['x_hist']) | ||
y_hist = np.array(data['y_hist']) | ||
t_hist = np.array(data['t_hist']) | ||
attributes = data.get('attributes', {}) | ||
|
||
image_bytes = request.files['stimulus'].read() | ||
image = Image.open(BytesIO(image_bytes)) | ||
stimulus = np.array(image) | ||
|
||
log_density = model.conditional_log_density(stimulus, x_hist, y_hist, t_hist, attributes) | ||
return jsonify({'log_density': log_density.tolist()}) | ||
|
||
|
||
@app.route('/type', methods=['GET']) | ||
def type(): | ||
type = "ScanpathModel" | ||
version = "v1.0.0" | ||
return jsonify({'type': type, 'version': version}) | ||
|
||
|
||
def main(): | ||
app.run(host="localhost", port="4000", debug="True", threaded=True) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
cython | ||
flask | ||
gunicorn | ||
numpy | ||
|
||
# Add additional dependencies here |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import numpy as np | ||
import sys | ||
from sample_submission import MySimpleScanpathModel | ||
from pysaliency.http_models import HTTPScanpathModel | ||
sys.path.insert(0, '..') | ||
import pysaliency | ||
|
||
|
||
if __name__ == "__main__": | ||
http_model = HTTPScanpathModel("http://localhost:4000") | ||
http_model.check_type() | ||
|
||
# for testing | ||
model = MySimpleScanpathModel() | ||
|
||
# get MIT1003 dataset | ||
stimuli, fixations = pysaliency.get_mit1003(location='pysaliency_datasets') | ||
|
||
eval_fixations = fixations[fixations.scanpath_history_length > 0] | ||
|
||
for fixation_index in range(10): | ||
# get server response for one stimulus | ||
server_density = http_model.conditional_log_density( | ||
stimulus=stimuli.stimuli[eval_fixations.n[fixation_index]], | ||
x_hist=eval_fixations.x_hist[fixation_index], | ||
y_hist=eval_fixations.y_hist[fixation_index], | ||
t_hist=eval_fixations.t_hist[fixation_index] | ||
) | ||
# get model response | ||
model_density = model.conditional_log_density( | ||
stimulus=stimuli.stimuli[eval_fixations.n[fixation_index]], | ||
x_hist=eval_fixations.x_hist[fixation_index], | ||
y_hist=eval_fixations.y_hist[fixation_index], | ||
t_hist=eval_fixations.t_hist[fixation_index] | ||
) | ||
|
||
# Testing | ||
test = np.testing.assert_allclose(server_density, model_density) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import numpy as np | ||
import sys | ||
from typing import Union | ||
from scipy.ndimage import gaussian_filter | ||
sys.path.insert(0, '..') | ||
import pysaliency | ||
|
||
|
||
class LocalContrastModel(pysaliency.Model): | ||
def __init__(self, bandwidth=0.05, **kwargs): | ||
super().__init__(**kwargs) | ||
self.bandwidth = bandwidth | ||
|
||
def _log_density(self, stimulus: Union[pysaliency.datasets.Stimulus, np.ndarray]): | ||
|
||
# _log_density can either take pysaliency Stimulus objects, or, for convenience, simply numpy arrays | ||
# `as_stimulus` ensures that we have a Stimulus object | ||
stimulus_object = pysaliency.datasets.as_stimulus(stimulus) | ||
|
||
# grayscale image | ||
gray_stimulus = np.mean(stimulus_object.stimulus_data, axis=2) | ||
|
||
# size contains the height and width of the image, but not potential color channels | ||
height, width = stimulus_object.size | ||
|
||
# define kernel size based on image size | ||
kernel_size = np.round(self.bandwidth * max(width, height)).astype(int) | ||
sigma = (kernel_size - 1) / 6 | ||
|
||
# apply Gausian blur and calculate squared difference between blurred and original image | ||
blurred_stimulus = gaussian_filter(gray_stimulus, sigma) | ||
|
||
prediction = gaussian_filter((gray_stimulus - blurred_stimulus)**2, sigma) | ||
|
||
# normalize to [1, 255] | ||
prediction = (254 * (prediction / prediction.max())).astype(int) + 1 | ||
|
||
density = prediction / prediction.sum() | ||
|
||
return np.log(density) | ||
|
||
class MySimpleScanpathModel(pysaliency.ScanpathModel): | ||
def __init__(self, spatial_model_bandwidth: float=0.05, saccade_width: float=0.1): | ||
self.spatial_model_bandwidth = spatial_model_bandwidth | ||
self.saccade_width = saccade_width | ||
self.spatial_model = LocalContrastModel(spatial_model_bandwidth) | ||
# self.spatial_model = pysaliency.UniformModel() | ||
|
||
|
||
def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, attributes=None, out=None,): | ||
stimulus_object = pysaliency.datasets.as_stimulus(stimulus) | ||
|
||
# size contains the height and width of the image, but not potential color channels | ||
height, width = stimulus_object.size | ||
|
||
spatial_prior_log_density = self.spatial_model.log_density(stimulus) | ||
spatial_prior_density = np.exp(spatial_prior_log_density) | ||
|
||
# compute saccade bias | ||
last_x = x_hist[-1] | ||
last_y = y_hist[-1] | ||
|
||
xs = np.arange(width, dtype=float) | ||
ys = np.arange(height, dtype=float) | ||
XS, YS = np.meshgrid(xs, ys) | ||
|
||
XS -= last_x | ||
YS -= last_y | ||
|
||
# compute prior | ||
max_size = max(width, height) | ||
actual_kernel_size = self.saccade_width * max_size | ||
|
||
saccade_bias = np.exp(-0.5 * (XS ** 2 + YS ** 2) / actual_kernel_size ** 2) | ||
|
||
prediction = spatial_prior_density * saccade_bias | ||
|
||
density = prediction / prediction.sum() | ||
return np.log(density) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
from .models import ScanpathModel | ||
from PIL import Image | ||
from io import BytesIO | ||
import requests | ||
import json | ||
import numpy as np | ||
|
||
class HTTPScanpathModel(ScanpathModel): | ||
def __init__(self, url): | ||
self.url = url | ||
self.check_type() | ||
|
||
@property | ||
def log_density_url(self): | ||
return self.url + "/conditional_log_density" | ||
|
||
@property | ||
def type_url(self): | ||
return self.url + "/type" | ||
|
||
def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, attributes=None, out=None): | ||
# build request | ||
pil_image = Image.fromarray(stimulus) | ||
image_bytes = BytesIO() | ||
pil_image.save(image_bytes, format='png') | ||
|
||
def _convert_attribute(attribute): | ||
if isinstance(attribute, np.ndarray): | ||
return attribute.tolist() | ||
return attribute | ||
|
||
json_data = { | ||
"x_hist": list(x_hist), | ||
"y_hist": list(y_hist), | ||
"t_hist": list(t_hist), | ||
"attributes": {key: _convert_attribute(value) for key, value in (attributes or {}).items()} | ||
} | ||
|
||
# send request | ||
response = requests.post(f"{self.log_density_url}", data={'json_data': json.dumps(json_data)}, files={'stimulus': image_bytes.getvalue()}) | ||
|
||
# parse response | ||
if response.status_code != 200: | ||
raise ValueError(f"Server returned status code {response.status_code}") | ||
|
||
return np.array(response.json()['log_density']) | ||
|
||
def check_type(self): | ||
# TODO in constuctor aufrufen, damit direkt gecheckt wird ob type passt | ||
response = requests.get(f"{self.type_url}").json() | ||
if not response['type'] == 'ScanpathModel': | ||
raise ValueError(f"invalid Model type: {response['type']}. Expected 'ScanpathModel'") | ||
if not response['version'] in ['v1.0.0']: | ||
raise ValueError(f"invalid Model type: {response['version']}. Expected 'v1.0.0'") |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete ;)