-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
470 additions
and
169 deletions.
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
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,20 @@ | ||
# Minimal makefile for Sphinx documentation | ||
# | ||
|
||
# You can set these variables from the command line, and also | ||
# from the environment for the first two. | ||
SPHINXOPTS ?= | ||
SPHINXBUILD ?= sphinx-build | ||
SOURCEDIR = . | ||
BUILDDIR = _build | ||
|
||
# Put it first so that "make" without argument is like "make help". | ||
help: | ||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) | ||
|
||
.PHONY: help Makefile | ||
|
||
# Catch-all target: route all unknown targets to Sphinx using the new | ||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). | ||
%: Makefile | ||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) |
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,11 @@ | ||
============================ | ||
Core (Frame and Sequence) | ||
============================ | ||
|
||
.. autoclass:: starfish.Frame | ||
:members: | ||
:special-members: __init__ | ||
|
||
.. autoclass:: starfish.Sequence | ||
:members: | ||
:special-members: __init__ |
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,9 @@ | ||
====================== | ||
API Documentation | ||
====================== | ||
|
||
.. toctree:: | ||
core | ||
utils | ||
postprocessing | ||
rotations |
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 @@ | ||
============================ | ||
Postprocessing | ||
============================ | ||
|
||
.. automodule:: starfish.postprocessing | ||
:members: |
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 @@ | ||
============================ | ||
Rotations | ||
============================ | ||
|
||
.. automodule:: starfish.rotations | ||
:members: |
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 @@ | ||
============================ | ||
Utils | ||
============================ | ||
|
||
.. automodule:: starfish.utils | ||
:members: |
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,59 @@ | ||
# Configuration file for the Sphinx documentation builder. | ||
# | ||
# This file only contains a selection of the most common options. For a full | ||
# list see the documentation: | ||
# https://www.sphinx-doc.org/en/master/usage/configuration.html | ||
|
||
master_doc = 'index' | ||
|
||
# -- Path setup -------------------------------------------------------------- | ||
|
||
# If extensions (or modules to document with autodoc) are in another directory, | ||
# add these directories to sys.path here. If the directory is relative to the | ||
# documentation root, use os.path.abspath to make it absolute, like shown here. | ||
# | ||
import os | ||
import sys | ||
sys.path.insert(0, os.path.abspath('../src')) | ||
|
||
|
||
# -- Project information ----------------------------------------------------- | ||
|
||
project = 'Starfish' | ||
copyright = '2020, Kevin Black' | ||
author = 'Kevin Black' | ||
|
||
|
||
# -- General configuration --------------------------------------------------- | ||
|
||
# Add any Sphinx extension module names here, as strings. They can be | ||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom | ||
# ones. | ||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] | ||
|
||
intersphinx_mapping = { | ||
'blender': ('https://docs.blender.org/api/2.82/', None) | ||
} | ||
|
||
default_role = 'py:obj' | ||
|
||
# Add any paths that contain templates here, relative to this directory. | ||
templates_path = ['_templates'] | ||
|
||
# List of patterns, relative to source directory, that match files and | ||
# directories to ignore when looking for source files. | ||
# This pattern also affects html_static_path and html_extra_path. | ||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] | ||
|
||
|
||
# -- Options for HTML output ------------------------------------------------- | ||
|
||
# The theme to use for HTML and HTML Help pages. See the documentation for | ||
# a list of builtin themes. | ||
# | ||
html_theme = "sphinx_rtd_theme" | ||
|
||
# Add any paths that contain custom static files (such as style sheets) here, | ||
# relative to this directory. They are copied after the builtin static files, | ||
# so a file named "default.css" will overwrite the builtin "default.css". | ||
html_static_path = ['_static'] |
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,127 @@ | ||
==================================== | ||
Documentation for Starfish | ||
==================================== | ||
Starfish is a Python library for automatically creating synthetic, labeled image data using Blender. | ||
|
||
This library it extends Blender's powerful Python scripting ability, providing utilities that make it easy to generate | ||
long sequences of synthetic images with intuitive parameters. It was designed for people who, like me, know Python | ||
better than they know Blender. | ||
|
||
These sequences can be smoothly interpolated from waypoint to waypoint, much like a traditional keyframe-based | ||
animation. They can also be exhaustive or random, containing images with various object poses, backgrounds, and lighting | ||
conditions. The intended use for these sequences is the generation of training and evaluation data for machine learning | ||
tasks where annotated real data may be difficult to obtain. | ||
|
||
.. contents:: Contents | ||
:local: | ||
:depth: 2 | ||
|
||
.. toctree:: | ||
:hidden: | ||
|
||
Home <self> | ||
api/index | ||
|
||
Installation | ||
------------------------------------ | ||
#. Identify the location of your Blender scripts directory. This can be done by opening Blender, clicking on the | ||
'Scripting' tab, and entering ``bpy.utils.script_path_user()`` in the Python console at the bottom. Generally, on | ||
Linux, the default location is ``~/.config/blender/[VERSION]/scripts``. From now on, this path will be referred to as | ||
``[SCRIPTS_DIR]``. | ||
#. Create the addon modules directory, if it does not exist already: ``mkdir -p [SCRIPTS_DIR]/addons/modules`` | ||
#. Install the library to Blender: ``pip install https://github.com/autognc/starfish --no-deps --target | ||
[SCRIPTS_DIR]/addons/modules``. Starfish does not require any additional packages besides what is already bundled | ||
with Blender, which is why ``--no-deps`` can be used. | ||
|
||
Starfish can also be pip-installed normally without Blender for testing purposes or for independent usage of the | ||
`postprocessing` module. | ||
|
||
Quickstart | ||
------------------------------------ | ||
|
||
Recommended Reading | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
To use Starfish, you're probably going to have to interact with Blender using the `Python API | ||
<https://docs.blender.org/api/current/>`_. This library also makes heavy use of `mathutils | ||
<https://docs.blender.org/api/current/mathutils.html>`_, which is an independent math library that comes bundled with | ||
Blender. | ||
|
||
Running Scripts in Blender | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
The easiest way to experiment with the library is by opening Blender, navigating to the Scripting tab, and hitting the | ||
plus button to create a new script. You can then import starfish, write some code, and hit ``Alt+P`` to see what it does. | ||
|
||
Once you're ready to execute a more long-running script, you can write it outside Blender and then execute it using | ||
``blender file.blend --background --python script.py`` (or ``blender file.blend -b -P script.py`` for short). | ||
|
||
Generating Images | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
Frames | ||
""""""""""""""""""" | ||
At the core of Starfish is the `Frame <starfish.Frame>` class, which represents a single image of a single object. A frame is defined by 6 parameters:: | ||
|
||
frame = starfish.Frame( | ||
position=(0, 0, 0), | ||
distance=10, | ||
pose=mathutils.Euler([0, math.pi, 0]), | ||
lighting=mathutils.Euler([0, 0, 0]), | ||
offset=(0.3, 0.7), | ||
background=mathutils.Euler([math.pi / 2, 0, 0]) | ||
) | ||
|
||
See the `starfish.Frame` documentation for more details about what each parameter means. Once you have a frame object, you use it to | ||
'set up' your scene:: | ||
frame.setup( | ||
bpy.data.scenes['MyScene'], | ||
bpy.data.objects['MyObject'], | ||
bpy.data.objects['Main_Camera'], | ||
bpy.data.objects['The_Sun'] | ||
) | ||
|
||
This moves all the objects so that the image that the camera sees matches up with the parameters in the frame object. At | ||
this point, you can render the frame using `bpy.ops.render.render`. You can also dump metadata about a frame into JSON | ||
format using the `Frame.dumps <starfish.Frame.dumps>` method. | ||
|
||
Sequences | ||
""""""""""""""""""" | ||
Of course, Starfish wouldn't be very useful without the ability to create multiple frames at once. The `Sequence | ||
<starfish.Sequence>` class is essentially just a list of frames, but with several classmethod constructors for | ||
generating these sequences of frames in different ways. For example, `Sequence.interpolated | ||
<starfish.Sequence.interpolated>` generates 'animated' sequences that smoothly interpolate between keyframes, and | ||
`Sequence.exhaustive <starfish.Sequence.exhaustive>` generates long sequences that contain every possible combination of | ||
the parameters given. | ||
|
||
Once you've created a sequences, you can iterate through its frames like so:: | ||
|
||
seq = starfish.Sequence... | ||
|
||
for frame in seq: | ||
frame.setup(...) | ||
bpy.ops.render.render() | ||
|
||
The `Sequence.bake <starfish.Sequence.bake>` method also provides an easy way to 'preview' sequences that you're working | ||
on in Blender. See `Sequence <starfish.Sequence>` for more detail. | ||
|
||
Postprocessing | ||
""""""""""""""""""" | ||
Starfish also contains a (currently small) `postprocessing module <starfish.postprocessing>` for common image | ||
postprocessing operations. | ||
|
||
One common type of annotation generated for computer vision task is some sort of segmentation mask (e.g. using the `ID | ||
Mask Node <https://docs.blender.org/manual/en/latest/compositing/types/converter/id_mask.html>`) where having perfectly | ||
uniform colors is important. Unfortunately, I've often encountered an issue in Blender where the output colors differ | ||
slightly: for example, instead of the background being solid ``rgb(0, 0, 0)`` black, it will actually be a random mix of | ||
``rgb(0, 0, 1)``, ``rgb(1, 1, 0)``, etc. The `normalize_mask_colors <starfish.postprocessing.normalize_mask_colors>` | ||
function can be used to clean up such images. | ||
|
||
Once a mask has been cleaned up, `get_bounding_boxes_from_mask <starfish.postprocessing.get_bounding_boxes_from_mask>` | ||
and `get_centroids_from_mask <starfish.postprocessing.get_centroids_from_mask>` can be used to get the bounding boxes | ||
and centroids of segmented areas, respectively. | ||
|
||
Example Script | ||
^^^^^^^^^^^^^^^^^^^^^^ | ||
All together, here is what an image generation script might look like: | ||
|
||
.. literalinclude:: ../example.py |
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,35 @@ | ||
@ECHO OFF | ||
|
||
pushd %~dp0 | ||
|
||
REM Command file for Sphinx documentation | ||
|
||
if "%SPHINXBUILD%" == "" ( | ||
set SPHINXBUILD=sphinx-build | ||
) | ||
set SOURCEDIR=. | ||
set BUILDDIR=_build | ||
|
||
if "%1" == "" goto help | ||
|
||
%SPHINXBUILD% >NUL 2>NUL | ||
if errorlevel 9009 ( | ||
echo. | ||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx | ||
echo.installed, then set the SPHINXBUILD environment variable to point | ||
echo.to the full path of the 'sphinx-build' executable. Alternatively you | ||
echo.may add the Sphinx directory to PATH. | ||
echo. | ||
echo.If you don't have Sphinx installed, grab it from | ||
echo.http://sphinx-doc.org/ | ||
exit /b 1 | ||
) | ||
|
||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% | ||
goto end | ||
|
||
:help | ||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% | ||
|
||
:end | ||
popd |
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 |
---|---|---|
@@ -1,41 +1,59 @@ | ||
import os | ||
import time | ||
|
||
import bpy | ||
|
||
import starfish | ||
from starfish import utils | ||
|
||
|
||
def main(): | ||
poses = utils.random_rotations(1000) | ||
|
||
seq = starfish.Sequence.exhaustive( | ||
pose=poses, | ||
distance=20 | ||
) | ||
|
||
output_node = bpy.data.scenes["Render"].node_tree.nodes["File Output"] | ||
output_node.base_path = "/home/black/TSL/render" | ||
for i, frame in enumerate(seq): | ||
frame.setup(bpy.data.scenes['Real'], bpy.data.objects["Enhanced Cygnus"], bpy.data.objects["Camera"], | ||
bpy.data.objects["Sun"]) | ||
|
||
# add metadata to frame | ||
frame.timestamp = int(time.time() * 1000) | ||
frame.sequence_name = "1000 random poses" | ||
|
||
# set output path | ||
output_node.file_slots[0].path = f"real#_{i}" | ||
output_node.file_slots[1].path = f"mask#_{i}" | ||
|
||
# dump data to json | ||
with open(os.path.join(output_node.base_path, f"{i}.json"), "w") as f: | ||
f.write(frame.dumps()) | ||
|
||
# render | ||
bpy.ops.render.render(scene="Render") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() | ||
import numpy as np | ||
from mathutils import Euler | ||
from starfish import Sequence | ||
from starfish.utils import random_rotations | ||
from starfish.postprocessing import normalize_mask_colors, get_centroids_from_mask, get_bounding_boxes_from_mask | ||
|
||
# create a standard sequence of random configurations... | ||
seq = Sequence.standard( | ||
pose=random_rotations(1000), | ||
lighting=random_rotations(1000), | ||
background=random_rotations(1000), | ||
distance=np.linspace(10, 50, num=1000) | ||
) | ||
# ...or an exhaustive sequence of combinations... | ||
seq = Sequence.exhaustive( | ||
distance=[10, 20, 30], | ||
offset=[(0.25, 0.25), (0.25, 0.75), (0.75, 0.25), (0.75, 0.75)], | ||
pose=[Euler((0, 0, 0)), Euler((np.pi, 0, 0))] | ||
) | ||
# ...or an interpolated sequence between keyframes... | ||
seq = Sequence.interpolated( | ||
waypoints=Sequence.standard(distance=[10, 20], pose=[Euler((0, 0, 0)), Euler((0, np.pi, np.pi))]), | ||
counts=[100] | ||
) | ||
|
||
for i, frame in enumerate(seq): | ||
# non-starfish Blender stuff: e.g. setting file output paths | ||
bpy.data.scenes['Real'].node_tree.nodes['File Output'].file_slots[0].path = f'real_{i}.png' | ||
bpy.data.scenes['Mask'].node_tree.nodes['File Output'].file_slots[0].path = f'mask_{i}.png' | ||
|
||
# set up and render | ||
scene = bpy.data.scenes['Real'] | ||
frame.setup(scene, bpy.data.objects['MyObject'], | ||
bpy.data.objects['MyCamera'], bpy.data.objects['TheSun']) | ||
bpy.ops.render.render(scene=scene) | ||
|
||
scene = bpy.data.scenes['Mask'] | ||
frame.setup(scene, bpy.data.objects['MyObject'], | ||
bpy.data.objects['MyCamera'], bpy.data.objects['TheSun']) | ||
bpy.ops.render.render(scene=scene) | ||
|
||
# postprocessing | ||
label_map = {'object': (255, 255, 255), 'background': (0, 0, 0)} | ||
clean_mask = normalize_mask_colors(f'mask_{i}.png', label_map.values()) | ||
bboxes = get_bounding_boxes_from_mask(clean_mask, label_map) | ||
centroids = get_centroids_from_mask(clean_mask, label_map) | ||
|
||
# add some extra metadata | ||
frame.timestamp = int(time.time() * 1000) | ||
frame.sequence_name = '1000 random poses' | ||
frame.tags = ['front_view', 'left_view', 'right_view'] | ||
frame.bboxes = bboxes | ||
frame.centroids = centroids | ||
|
||
# save metadata to JSON | ||
with open(f'meta_{i}.json', 'w') as f: | ||
f.write(frame.dumps()) |
Oops, something went wrong.