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

Built In Jupyter Notebook #213

Merged
merged 15 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from 12 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
10 changes: 9 additions & 1 deletion docker/dockerfiles/Dockerfile.onnx.cpu
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ RUN pip3 install --upgrade pip && pip3 install \
-r requirements.gaze.txt \
-r requirements.doctr.txt \
-r requirements.groundingdino.txt \
jupyterlab \
wheel>=0.38.0 \
setuptools>=65.5.1 \
--upgrade \
Expand All @@ -42,8 +43,15 @@ RUN pip3 install --upgrade pip && pip3 install \
FROM scratch
COPY --from=base / /

WORKDIR /app
WORKDIR /build
COPY . .
RUN make create_wheels
RUN pip3 install dist/inference_core*.whl dist/inference_cpu*.whl dist/inference_sdk*.whl

WORKDIR /notebooks
COPY examples/notebooks .

WORKDIR /app
COPY inference inference
COPY docker/config/cpu_http.py cpu_http.py

Expand Down
9 changes: 9 additions & 0 deletions docker/dockerfiles/Dockerfile.onnx.gpu
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,21 @@ RUN pip3 install --upgrade pip && pip3 install \
-r requirements.groundingdino.txt \
-r requirements.doctr.txt \
-r requirements.cogvlm.txt \
jupyterlab \
--upgrade \
&& rm -rf ~/.cache/pip

FROM scratch
COPY --from=base / /

WORKDIR /build
COPY . .
RUN make create_wheels
RUN pip3 install dist/inference_core*.whl dist/inference_gpu*.whl dist/inference_sdk*.whl

WORKDIR /notebooks
COPY examples/notebooks .

WORKDIR /app/
COPY inference inference
COPY docker/config/gpu_http.py gpu_http.py
Expand Down
6 changes: 6 additions & 0 deletions docker/dockerfiles/Dockerfile.onnx.jetson.4.5.0
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ RUN python3.8 -m pip install --upgrade pip && python3.8 -m pip install \
-r requirements.http.txt \
-r requirements.doctr.txt \
-r requirements.groundingdino.txt \
jupyterlab \
--upgrade \
&& rm -rf ~/.cache/pip

Expand All @@ -47,6 +48,11 @@ RUN python3.8 -m pip install onnxruntime_gpu-1.11.0-cp38-cp38-linux_aarch64.whl
&& rm -rf ~/.cache/pip \
&& rm onnxruntime_gpu-1.11.0-cp38-cp38-linux_aarch64.whl

WORKDIR /build
COPY . .
RUN make create_wheels
RUN pip3 install dist/inference_core*.whl dist/inference_cpu*.whl dist/inference_sdk*.whl

WORKDIR /app/
COPY inference inference
COPY docker/config/gpu_http.py gpu_http.py
Expand Down
6 changes: 6 additions & 0 deletions docker/dockerfiles/Dockerfile.onnx.jetson.4.6.1
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ RUN python3.8 -m pip install --upgrade pip && python3.8 -m pip install \
-r requirements.http.txt \
-r requirements.doctr.txt \
-r requirements.groundingdino.txt \
jupyterlab \
--upgrade \
&& rm -rf ~/.cache/pip

Expand All @@ -47,6 +48,11 @@ RUN python3.8 -m pip install onnxruntime_gpu-1.11.0-cp38-cp38-linux_aarch64.whl
&& rm -rf ~/.cache/pip \
&& rm onnxruntime_gpu-1.11.0-cp38-cp38-linux_aarch64.whl

WORKDIR /build
COPY . .
RUN make create_wheels
RUN pip3 install dist/inference_core*.whl dist/inference_cpu*.whl dist/inference_sdk*.whl

WORKDIR /app/
COPY inference inference
COPY docker/config/gpu_http.py gpu_http.py
Expand Down
6 changes: 6 additions & 0 deletions docker/dockerfiles/Dockerfile.onnx.jetson.5.1.1
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ RUN pip3 install --upgrade pip && pip3 install \
-r requirements.http.txt \
-r requirements.doctr.txt \
-r requirements.groundingdino.txt \
jupyterlab \
--upgrade \
&& rm -rf ~/.cache/pip

Expand All @@ -45,6 +46,11 @@ RUN pip3 install onnxruntime_gpu-1.12.1-cp38-cp38-linux_aarch64.whl "opencv-pyth
&& rm -rf ~/.cache/pip \
&& rm onnxruntime_gpu-1.12.1-cp38-cp38-linux_aarch64.whl

WORKDIR /build
COPY . .
RUN make create_wheels
RUN pip3 install dist/inference_core*.whl dist/inference_cpu*.whl dist/inference_sdk*.whl

