Skip to content

Commit

Permalink
Merge branch 'main' into feature/new_clip_block
Browse files Browse the repository at this point in the history
  • Loading branch information
PawelPeczek-Roboflow authored Aug 29, 2024
2 parents 5209684 + a2d06b5 commit 6ff2a2a
Show file tree
Hide file tree
Showing 54 changed files with 5,047 additions and 781 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ jobs:
- run: |
python -m pip install --upgrade pip
pip install --upgrade setuptools
pip install -r requirements/_requirements.txt -r requirements/requirements.cpu.txt -r requirements/requirements.sdk.http.txt -r requirements/requirements.test.unit.txt -r requirements/requirements.http.txt
pip install -r requirements/_requirements.txt -r requirements/requirements.cpu.txt -r requirements/requirements.sdk.http.txt -r requirements/requirements.test.unit.txt -r requirements/requirements.test.integration.txt -r requirements/requirements.http.txt
working-directory: ./inference_repo
- run: pip install .
working-directory: ./roboflow_enterprise_blocks
- run: python -m development.docs.build_block_docs
- run: |
python -m development.docs.build_block_docs
python -m development.docs.workflows_gallery_builder
working-directory: ./inference_repo
- run: mkdocs gh-deploy --force
working-directory: ./inference_repo
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ scratch
!tests/inference/unit_tests/core/utils/assets/*.jpg
docs/workflows/blocks/*
docs/workflows/kinds/*
docs/workflows/gallery/*
!tests/workflows/integration_tests/execution/assets/*.jpg
!tests/workflows/integration_tests/execution/assets/rock_paper_scissors/*.jpg
!tests/workflows/unit_tests/core_steps/models/third_party/assets/*.png
Expand Down
Empty file added development/docs/__init__.py
Empty file.
147 changes: 147 additions & 0 deletions development/docs/workflows_gallery_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import json
import os
from collections import defaultdict
from typing import List, Dict, Optional

import pytest

from tests.workflows.integration_tests.execution.workflows_gallery_collector.decorators import GALLERY_ENTRIES, \
WorkflowGalleryEntry

INTEGRATION_TESTS_DIRECTORY = os.path.abspath(os.path.join(
os.path.dirname(__file__),
"..",
"..",
"tests/workflows/integration_tests/execution"
))
DOCS_ROOT = os.path.abspath(os.path.join(
os.path.dirname(__file__),
"..",
"..",
"docs"
))
WORKFLOWS_DOCS_ROOT_PATH = os.path.join(DOCS_ROOT, "workflows")

GALLERY_INDEX_PATH = os.path.join(WORKFLOWS_DOCS_ROOT_PATH, "gallery_index.md")
GALLERY_DIR_PATH = os.path.join(WORKFLOWS_DOCS_ROOT_PATH, "gallery")


def generate_gallery() -> None:
run_pytest_collection_to_fill_workflows_gallery()
categorised_gallery = categorise_gallery(gallery=GALLERY_ENTRIES)
generate_gallery_index(categories=list(categorised_gallery.keys()))
for category, entries in categorised_gallery.items():
generate_gallery_page_for_category(category=category, entries=entries)


def run_pytest_collection_to_fill_workflows_gallery() -> None:
pytest.main(["--collect-only", INTEGRATION_TESTS_DIRECTORY])


def categorise_gallery(gallery: List[WorkflowGalleryEntry]) -> Dict[str, List[WorkflowGalleryEntry]]:
result = defaultdict(list)
for item in gallery:
result[item.category].append(item)
return result


def generate_gallery_index(categories: List[str]) -> None:
index = read_file(path=GALLERY_INDEX_PATH)
index_lines = index.split("\n")
list_start_index = find_line_with_marker(lines=index_lines, marker='<ul id="workflows-gallery">')
list_end_index = find_line_with_marker(lines=index_lines, marker="</ul>")
if list_start_index is None or list_end_index is None:
raise RuntimeError("Could not find expected <ul> markers in gallery index file")
categories_entries = [
f'\t<li><a href="{generate_gallery_page_link(category=category)}">{category}</a></li>'
for category in categories
]
new_index = index_lines[:list_start_index + 1]
new_index.extend(categories_entries)
new_index.extend(index_lines[list_end_index:])
new_index_content = "\n".join(new_index)
write_file(path=GALLERY_INDEX_PATH, content=new_index_content)


GALLERY_PAGE_TEMPLATE = """
# Example Workflows - {category}
Below you can find example workflows you can use as inspiration to build your apps.
{examples}
""".strip()


def generate_gallery_page_for_category(
category: str,
entries: List[WorkflowGalleryEntry],
) -> None:
examples = [
generate_gallery_entry_docs(entry=entry)
for entry in entries
]
page_content = GALLERY_PAGE_TEMPLATE.format(
category=category,
examples="\n\n".join(examples)
)
file_path = generate_gallery_page_file_path(category=category)
write_file(path=file_path, content=page_content)


GALLERY_ENTRY_TEMPLATE = """
## {title}
{description}
??? tip "Workflow definition"
```json
{workflow_definition}
```
""".strip()


def generate_gallery_entry_docs(entry: WorkflowGalleryEntry) -> str:
return GALLERY_ENTRY_TEMPLATE.format(
title=entry.use_case_title,
description=entry.use_case_description,
workflow_definition="\n\t".join(json.dumps(entry.workflow_definition, indent=4).split("\n")),
)


def read_file(path: str) -> str:
with open(path, "r") as f:
return f.read()


def write_file(path: str, content: str) -> None:
path = os.path.abspath(path)
parent_dir = os.path.dirname(path)
os.makedirs(parent_dir, exist_ok=True)
with open(path, "w") as f:
f.write(content)


def find_line_with_marker(lines: List[str], marker: str) -> Optional[int]:
for i, line in enumerate(lines):
if marker in line:
return i
return None


def generate_gallery_page_link(category: str) -> str:
file_path = generate_gallery_page_file_path(category=category)
return file_path[len(DOCS_ROOT):-3]


def generate_gallery_page_file_path(category: str) -> str:
category_slug = slugify_category(category=category)
return os.path.join(GALLERY_DIR_PATH, f"{category_slug}.md")


def slugify_category(category: str) -> str:
return category.lower().replace(" ", "_").replace("/", "_")


if __name__ == "__main__":
generate_gallery()
2 changes: 1 addition & 1 deletion docs/enterprise/active-learning/active_learning.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Active learning can be disabled by setting `ACTIVE_LEARNING_ENABLED=false` in th
## Usage patterns
Active Learning data collection may be combined with different components of the Roboflow ecosystem. In particular:

- the `inference` Python package can be used to get predictions from the model and register them at Roboflow platform
- the `inference` Python package can be used to get predictions from the model and register them on the Roboflow platform
- one may want to use `InferencePipeline` to get predictions from video and register its video frames using Active Learning
- self-hosted `inference` server - where data is collected while processing requests
- Roboflow hosted `inference` - where you let us make sure you get your predictions and data registered. No
Expand Down
2 changes: 1 addition & 1 deletion docs/foundation/clip.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ In this guide, we will show:
## How can I use CLIP model in `inference`?

- directly from `inference[clip]` package, integrating the model directly into your code
- using `inference` HTTP API (hosted locally, or at Roboflow platform), integrating via HTTP protocol
- using `inference` HTTP API (hosted locally, or on the Roboflow platform), integrating via HTTP protocol
- using `inference-sdk` package (`pip install inference-sdk`) and [`InferenceHTTPClient`](/docs/inference_sdk/http_client.md)
- creating custom code to make HTTP requests (see [API Reference](/api/))

Expand Down
12 changes: 6 additions & 6 deletions docs/inference_helpers/inference_sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ You can use this client to run models hosted:
1. On the Roboflow platform (use client version `v0`), and;
2. On device with Inference.

For models trained at Roboflow platform, client accepts the following inputs:
For models trained on the Roboflow platform, client accepts the following inputs:

- A single image (Given as a local path, URL, `np.ndarray` or `PIL.Image`);
- Multiple images;
Expand Down Expand Up @@ -60,7 +60,7 @@ result = loop.run_until_complete(
)
```

## Configuration options (used for models trained at Roboflow platform)
## Configuration options (used for models trained on the Roboflow platform)

### configuring with context managers

Expand Down Expand Up @@ -195,8 +195,8 @@ Methods that support batching / parallelism:

## Client for core models

`InferenceHTTPClient` now supports core models hosted via `inference`. Part of the models can be used at Roboflow hosted
inference platform (use `https://infer.roboflow.com` as url), other are possible to be deployed locally (usually
`InferenceHTTPClient` now supports core models hosted via `inference`. Part of the models can be used on the Roboflow
hosted inference platform (use `https://infer.roboflow.com` as url), other are possible to be deployed locally (usually
local server will be available under `http://localhost:9001`).

!!! tip
Expand Down Expand Up @@ -705,12 +705,12 @@ to prevent errors)
## Why does the Inference client have two modes (`v0` and `v1`)?

We are constantly improving our `infrence` package - initial version (`v0`) is compatible with
models deployed at Roboflow platform (task types: `classification`, `object-detection`, `instance-segmentation` and
models deployed on the Roboflow platform (task types: `classification`, `object-detection`, `instance-segmentation` and
`keypoints-detection`)
are supported. Version `v1` is available in locally hosted Docker images with HTTP API.

Locally hosted `inference` server exposes endpoints for model manipulations, but those endpoints are not available
at the moment for models deployed at Roboflow platform.
at the moment for models deployed on the Roboflow platform.

`api_url` parameter passed to `InferenceHTTPClient` will decide on default client mode - URLs with `*.roboflow.com`
will be defaulted to version `v0`.
Expand Down
25 changes: 18 additions & 7 deletions docs/workflows/about.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
# Inference Workflows
# Workflows

## What is a Workflow?
## What is Roboflow Workflows?

Workflows allow you to define multi-step processes that run one or more models to return results based on model outputs and custom logic.
Roboflow Workflows is an ecosystem that enables users to create machine learning applications using a wide range
of pluggable and reusable blocks. These blocks are organized in a way that makes it easy for users to design
and connect different components. Graphical interface allows to visually construct workflows
without needing extensive technical expertise. Once the workflow is designed, Workflows engine runs the
application, ensuring all the components work together seamlessly, providing a rapid transition
from prototype to production-ready solutions, allowing you to quickly iterate and deploy applications.

Roboflow offers a growing selection of workflows blocks, and the community can also create new blocks, ensuring
that the ecosystem is continuously expanding and evolving. Moreover, Roboflow provides flexible deployment options,
including on-premises and cloud-based solutions, allowing users to deploy their applications in the environment
that best suits their needs.

With Workflows, you can:

- Detect, classify, and segment objects in images.
- Apply logic filters such as establish detection consensus or filter detections by confidence.
- Detect, classify, and segment objects in images using state-of-the-art models.

- Use Large Multimodal Models (LMMs) to make determinations at any stage in a workflow.

- Introduce elements of business logic to translate model predictions into your domain language

<div class="button-holder">
<a href="https://inference.roboflow.com/workflows/blocks/" class="button half-button">Explore all Workflows blocks</a>
<a href="/workflows/blocks/" class="button half-button">Explore all Workflows blocks</a>
<a href="https://app.roboflow.com/workflows" class="button half-button">Begin building with Workflows</a>
</div>

![A license plate detection workflow implemented in Workflows](https://media.roboflow.com/inference/workflow-example.png)

You can build and configure Workflows in the Roboflow web interface that you can then deploy using the Roboflow Hosted API, self-host locally and on the cloud using inference, or offline to your hardware devices. You can also build more advanced workflows by writing a Workflow configuration directly in the JSON editor.

In this section of documentation, we walk through what you need to know to create and run workflows. Let’s get started!

Expand Down
121 changes: 121 additions & 0 deletions docs/workflows/blocks_bundling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Bundling Workflows blocks

To efficiently manage the Workflows ecosystem, a standardized method for building and distributing blocks is
essential. This allows users to create their own blocks and bundle them into Workflow plugins. A Workflow plugin
is essentially a Python library that implements a defined interface and can be structured in various ways.

This page outlines the mandatory interface requirements and suggests a structure for blocks that aligns with
the [Workflows versioning](/workflows/versioning) guidelines.

## Proposed structure of plugin

We propose the following structure of plugin:

```
.
├── requirements.txt # file with requirements
├── setup.py # use different package creation method if you like
├── {plugin_name}
│ ├── __init__.py # main module that contains loaders
│ ├── kinds.py # optionally - definitions of custom kinds
│ ├── {block_name} # package for your block
│ │ ├── v1.py # version 1 of your block
│ │ ├── ... # ... next versions
│ │ └── v5.py # version 5 of your block
│ └── {block_name} # package for another block
└── tests # tests for blocks
```

## Required interface

Plugin must only provide few extensions to `__init__.py` in main package
compared to standard Python library:

* `load_blocks()` function to provide list of blocks' classes (required)

* `load_kinds()` function to return all custom [kinds](/workflows/kinds/) the plugin defines (optional)

* `REGISTERED_INITIALIZERS` module property which is a dict mapping name of block
init parameter into default value or parameter-free function constructing that value - optional


### `load_blocks()` function

Function is supposed to enlist all blocks in the plugin - it is allowed to define
a block once.

Example:

```python
from typing import List, Type
from inference.core.workflows.prototypes.block import WorkflowBlock

# example assumes that your plugin name is `my_plugin` and
# you defined the blocks that are imported here
from my_plugin.block_1.v1 import Block1V1
from my_plugin.block_2.v1 import Block2V1

def load_blocks() -> List[Type[WorkflowBlock]]:
return [
Block1V1,
Block2V1,
]
```

### `load_kinds()` function

`load_kinds()` function to return all custom kinds the plugin defines. It is optional as your blocks
may not need custom kinds.

Example:

```python
from typing import List
from inference.core.workflows.execution_engine.entities.types import Kind

# example assumes that your plugin name is `my_plugin` and
# you defined the imported kind
from my_plugin.kinds import MY_KIND


def load_kinds() -> List[Kind]:
return [MY_KIND]
```


## `REGISTERED_INITIALIZERS` dictionary

As you know from [the docs describing the Workflows Compiler](/workflows/workflows_compiler/)
and the [blocks development guide](/workflows/create_workflow_block/), Workflow
blocs are dynamically initialized during compilation and may require constructor
parameters. Those parameters can default to values registered in the `REGISTERED_INITIALIZERS`
dictionary. To expose default a value for an init parameter of your block -
simply register the name of the init param and its value (or a function generating a value) in the dictionary.
This is optional part of the plugin interface, as not every block requires a constructor.

Example:

```python
import os

def init_my_param() -> str:
# do init here
return "some-value"

REGISTERED_INITIALIZERS = {
"param_1": 37,
"param_2": init_my_param,
}
```

## Enabling plugin in your Workflows ecosystem

To load a plugin you must:

* install the Python package with the plugin in the environment you run Workflows

* export an environment variable named `WORKFLOWS_PLUGINS` set to a comma-separated list of names
of plugins you want to load.

* Example: to load two plugins `plugin_a` and `plugin_b`, you need to run
`export WORKFLOWS_PLUGINS="plugin_a,plugin_b"`
Loading

0 comments on commit 6ff2a2a

Please sign in to comment.