summaryrefslogtreecommitdiffstats
path: root/python
diff options
context:
space:
mode:
authorWillem Jan Palenstijn <Willem.Jan.Palenstijn@cwi.nl>2015-12-04 16:35:00 +0100
committerWillem Jan Palenstijn <Willem.Jan.Palenstijn@cwi.nl>2015-12-04 16:35:00 +0100
commitf3ac1849f2b141ea1845752e1ca2317845e90e3a (patch)
treeadcdc9013af5cca4165857408ed7181ffdf0de64 /python
parent8144bf0397ee1913b830d82058ccd40df741f1b3 (diff)
parent0d015b1c91581ee5ef3e936f03e4c62fbc7ea362 (diff)
downloadastra-f3ac1849f2b141ea1845752e1ca2317845e90e3a.tar.gz
astra-f3ac1849f2b141ea1845752e1ca2317845e90e3a.tar.bz2
astra-f3ac1849f2b141ea1845752e1ca2317845e90e3a.tar.xz
astra-f3ac1849f2b141ea1845752e1ca2317845e90e3a.zip
Merge branch 'master'
Diffstat (limited to 'python')
-rw-r--r--python/astra/PyIncludes.pxd4
-rw-r--r--python/astra/__init__.py1
-rw-r--r--python/astra/creators.py15
-rw-r--r--python/astra/data2d_c.pyx27
-rw-r--r--python/astra/experimental.pyx84
-rw-r--r--python/astra/plugin.py121
-rw-r--r--python/astra/plugin_c.pyx67
-rw-r--r--python/astra/utils.pyx10
-rw-r--r--python/builder.py8
-rw-r--r--python/conda/build.sh16
-rw-r--r--python/conda/meta.yaml40
-rw-r--r--python/docSRC/conf.py4
-rw-r--r--python/docSRC/index.rst1
-rw-r--r--python/docSRC/plugins.rst8
14 files changed, 401 insertions, 5 deletions
diff --git a/python/astra/PyIncludes.pxd b/python/astra/PyIncludes.pxd
index 35dea5f..4a4ce43 100644
--- a/python/astra/PyIncludes.pxd
+++ b/python/astra/PyIncludes.pxd
@@ -62,6 +62,7 @@ cdef extern from "astra/VolumeGeometry2D.h" namespace "astra":
float32 getWindowMaxX()
float32 getWindowMaxY()
Config* getConfiguration()
+ bool isEqual(CVolumeGeometry2D*)
cdef extern from "astra/Float32Data2D.h" namespace "astra":
cdef cppclass CFloat32CustomMemory:
@@ -89,6 +90,7 @@ cdef extern from "astra/ProjectionGeometry2D.h" namespace "astra":
float32 getProjectionAngle(int)
float32 getDetectorWidth()
Config* getConfiguration()
+ bool isEqual(CProjectionGeometry2D*)
cdef extern from "astra/Float32Data2D.h" namespace "astra::CFloat32Data2D":
cdef enum TWOEDataType "astra::CFloat32Data2D::EDataType":
@@ -224,6 +226,7 @@ cdef extern from "astra/Float32VolumeData3DMemory.h" namespace "astra":
int getRowCount()
int getColCount()
int getSliceCount()
+ bool isInitialized()
@@ -255,6 +258,7 @@ cdef extern from "astra/Float32ProjectionData3DMemory.h" namespace "astra":
int getDetectorColCount()
int getDetectorRowCount()
int getAngleCount()
+ bool isInitialized()
cdef extern from "astra/Float32Data3D.h" namespace "astra":
cdef cppclass CFloat32Data3D:
diff --git a/python/astra/__init__.py b/python/astra/__init__.py
index 6c15d30..10ed74d 100644
--- a/python/astra/__init__.py
+++ b/python/astra/__init__.py
@@ -34,6 +34,7 @@ from . import algorithm
from . import projector
from . import projector3d
from . import matrix
+from . import plugin
from . import log
from .optomo import OpTomo
diff --git a/python/astra/creators.py b/python/astra/creators.py
index 68bc8a2..f3474d8 100644
--- a/python/astra/creators.py
+++ b/python/astra/creators.py
@@ -72,6 +72,10 @@ This method can be called in a number of ways:
``create_vol_geom(M, N, Z)``:
:returns: A 3D volume geometry of size :math:`M \\times N \\times Z`.
+``create_vol_geom(M, N, Z, minx, maxx, miny, maxy, minz, maxz)``:
+ :returns: A 3D volume geometry of size :math:`M \\times N \\times Z`, windowed as :math:`minx \\leq x \\leq maxx` and :math:`miny \\leq y \\leq maxy` and :math:`minz \\leq z \\leq maxz` .
+
+
"""
vol_geom = {'option': {}}
# astra_create_vol_geom(row_count)
@@ -122,6 +126,17 @@ This method can be called in a number of ways:
vol_geom['GridRowCount'] = varargin[0]
vol_geom['GridColCount'] = varargin[1]
vol_geom['GridSliceCount'] = varargin[2]
+ # astra_create_vol_geom(row_count, col_count, slice_count, min_x, max_x, min_y, max_y, min_z, max_z)
+ elif len(varargin) == 9:
+ vol_geom['GridRowCount'] = varargin[0]
+ vol_geom['GridColCount'] = varargin[1]
+ vol_geom['GridSliceCount'] = varargin[2]
+ vol_geom['option']['WindowMinX'] = varargin[3]
+ vol_geom['option']['WindowMaxX'] = varargin[4]
+ vol_geom['option']['WindowMinY'] = varargin[5]
+ vol_geom['option']['WindowMaxY'] = varargin[6]
+ vol_geom['option']['WindowMinZ'] = varargin[7]
+ vol_geom['option']['WindowMaxZ'] = varargin[8]
return vol_geom
diff --git a/python/astra/data2d_c.pyx b/python/astra/data2d_c.pyx
index 4919bf2..801fd8e 100644
--- a/python/astra/data2d_c.pyx
+++ b/python/astra/data2d_c.pyx
@@ -34,6 +34,9 @@ from cython cimport view
cimport PyData2DManager
from .PyData2DManager cimport CData2DManager
+cimport PyProjector2DManager
+from .PyProjector2DManager cimport CProjector2DManager
+
cimport PyXMLDocument
from .PyXMLDocument cimport XMLDocument
@@ -54,6 +57,8 @@ import operator
from six.moves import reduce
cdef CData2DManager * man2d = <CData2DManager * >PyData2DManager.getSingletonPtr()
+cdef CProjector2DManager * manProj = <CProjector2DManager * >PyProjector2DManager.getSingletonPtr()
+
cdef extern from "CFloat32CustomPython.h":
cdef cppclass CFloat32CustomPython:
@@ -164,7 +169,6 @@ def store(i, data):
cdef CFloat32Data2D * pDataObject = getObject(i)
fillDataObject(pDataObject, data)
-
def get_geometry(i):
cdef CFloat32Data2D * pDataObject = getObject(i)
cdef CFloat32ProjectionData2D * pDataObject2
@@ -179,6 +183,27 @@ def get_geometry(i):
raise Exception("Not a known data object")
return geom
+cdef CProjector2D * getProjector(i) except NULL:
+ cdef CProjector2D * proj = manProj.get(i)
+ if proj == NULL:
+ raise Exception("Projector not initialized.")
+ if not proj.isInitialized():
+ raise Exception("Projector not initialized.")
+ return proj
+
+def check_compatible(i, proj_id):
+ cdef CProjector2D * proj = getProjector(proj_id)
+ cdef CFloat32Data2D * pDataObject = getObject(i)
+ cdef CFloat32ProjectionData2D * pDataObject2
+ cdef CFloat32VolumeData2D * pDataObject3
+ if pDataObject.getType() == TWOPROJECTION:
+ pDataObject2 = <CFloat32ProjectionData2D * >pDataObject
+ return pDataObject2.getGeometry().isEqual(proj.getProjectionGeometry())
+ elif pDataObject.getType() == TWOVOLUME:
+ pDataObject3 = <CFloat32VolumeData2D * >pDataObject
+ return pDataObject3.getGeometry().isEqual(proj.getVolumeGeometry())
+ else:
+ raise Exception("Not a known data object")
def change_geometry(i, geom):
cdef Config *cfg
diff --git a/python/astra/experimental.pyx b/python/astra/experimental.pyx
new file mode 100644
index 0000000..da27504
--- /dev/null
+++ b/python/astra/experimental.pyx
@@ -0,0 +1,84 @@
+#-----------------------------------------------------------------------
+# Copyright: 2010-2015, iMinds-Vision Lab, University of Antwerp
+# 2014-2015, CWI, Amsterdam
+#
+# Contact: astra@uantwerpen.be
+# Website: http://sf.net/projects/astra-toolbox
+#
+# This file is part of the ASTRA Toolbox.
+#
+#
+# The ASTRA Toolbox is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# The ASTRA Toolbox is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with the ASTRA Toolbox. If not, see <http://www.gnu.org/licenses/>.
+#
+#-----------------------------------------------------------------------
+
+# distutils: language = c++
+# distutils: libraries = astra
+
+include "config.pxi"
+
+import six
+from .PyIncludes cimport *
+from libcpp.vector cimport vector
+
+cdef extern from "astra/CompositeGeometryManager.h" namespace "astra":
+ cdef cppclass CCompositeGeometryManager:
+ bool doFP(CProjector3D *, vector[CFloat32VolumeData3DMemory *], vector[CFloat32ProjectionData3DMemory *])
+ bool doBP(CProjector3D *, vector[CFloat32VolumeData3DMemory *], vector[CFloat32ProjectionData3DMemory *])
+
+cdef extern from *:
+ CFloat32VolumeData3DMemory * dynamic_cast_vol_mem "dynamic_cast<astra::CFloat32VolumeData3DMemory*>" (CFloat32Data3D * ) except NULL
+ CFloat32ProjectionData3DMemory * dynamic_cast_proj_mem "dynamic_cast<astra::CFloat32ProjectionData3DMemory*>" (CFloat32Data3D * ) except NULL
+
+cimport PyProjector3DManager
+from .PyProjector3DManager cimport CProjector3DManager
+cimport PyData3DManager
+from .PyData3DManager cimport CData3DManager
+
+cdef CProjector3DManager * manProj = <CProjector3DManager * >PyProjector3DManager.getSingletonPtr()
+cdef CData3DManager * man3d = <CData3DManager * >PyData3DManager.getSingletonPtr()
+
+def do_composite(projector_id, vol_ids, proj_ids, t):
+ cdef vector[CFloat32VolumeData3DMemory *] vol
+ cdef CFloat32VolumeData3DMemory * pVolObject
+ cdef CFloat32ProjectionData3DMemory * pProjObject
+ for v in vol_ids:
+ pVolObject = dynamic_cast_vol_mem(man3d.get(v))
+ if pVolObject == NULL:
+ raise Exception("Data object not found")
+ if not pVolObject.isInitialized():
+ raise Exception("Data object not initialized properly")
+ vol.push_back(pVolObject)
+ cdef vector[CFloat32ProjectionData3DMemory *] proj
+ for v in proj_ids:
+ pProjObject = dynamic_cast_proj_mem(man3d.get(v))
+ if pProjObject == NULL:
+ raise Exception("Data object not found")
+ if not pProjObject.isInitialized():
+ raise Exception("Data object not initialized properly")
+ proj.push_back(pProjObject)
+ cdef CCompositeGeometryManager m
+ cdef CProjector3D * projector = manProj.get(projector_id) # may be NULL
+ if t == "FP":
+ if not m.doFP(projector, vol, proj):
+ raise Exception("Failed to perform FP")
+ else:
+ if not m.doBP(projector, vol, proj):
+ raise Exception("Failed to perform BP")
+
+def do_composite_FP(projector_id, vol_ids, proj_ids):
+ do_composite(projector_id, vol_ids, proj_ids, "FP")
+
+def do_composite_BP(projector_id, vol_ids, proj_ids):
+ do_composite(projector_id, vol_ids, proj_ids, "BP")
diff --git a/python/astra/plugin.py b/python/astra/plugin.py
new file mode 100644
index 0000000..3e3528d
--- /dev/null
+++ b/python/astra/plugin.py
@@ -0,0 +1,121 @@
+#-----------------------------------------------------------------------
+#Copyright 2013 Centrum Wiskunde & Informatica, Amsterdam
+#
+#Author: Daniel M. Pelt
+#Contact: D.M.Pelt@cwi.nl
+#Website: http://dmpelt.github.io/pyastratoolbox/
+#
+#
+#This file is part of the Python interface to the
+#All Scale Tomographic Reconstruction Antwerp Toolbox ("ASTRA Toolbox").
+#
+#The Python interface to the ASTRA Toolbox is free software: you can redistribute it and/or modify
+#it under the terms of the GNU General Public License as published by
+#the Free Software Foundation, either version 3 of the License, or
+#(at your option) any later version.
+#
+#The Python interface to the ASTRA Toolbox is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+#GNU General Public License for more details.
+#
+#You should have received a copy of the GNU General Public License
+#along with the Python interface to the ASTRA Toolbox. If not, see <http://www.gnu.org/licenses/>.
+#
+#-----------------------------------------------------------------------
+
+from . import plugin_c as p
+from . import log
+from . import data2d
+from . import data2d_c
+from . import data3d
+from . import projector
+import inspect
+import traceback
+
+class base(object):
+
+ def astra_init(self, cfg):
+ args, varargs, varkw, defaults = inspect.getargspec(self.initialize)
+ if not defaults is None:
+ nopt = len(defaults)
+ else:
+ nopt = 0
+ if nopt>0:
+ req = args[2:-nopt]
+ opt = args[-nopt:]
+ else:
+ req = args[2:]
+ opt = []
+
+ try:
+ optDict = cfg['options']
+ except KeyError:
+ optDict = {}
+
+ cfgKeys = set(optDict.keys())
+ reqKeys = set(req)
+ optKeys = set(opt)
+
+ if not reqKeys.issubset(cfgKeys):
+ for key in reqKeys.difference(cfgKeys):
+ log.error("Required option '" + key + "' for plugin '" + self.__class__.__name__ + "' not specified")
+ raise ValueError("Missing required options")
+
+ if not cfgKeys.issubset(reqKeys | optKeys):
+ log.warn(self.__class__.__name__ + ": unused configuration option: " + str(list(cfgKeys.difference(reqKeys | optKeys))))
+
+ args = [optDict[k] for k in req]
+ kwargs = dict((k,optDict[k]) for k in opt if k in optDict)
+ self.initialize(cfg, *args, **kwargs)
+
+class ReconstructionAlgorithm2D(base):
+
+ def astra_init(self, cfg):
+ self.pid = cfg['ProjectorId']
+ self.s = data2d.get_shared(cfg['ProjectionDataId'])
+ self.v = data2d.get_shared(cfg['ReconstructionDataId'])
+ self.vg = projector.volume_geometry(self.pid)
+ self.pg = projector.projection_geometry(self.pid)
+ if not data2d_c.check_compatible(cfg['ProjectionDataId'], self.pid):
+ raise ValueError("Projection data and projector not compatible")
+ if not data2d_c.check_compatible(cfg['ReconstructionDataId'], self.pid):
+ raise ValueError("Reconstruction data and projector not compatible")
+ super(ReconstructionAlgorithm2D,self).astra_init(cfg)
+
+class ReconstructionAlgorithm3D(base):
+
+ def astra_init(self, cfg):
+ self.pid = cfg['ProjectorId']
+ self.s = data3d.get_shared(cfg['ProjectionDataId'])
+ self.v = data3d.get_shared(cfg['ReconstructionDataId'])
+ self.vg = data3d.get_geometry(cfg['ReconstructionDataId'])
+ self.pg = data3d.get_geometry(cfg['ProjectionDataId'])
+ super(ReconstructionAlgorithm3D,self).astra_init(cfg)
+
+def register(className):
+ """Register plugin with ASTRA.
+
+ :param className: Class name or class object to register
+ :type className: :class:`str` or :class:`class`
+
+ """
+ p.register(className)
+
+def get_registered():
+ """Get dictionary of registered plugins.
+
+ :returns: :class:`dict` -- Registered plugins.
+
+ """
+ return p.get_registered()
+
+def get_help(name):
+ """Get help for registered plugin.
+
+ :param name: Plugin name to get help for
+ :type name: :class:`str`
+ :returns: :class:`str` -- Help string (docstring).
+
+ """
+ return p.get_help(name) \ No newline at end of file
diff --git a/python/astra/plugin_c.pyx b/python/astra/plugin_c.pyx
new file mode 100644
index 0000000..8d6816b
--- /dev/null
+++ b/python/astra/plugin_c.pyx
@@ -0,0 +1,67 @@
+#-----------------------------------------------------------------------
+#Copyright 2013 Centrum Wiskunde & Informatica, Amsterdam
+#
+#Author: Daniel M. Pelt
+#Contact: D.M.Pelt@cwi.nl
+#Website: http://dmpelt.github.io/pyastratoolbox/
+#
+#
+#This file is part of the Python interface to the
+#All Scale Tomographic Reconstruction Antwerp Toolbox ("ASTRA Toolbox").
+#
+#The Python interface to the ASTRA Toolbox is free software: you can redistribute it and/or modify
+#it under the terms of the GNU General Public License as published by
+#the Free Software Foundation, either version 3 of the License, or
+#(at your option) any later version.
+#
+#The Python interface to the ASTRA Toolbox is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+#GNU General Public License for more details.
+#
+#You should have received a copy of the GNU General Public License
+#along with the Python interface to the ASTRA Toolbox. If not, see <http://www.gnu.org/licenses/>.
+#
+#-----------------------------------------------------------------------
+# distutils: language = c++
+# distutils: libraries = astra
+
+import six
+import inspect
+
+from libcpp.string cimport string
+from libcpp cimport bool
+
+cdef CPluginAlgorithmFactory *fact = getSingletonPtr()
+
+from . import utils
+
+cdef extern from "astra/PluginAlgorithm.h" namespace "astra":
+ cdef cppclass CPluginAlgorithmFactory:
+ bool registerPlugin(string className)
+ bool registerPlugin(string name, string className)
+ bool registerPluginClass(object className)
+ bool registerPluginClass(string name, object className)
+ object getRegistered()
+ string getHelp(string name)
+
+cdef extern from "astra/PluginAlgorithm.h" namespace "astra::CPluginAlgorithmFactory":
+ cdef CPluginAlgorithmFactory* getSingletonPtr()
+
+def register(className, name=None):
+ if inspect.isclass(className):
+ if name==None:
+ fact.registerPluginClass(className)
+ else:
+ fact.registerPluginClass(six.b(name), className)
+ else:
+ if name==None:
+ fact.registerPlugin(six.b(className))
+ else:
+ fact.registerPlugin(six.b(name), six.b(className))
+
+def get_registered():
+ return fact.getRegistered()
+
+def get_help(name):
+ return utils.wrap_from_bytes(fact.getHelp(six.b(name)))
diff --git a/python/astra/utils.pyx b/python/astra/utils.pyx
index 260c308..07727ce 100644
--- a/python/astra/utils.pyx
+++ b/python/astra/utils.pyx
@@ -29,9 +29,13 @@
cimport numpy as np
import numpy as np
import six
+if six.PY3:
+ import builtins
+else:
+ import __builtin__
from libcpp.string cimport string
-from libcpp.list cimport list
from libcpp.vector cimport vector
+from libcpp.list cimport list
from cython.operator cimport dereference as deref, preincrement as inc
from cpython.version cimport PY_MAJOR_VERSION
@@ -91,6 +95,8 @@ cdef void readDict(XMLNode root, _dc):
dc = convert_item(_dc)
for item in dc:
val = dc[item]
+ if isinstance(val, __builtins__.list) or isinstance(val, tuple):
+ val = np.array(val,dtype=np.float64)
if isinstance(val, np.ndarray):
if val.size == 0:
break
@@ -125,6 +131,8 @@ cdef void readOptions(XMLNode node, dc):
val = dc[item]
if node.hasOption(item):
raise Exception('Duplicate Option: %s' % item)
+ if isinstance(val, __builtins__.list) or isinstance(val, tuple):
+ val = np.array(val,dtype=np.float64)
if isinstance(val, np.ndarray):
if val.size == 0:
break
diff --git a/python/builder.py b/python/builder.py
index cfdb7d1..5322182 100644
--- a/python/builder.py
+++ b/python/builder.py
@@ -41,6 +41,12 @@ try:
usecuda=True
except KeyError:
pass
+try:
+ if os.environ['CL'].find('/DASTRA_CUDA')!=-1:
+ usecuda=True
+except KeyError:
+ pass
+
cfgToWrite = 'DEF HAVE_CUDA=' + str(usecuda) + "\n"
cfgHasToBeUpdated = True
@@ -65,7 +71,7 @@ ext_modules = cythonize("astra/*.pyx", language_level=2)
cmdclass = { 'build_ext': build_ext }
setup (name = 'PyASTRAToolbox',
- version = '1.6',
+ version = '1.7',
description = 'Python interface to the ASTRA-Toolbox',
author='D.M. Pelt',
author_email='D.M.Pelt@cwi.nl',
diff --git a/python/conda/build.sh b/python/conda/build.sh
new file mode 100644
index 0000000..814ea7e
--- /dev/null
+++ b/python/conda/build.sh
@@ -0,0 +1,16 @@
+cd build/linux
+./autogen.sh
+./configure --with-python --with-cuda=$CUDA_ROOT --prefix=$PREFIX
+if [ $MAKEOPTS == '<UNDEFINED>' ]
+ then
+ MAKEOPTS=""
+fi
+make $MAKEOPTS install-libraries
+make $MAKEOPTS python-root-install
+LIBPATH=lib
+if [ $ARCH == 64 ]
+ then
+ LIBPATH+=64
+fi
+cp -P $CUDA_ROOT/$LIBPATH/libcudart.so.* $PREFIX/lib
+cp -P $CUDA_ROOT/$LIBPATH/libcufft.so.* $PREFIX/lib
diff --git a/python/conda/meta.yaml b/python/conda/meta.yaml
new file mode 100644
index 0000000..e1adc83
--- /dev/null
+++ b/python/conda/meta.yaml
@@ -0,0 +1,40 @@
+package:
+ name: astra-toolbox
+ version: '1.7'
+
+source:
+ git_url: https://github.com/astra-toolbox/astra-toolbox.git
+ git_tag: v1.7
+
+build:
+ number: 0
+ script_env:
+ - CUDA_ROOT
+ - MAKEOPTS
+
+test:
+ imports:
+ - astra
+
+requirements:
+ build:
+ - python
+ - cython >=0.13
+ - numpy
+ - six
+
+ run:
+ - python
+ - numpy
+ - scipy
+ - six
+
+
+about:
+ home: http://sourceforge.net/p/astra-toolbox/wiki/Home/
+ license: GPLv3
+ summary: 'The ASTRA Toolbox is a Python toolbox of high-performance GPU primitives for 2D and 3D tomography.'
+
+# See
+# http://docs.continuum.io/conda/build.html for
+# more information about meta.yaml
diff --git a/python/docSRC/conf.py b/python/docSRC/conf.py
index e54bbb8..cc95a80 100644
--- a/python/docSRC/conf.py
+++ b/python/docSRC/conf.py
@@ -48,9 +48,9 @@ copyright = u'2013, Centrum Wiskunde & Informatica, Amsterdam'
# built documents.
#
# The short X.Y version.
-version = '1.6'
+version = '1.7'
# The full version, including alpha/beta/rc tags.
-release = '1.6'
+release = '1.7'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/python/docSRC/index.rst b/python/docSRC/index.rst
index b7cc6d6..dcc6590 100644
--- a/python/docSRC/index.rst
+++ b/python/docSRC/index.rst
@@ -19,6 +19,7 @@ Contents:
creators
functions
operator
+ plugins
matlab
astra
.. astra
diff --git a/python/docSRC/plugins.rst b/python/docSRC/plugins.rst
new file mode 100644
index 0000000..dc7c607
--- /dev/null
+++ b/python/docSRC/plugins.rst
@@ -0,0 +1,8 @@
+Plugins: the :mod:`plugin` module
+=========================================
+
+.. automodule:: astra.plugin
+ :members:
+ :undoc-members:
+ :show-inheritance:
+