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

http_server first commit #90

Merged
merged 9 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
23 changes: 23 additions & 0 deletions exaples/docker_submission/Dockerfile
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"]
27 changes: 27 additions & 0 deletions exaples/docker_submission/README.md
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
```
46 changes: 46 additions & 0 deletions exaples/docker_submission/model_server.py
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()
6 changes: 6 additions & 0 deletions exaples/docker_submission/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cython
flask
gunicorn
numpy

# Add additional dependencies here
39 changes: 39 additions & 0 deletions exaples/docker_submission/sample_evaluation.py
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)

80 changes: 80 additions & 0 deletions exaples/docker_submission/sample_submission.py
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)

54 changes: 54 additions & 0 deletions pysaliency/http_models.py
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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete ;)

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'")
Loading