WORKDIR /app/
COPY inference inference
COPY docker/config/gpu_http.py gpu_http.py
Expand Down
123 changes: 123 additions & 0 deletions examples/notebooks/inference_pipeline.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "83db9682-cfc4-4cd0-889f-c8747c4033b3",
"metadata": {},
"source": [
"# Inference Pipeline\n",
"\n",
"Inference Pipelines are a great way to process video streams with Inference. You can configure different sources that include streams from local devices, RTSP streams, and local video files. You can also configure different sinks that include UDP streaming of results, render of results, and custom callbacks to run your own logic after each new set of predictions is available. "
]
},
{
"cell_type": "markdown",
"id": "4ec4136f-53e9-4c8c-9217-a2c533d498ae",
"metadata": {},
"source": [
"### Roboflow API Key\n",
"\n",
"To load models with `inference`, you'll need a Roboflow API Key. Find instructions for retrieving your API key [here](https://docs.roboflow.com/api-reference/authentication). The utility function below attempts to load your Roboflow API key from your enviornment. If it isn't found, it then prompts you to input it. To avoid needing to input your API key for each example, you can configure your Roboflow API key in your environment via the variable `ROBOFLOW_API_KEY`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af3aad40-d41b-4bc1-ade8-dac052951257",
"metadata": {},
"outputs": [],
"source": [
"from utils import get_roboflow_api_key\n",
"\n",
"api_key = get_roboflow_api_key()"
]
},
{
"cell_type": "markdown",
"id": "86f3f805-f628-4e94-91ac-3b2f44bebdc0",
"metadata": {},
"source": [
"### Inference Pipeline Example\n",
"\n",
"In this example we create a new InferencePipeline. We pass the model ID, the video reference, and a method to render our results. Out pipeline does the rest!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "58dd049c-dcc6-4d0b-85ad-e6d1c0ba805b",
"metadata": {},
"outputs": [],
"source": [
"from functools import partial\n",
"\n",
"import numpy as np\n",
"from matplotlib import pyplot as plt\n",
"from IPython import display\n",
"\n",
"from inference.core.interfaces.stream.inference_pipeline import InferencePipeline\n",
"from inference.core.interfaces.stream.sinks import render_boxes\n",
"\n",
"# Define source video\n",
"video_url = \"https://storage.googleapis.com/com-roboflow-marketing/football-video.mp4\"\n",
"\n",
"# Prepare to plot results\n",
"\n",
"fig, ax = plt.subplots()\n",
"frame_placeholder = np.zeros((480, 640, 3), dtype=np.uint8) # Adjust the dimensions to match your frame size\n",
"image_display = ax.imshow(frame_placeholder)\n",
"\n",
"# Define our plotting function\n",
"def update_plot(new_frame):\n",
" # Update the image displayed\n",
" image_display.set_data(new_frame)\n",
" # Redraw the canvas immediately\n",
" display.display(plt.gcf())\n",
" display.clear_output(wait=True)\n",
"\n",
"# Define our pipeline's sink\n",
"render = partial(render_boxes, on_frame_rendered=update_plot)\n",
"\n",
"# Instantiate the pipeline\n",
"pipeline = InferencePipeline.init(\n",
" model_id=\"soccer-players-5fuqs/1\",\n",
" video_reference=video_url,\n",
" on_prediction=render,\n",
")\n",
"\n",
"# Start the pipeline\n",
"pipeline.start()\n",
"pipeline.join()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "07762936-ff33-46c0-a4a2-0a8e729053d1",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
193 changes: 193 additions & 0 deletions examples/notebooks/inference_sdk.ipynb

Large diffs are not rendered by default.

212 changes: 212 additions & 0 deletions examples/notebooks/quickstart.ipynb

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions examples/notebooks/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import getpass
import requests

import cv2
import numpy as np

from inference.core.env import API_KEY

def get_roboflow_api_key():
if API_KEY is None:
api_key = getpass.getpass("Roboflow API Key:")
else:
api_key = API_KEY
return api_key

def load_image_from_url(url):
# Send a GET request to the URL
response = requests.get(url)

# Ensure that the request was successful
if response.status_code == 200:
# Convert the response content into a numpy array
image_array = np.asarray(bytearray(response.content), dtype=np.uint8)

# Decode the image array into an OpenCV image
image = cv2.imdecode(image_array, cv2.IMREAD_COLOR)

return image
else:
print(f"Failed to retrieve the image. HTTP status code: {response.status_code}")
return None
8 changes: 8 additions & 0 deletions inference/core/entities/responses/notebooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pydantic import BaseModel, Field, ValidationError


class NotebookStartResponse(BaseModel):
"""Response model for notebook start request"""

success: str = Field(..., description="Status of the request")
message: str = Field(..., description="Message of the request", optional=True)
11 changes: 10 additions & 1 deletion inference/core/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
LICENSE_SERVER = os.getenv("LICENSE_SERVER", None)

# Log level, default is "INFO"
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
LOG_LEVEL = os.getenv("LOG_LEVEL", "WARNING")

# Maximum number of active models, default is 8
MAX_ACTIVE_MODELS = int(os.getenv("MAX_ACTIVE_MODELS", 8))
Expand Down Expand Up @@ -204,6 +204,15 @@
# Model ID, default is None
MODEL_ID = os.getenv("MODEL_ID")

# Enable jupyter notebook server route, default is False
NOTEBOOK_ENABLED = str2bool(os.getenv("NOTEBOOK_ENABLED", False))

# Jupyter notebook password, default is "roboflow"
NOTEBOOK_PASSWORD = os.getenv("NOTEBOOK_PASSWORD", "roboflow")

# Jupyter notebook port, default is 9002
NOTEBOOK_PORT = int(os.getenv("NOTEBOOK_PORT", 9002))

# Number of workers, default is 1
NUM_WORKERS = int(os.getenv("NUM_WORKERS", 1))

Expand Down
47 changes: 46 additions & 1 deletion inference/core/interfaces/http/http_api.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import base64
import traceback
from functools import partial, wraps
from time import sleep
from typing import Any, List, Optional, Union

import uvicorn
from fastapi import BackgroundTasks, Body, FastAPI, Path, Query, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, Response
from fastapi.responses import JSONResponse, RedirectResponse, Response
from fastapi.staticfiles import StaticFiles
from fastapi_cprofile.profiler import CProfileMiddleware

Expand Down Expand Up @@ -52,6 +53,7 @@
ObjectDetectionInferenceResponse,
StubResponse,
)
from inference.core.entities.responses.notebooks import NotebookStartResponse
from inference.core.entities.responses.sam import (
SamEmbeddingResponse,
SamSegmentationResponse,
Expand All @@ -73,6 +75,9 @@
LEGACY_ROUTE_ENABLED,
METLO_KEY,
METRICS_ENABLED,
NOTEBOOK_ENABLED,
NOTEBOOK_PASSWORD,
NOTEBOOK_PORT,
PROFILE,
ROBOFLOW_SERVICE_SECRET,
)
Expand Down Expand Up @@ -101,6 +106,7 @@
from inference.core.interfaces.base import BaseInterface
from inference.core.interfaces.http.orjson_utils import orjson_response
from inference.core.managers.base import ModelManager
from inference.core.utils.notebooks import start_notebook

if LAMBDA:
from inference.core.usage import trackUsage
Expand Down Expand Up @@ -1200,6 +1206,45 @@ async def model_add(dataset_id: str, version_id: str, api_key: str = None):
}
)

if not LAMBDA:

@app.get(
Copy link
Collaborator

Choose a reason for hiding this comment

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

probably should be post?

Copy link
Collaborator

Choose a reason for hiding this comment

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

not sure how that would work with redirect

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ya, I'm not sure how the redirect works with POST. I'm going to leave it as GET for now since it's working and we don't expect users to be invoking this route other than through our own landing page.

"/notebook/start",
summary="Jupyter Lab Server Start",
description="Starts a jupyter lab server for running development code",
)
@with_route_exceptions
async def notebook_start(browserless: bool = False):
"""Starts a jupyter lab server for running development code.

Args:
inference_request (NotebookStartRequest): The request containing the necessary details for starting a jupyter lab server.
background_tasks: (BackgroundTasks) pool of fastapi background tasks

Returns:
NotebookStartResponse: The response containing the URL of the jupyter lab server.
"""
if NOTEBOOK_ENABLED:
start_notebook()
if browserless:
return {
"success": True,
"message": f"Jupyter Lab server started at http://localhost:{NOTEBOOK_PORT}?token={NOTEBOOK_PASSWORD}",
}
else:
sleep(2)
return RedirectResponse(
f"http://localhost:{NOTEBOOK_PORT}/lab/tree/quickstart.ipynb?token={NOTEBOOK_PASSWORD}"
)
else:
if browserless:
return {
"success": False,
"message": "Notebook server is not enabled. Enable notebooks via the NOTEBOOK_ENABLED environment variable.",
}
else:
return RedirectResponse(f"/notebook-instructions.html")

app.mount(
"/",
StaticFiles(directory="./inference/landing/out", html=True),
Expand Down
4 changes: 2 additions & 2 deletions inference/core/interfaces/http/orjson_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def orjson_response(
response: Union[List[InferenceResponse], InferenceResponse]
) -> ORJSONResponseBytes:
if isinstance(response, list):
content = [r.dict(by_alias=True) for r in response]
content = [r.dict(by_alias=True, exclude_none=True) for r in response]
else:
content = response.dict(by_alias=True)
content = response.dict(by_alias=True, exclude_none=True)
return ORJSONResponseBytes(content=content)
Loading
Loading