set the node to the new expression and perform it. The input can accept any type of data
and the output type is determined when the node is run.
The four inputs are assigned to the variables a, b, c, and d. They are typically (but not necessarily) images
-or scalar values.
+or numeric values.
The standard operators +,/,*,- and ^ all have their usual meanings. When applied to images they work in
a pixel-wise fashion, so if a is an image, 2*a will double the brightness. If b is also an image,
a+b will add the two images, pixel by pixel. There are two non-standard operators: . for properties
@@ -251,7 +251,7 @@
Image/numeric operators:
-
All operators can act on images and scalars (numeric values),
+
All operators can act on images, 1D vectors and scalars
with the exception of . and $ which have images on the left-hand side and identifiers
or integers on the right-hand side.
Those operators marked with (can act on ROIs) can also act on pairs of ROIs (regions of interest, see below).
@@ -293,11 +293,18 @@
Binary operations on image pairs
This probably isn't what you wanted. Note that this is obviously not an issue when an operation is being performed
on bands in a single image.
-
binary operators on images with regions of interest
+
Binary operators on images with regions of interest
If one of the two images has an ROI, the operation is only performed on that ROI; the remaining area of output is
taken from the image without an ROI. If both images have an ROI an error will result - it is likely that this
is a mistake on the user's part, and doing something more "intelligent" might conceal this. The desired result
can be achieved using expr nodes on ROIs and an importroi node.
+
Operations with vectors
+
Some functions can generate vectors, such as mean for getting the means of the bands, and vec for generating
+vectors by hand.
+
If an image is used in a binary operation with a vector on the other side, the vector must have the same number of
+elements as there are bands in the image. The operation will be performed on each band. Consider a 3-band image
+and the vector [2,3,4]. If we multiply them, the result will an image with the first band multiplied by 2,
+the second band multiplied by 3, and the third band multiplied by 4.
Operators on ROIs themselves (as opposed to images with ROIs)
@@ -373,6 +380,8 @@
Band extraction
+
Band extraction can also be performed with vectors provided the vector elements are numeric (i.e. wavelengths):
+a $ vec(640,550,440) is valid.
Properties
Properties are indicated by the . operator, e.g. a.w to find an
image's width.
This node allows a region of interest to be composed from several regions of interest using an expression and
-imposed on an image. Several ROIs can be created within the node itself by using the "Add ROI" button.
+imposed on an image.
+
It is not a node for creating several ROIs at once - the output is always a single ROI.
+
ROIs can be created for use within the expression by using the "Add ROI" button.
These will be assigned to the variables a,b,c.. within the expression, and can be edited by:
clicking on their label in the left-most column of the table (to select the entire row) and then clicking and dragging on the canvas,
double clicking on the description text in the table to open a numerical editor (not for poly or painted).
Additional ROIs can be connected to the p, q, r inputs; these will be assigned to those variables within the expression.
-The input image is available as the variable 'img', so it is possible
-to access the image's original region of interest (or the union of all ROIs if it has more than one) by using roi(img).
+The input image's ROIs are combined into a single ROI and assigned to the variable 'i'.
+The input image itself is available as the variable 'img'.
Other properties of the image are available and other calculations may be made, but the result of the expression must be an ROI.
Examples:
diff --git a/devguide/classes.md~ b/devguide/classes.md~
index 6eaa0ecf..92d32e2a 100644
--- a/devguide/classes.md~
+++ b/devguide/classes.md~
@@ -123,12 +123,15 @@ This is the fundamental numeric type, consisting of
* uncertainty value (standard deviation)
* DQ bits
-It's usually used for scalars but can also hold array data. If it
-does hold array data, the three elements must be the same shape.
+It's usually used for scalars but can also hold array data - internally
+ImageCubes (or parts of them) are converted into array Values for maths.
+If it does hold array data, the three elements must be the same shape.
This type supports mathematical operations which propagate uncertainty
and DQ.
+More on how Values work [here](values.md)
+
@@@info
**Note**: You may wonder why ImageCube and SubImageCube don't use
Value internally. The answer is simply historical reasons: they
diff --git a/devguide/classes/index.html b/devguide/classes/index.html
index 5425e608..8b3497f8 100644
--- a/devguide/classes/index.html
+++ b/devguide/classes/index.html
@@ -310,7 +310,9 @@
pcot.value.Value
It's usually used for scalars but can also hold array data - internally
ImageCubes (or parts of them) are converted into array Values for maths.
-If it does hold array data, the three elements must be the same shape.
+If it does hold array data, the three elements must be the same shape.
+It's possible to create 1D vectors in an expr node using square brackets,
+and some functions and operations return such vectors.
This type supports mathematical operations which propagate uncertainty
and DQ.
diff --git a/devguide/operators.md~ b/devguide/operators.md~
new file mode 100644
index 00000000..e577452d
--- /dev/null
+++ b/devguide/operators.md~
@@ -0,0 +1,40 @@
+# Behaviour of binary and unary operators on Value and Datum
+
+* **scalar** refers to a Value or NUMBER Datum with a single numeric value
+* **vector** refers to a Value or NUMBER Datum with a 1D vector
+
+## Datum within *expr* node
+
+### Binary operators
+
+|operator and types|behaviour|
+|--------------|------|
+|+|pixelwise addition|
+|-|pixelwise subtraction|
+|*|pixelwise multiplication|
+|/|pixelwise division|
+|^|pixelwise exponentiation|
+|&|maximum of two values (essentially a fuzzy AND operator)|
+|\||minimum of two values (essentially a fuzzy OR operator)|
+
+### Behaviour for different types of operands
+
+* **Scalar and Image**, **Image and Scalar**: the scalar is applied to every pixel in the image, so if `a` is an image,
+then `a*2` multiplies every the value of every pixel in every band by 2, and `1/a`
+reciprocates every pixel.
+
+* **Vector and Image**, **Image and Vector**: The vector must have the same number of elements as there are bands
+in the image. The corresponding element in the vector is applied to each band in the image, so if `a` is
+a 3-band image, then `a*vec(1,2,3)` will multiply the pixel values in the channels by 1, 2 and 3 respectively.
+
+* **Vector and Scalar, Scalar and Vector**: the scalar is applied to each element in the vector: `2*vec(1,2) = [2,4]`
+
+* **Vector and Vector:** The operator is applied to the vectors element-wise: `vec(1,2) + vec(3,4) = [4,6]`
+
+* **Scalar and Scalar:** does exactly what you think it does.
+
+### More unusual binary operators
+
+* **$ : band extraction -** - LHS must be image, RHS must be a wavelength or identifier or a vector of wavelengths: `a$R`, `a$640`, `a$vec(640,550,440)` will all work. You can also do `a$_0` to extract band
+by index, but this won't work with vectors (reason - we could allow `a$5` to extract band 5, but what if we end up being able to process hyperspectral imagery where `a$440` could be either a wavelength
+or a band?)
diff --git a/devguide/operators/index.html b/devguide/operators/index.html
new file mode 100644
index 00000000..60c8da33
--- /dev/null
+++ b/devguide/operators/index.html
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Behaviour of binary and unary operators on Value and Datum - PCOT Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Behaviour of binary and unary operators on Value and Datum
+
+
scalar refers to a Value or NUMBER Datum with a single numeric value
+
vector refers to a Value or NUMBER Datum with a 1D vector
+
+
Datum within expr node
+
See the Expr node docs.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Search
+
+
+
+
From here you can search these documents. Enter your search terms below.
+
+
+
+
+
+
+
+
+
+
+
Keyboard Shortcuts
+
+
+
+
+
+
+
Keys
+
Action
+
+
+
+
+
?
+
Open this help
+
+
+
n
+
Next page
+
+
+
p
+
Previous page
+
+
+
s
+
Search
+
+
+
+
+
+
+
+
+
+
+
diff --git a/devguide/values.md~ b/devguide/values.md~
index 591494ec..81691d22 100644
--- a/devguide/values.md~
+++ b/devguide/values.md~
@@ -1,11 +1,14 @@
# How Values work
-Value is PCOT's fundamental type for working with uncertain numerical data. Values are triplets of:
+Value is PCOT's fundamental type for working with uncertain numerical data.
+Values are triplets of:
* a 32-bit floating point **nominal** value (i.e. a mean),
* a 32-bit floating point **uncertainty** value (as standard deviation) around that mean,
* and a set of 16 **data quality** (DQ) bits.
+These are usually scalar, but 1D vectors can also be created.
+
As a user of PCOT you may never encounter Values, but they are used internally
whenever any operations involving uncertainty are done. Here are the typical
situations where that occurs:
@@ -127,11 +130,11 @@ all Datum objects.
### Datumfuncs of single numeric/scalar arguments
-Functions of a single Datum are sometimes written using an "inner wrapper", which will
+Functions of a single numeric or image Datum are sometimes written using an "inner wrapper", which will
turn imagecubes and numbers into Value objects in a similar way to how the semantic binop
wrappers work for operations. It will also wrap the resulting Value in a Datum.
An example is `pcot.expression.register.funcWrapper`. Here is the datumfunc `sin` in full:
-```
+```python
@datumfunc
def sin(a):
"""
@@ -145,12 +148,22 @@ will deal with it. However, funcWrapper can only deal with images and scalars.
### The stats wrapper
-Another datumfunc inner wrapper which can be used is statsWrapper. This takes a variable number
-of image and numeric arguments which it aggregates into a single 1D array, on which it then
-runs its function. It's typically for taking the mean or sum of images and/or values
+Another datumfunc inner wrapper which can be used is statsWrapper. This wraps a function
+which takes a (nominal,uncertainty,dq) tuple and returns another tuple of the same type.
+It does the following:
+
+* If provided with a numeric value, calls the wrapped function and creates a Value from the
+returned data.
+* If provided with an ImageCube, gets the subimage, splits it into bands, and calls the
+wrapped function on the non-BAD values in each band. The results for each band - assumed to be
+scalar - are converted into a vector Value with one value for each band.
-@@@warning
-I don't like how this works at the moment; the results are often meaningless or prone to
-misinterpretation. Finding the mean of a multiband image should result in a vector of values,
-ideally.
-@@@
+This lets us write the `mean` like this:
+```
+@datumfunc
+def mean(val):
+ return stats_wrapper(val,
+ lambda n, u, d: (np.mean(n), pooled_sd(n, u), pcot.dq.NONE))
+```
+and it will work on scalar Values, vector Values and images - in the latter case, producing a vector
+Value.
diff --git a/devguide/values/index.html b/devguide/values/index.html
index 4d9552c5..b834fc3c 100644
--- a/devguide/values/index.html
+++ b/devguide/values/index.html
@@ -181,12 +181,14 @@
How Values work
-
Value is PCOT's fundamental type for working with uncertain numerical data. Values are triplets of:
+
Value is PCOT's fundamental type for working with uncertain numerical data.
+Values are triplets of:
a 32-bit floating point nominal value (i.e. a mean),
a 32-bit floating point uncertainty value (as standard deviation) around that mean,
and a set of 16 data quality (DQ) bits.
+
These are usually scalar, but 1D vectors can also be created.
As a user of PCOT you may never encounter Values, but they are used internally
whenever any operations involving uncertainty are done. Here are the typical
situations where that occurs:
@@ -299,26 +301,36 @@
Datumfuncs of single numer
Functions of a single numeric or image Datum are sometimes written using an "inner wrapper", which will
turn imagecubes and numbers into Value objects in a similar way to how the semantic binop
wrappers work for operations. It will also wrap the resulting Value in a Datum.
-An example is pcot.expression.register.funcWrapper. Here is the datumfunc sin in full:
-
@datumfunc
+An example is pcot.datumfuncs.func_wrapper. Here is the datumfunc sin in full:
+
@datumfunc
def sin(a):
"""
Calculate sine of an angle in radians
@param a:img,number:the angle (or image in which each pixel is a single angle)
"""
- return funcWrapper(lambda xx: xx.sin(), a)
+ return func_wrapper(lambda xx: xx.sin(), a)
-
We don't need worry about whether the argument is an image or a scalar, because the funcWrapper
-will deal with it. However, funcWrapper can only deal with images and scalars.
+
We don't need worry about whether the argument is an image or a scalar, because the func_wrapper
+will deal with it. However, func_wrapper can only deal with images and scalars.
The stats wrapper
-
Another datumfunc inner wrapper which can be used is statsWrapper. This takes a variable number
-of image and numeric arguments which it aggregates into a single 1D array, on which it then
-runs its function. It's typically for taking the mean or sum of images and/or values
-
-
I don't like how this works at the moment; the results are often meaningless or prone to
-misinterpretation. Finding the mean of a multiband image should result in a vector of values,
-ideally.
-
+
Another datumfunc inner wrapper which can be used is stats_wrapper. This wraps a function
+which takes a (nominal,uncertainty,dq) tuple and returns another tuple of the same type.
+It does the following:
+
+
If provided with a numeric value, calls the wrapped function and creates a Value from the
+returned data.
+
If provided with an ImageCube, gets the subimage, splits it into bands, and calls the
+wrapped function on the non-BAD values in each band. The results for each band - assumed to be
+scalar - are converted into a vector Value with one value for each band.
+
+
This lets us write the mean like this:
+
@datumfunc
+def mean(val):
+ return stats_wrapper(val,
+ lambda n, u, d: (np.mean(n), pooled_sd(n, u), pcot.dq.NONE))
+
+
and it will work on scalar Values, vector Values and images - in the latter case, producing a vector
+Value.
diff --git a/search/search_index.json b/search/search_index.json
index bd1bc0a9..7338afcb 100644
--- a/search/search_index.json
+++ b/search/search_index.json
@@ -1 +1 @@
-{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"PCOT - the PanCam Operations Toolkit PCOT is a Python program and library which allows users to manipulate multispectral images and other data from the ExoMars Rosalind Franklin rover. This is an alpha version with serious limitations. There is no calibration code of any kind (although the preliminaries are in place) PDS4 import capabilities are poor - we only support spec-rad products from the ExoMars PANCAM instrument - but we also support ENVI provided the images are 32-bit float BSQ RGB PNGs multispectral images made of multiple monochrome PNGs multispectral images made of monochrome raw files in various formats and adding new PDS4 formats should be relatively straightforward There are probably a lot of useful operations missing There are certainly a lot of bugs. Getting Started User Guide Dev Roadmap Automatically generated node and function docs Reporting bugs There are known issues which can stop PCOT running - see issues . If your problem isn't described there please create a new issue on Github or contact the Aberystwyth team. Release history Can be found here","title":"PCOT - the PanCam Operations Toolkit"},{"location":"#pcot-the-pancam-operations-toolkit","text":"PCOT is a Python program and library which allows users to manipulate multispectral images and other data from the ExoMars Rosalind Franklin rover. This is an alpha version with serious limitations. There is no calibration code of any kind (although the preliminaries are in place) PDS4 import capabilities are poor - we only support spec-rad products from the ExoMars PANCAM instrument - but we also support ENVI provided the images are 32-bit float BSQ RGB PNGs multispectral images made of multiple monochrome PNGs multispectral images made of monochrome raw files in various formats and adding new PDS4 formats should be relatively straightforward There are probably a lot of useful operations missing There are certainly a lot of bugs. Getting Started User Guide Dev Roadmap Automatically generated node and function docs","title":"PCOT - the PanCam Operations Toolkit"},{"location":"#reporting-bugs","text":"There are known issues which can stop PCOT running - see issues . If your problem isn't described there please create a new issue on Github or contact the Aberystwyth team.","title":"Reporting bugs"},{"location":"#release-history","text":"Can be found here","title":"Release history"},{"location":"releases/","text":"Releases Production releases None Beta releases None Alpha releases 0.7.0-alpha 2024-05-03 EAST PENTIRE Very many more unit tests Bug fixes Complete rewrite of spectrum system, using the SpectrumSet object Multidot now does painted regions and floodfill Joseph's PCT detector outputs image with ROIs Dump removed and sink enhanced TabData shows sources Inputs decoupled from Sources - Sources now use composition, not inheritance Comment box for nodes removed (it was never used) Direct multifile loading Direct PDS4 loading - required refactoring of entire PDS4 layer Direct ENVI loading Raw file loading from mono images supporting lots of formats Loader presets for multifile Operator overloading on Datum objects The \"datumfunc\" system replacing hand-registration of functions flip and rotate functions (datumfuncs) String datum objects and strings usable in expr Docs on library usage Changes to nodes so that slow nodes can be disabled and very slow nodes start disabled. This functionality existed before, but was \"ad-hoc\" Document.changed() is now Document.run() and forces disabled nodes to run Most nodes now store data in their outputs rather than a \"node.out\" which is then written to an output Changes to multidot - doc improvements, UX and bug fixes 0.6.1-alpha 2023-10-04 DYNAS COVE (minor release) Multifile input can accept BMP files Better multifile documentation Filter specifications are no longer hardwired and are loaded from CSVs PANCAM and AUPE filters are default filter sets loaded in Others can be specified in a config file (and can override PANCAM and AUPE) Filter set no longer required by PDS4 input 0.6.0-alpha 2023-09-11 DRIFT STONES uncertainty and error bit propagation in expr and all nodes Testing quality and propagation rules (see Principles ) Test graphs for nodes and other high-level functionality Test nodes for those graphs Tabular output on spectrum and histogram nodes Gen node for test patterns Refactoring of Datum Utility nodes - e.g. roidq for generating an ROI from DQ bits Output enhancements Gradient node can export to PDF Annotations (e.g. text labels) are now drawn on the painter at high res, and have been refactored hugely Annotations use thickness zero by default (the Qt \"cosmetic\" thickness) PCT detector node ROI negation and refactoring of operators roiexpr node for composing ROIs using expressions Crude band depth node (needs work) A lot of bug fixes and regression fixes 0.5.0-alpha 2023-03-08 CARLENNO ROUND Data quality and bit viewing on canvas Palette and canvas interface with collapsable sections Annotations (ROIs, legends) are now drawn onto the canvas rather than the image Export to PDF, SVG and PNG with those hi-res annotations gradient is much simpler, can overlay onto the image and can draw a legend 0.4.0-alpha 2022-11-30 CAER BRAN Annotation system entirely rewritten PDF/PNG/SVG exporter Gradient legend annotation Doc updates 0.3.0-alpha 2022-10-27 BEACON HUT Open source! PDS4 importer with proctools Ad-hoc Spectrum viewer in canvas Significant rewrite of expression execution code, permitting custom types to have operations defined on them Direct input method for library use Improved default RGB mapping in canvas Testing Basics testing Testing of the operating principles (see Principles ) Source rules ROI rules rect node can now be edited numerically circle node can add circular ROIs, which can be edited numerically. 0.2.0-alpha 2022-04-21 ANJARDEN SPRING \"pixel scanning\" on canvases, shows spectrum of pixel when active custom cursor, pixel under cursor highlighted at high zooms text toggle button (currently unused) fixes to example plugin added macos.spec for pyinstaller archive system shows progress when loading each archive element Issue 1 fix (multiple tab closes when main window reinitialised) dynamic type determination for expr output can connect incompatible node outputs to inputs; indicated as red arrows infinite recursion in ROI nodes fix splash screen for Windows/Linux pyinstaller startup (not yet supported on MacOS pyinstaller) custom Datum and connection brush types now easy expr resizing regression fix multiple input buttons after load/resize fix status bar repaints on ui.msg, so it's updated in load and perform context menu on editable text caused a crash (bug in Qt). Workaround. comment boxes 0.1.0-alpha 2022-03-02 ALSIA WELL Initial alpha release outside Aberystwyth","title":"Releases"},{"location":"releases/#releases","text":"","title":"Releases"},{"location":"releases/#production-releases","text":"None","title":"Production releases"},{"location":"releases/#beta-releases","text":"None","title":"Beta releases"},{"location":"releases/#alpha-releases","text":"","title":"Alpha releases"},{"location":"releases/#070-alpha-2024-05-03-east-pentire","text":"Very many more unit tests Bug fixes Complete rewrite of spectrum system, using the SpectrumSet object Multidot now does painted regions and floodfill Joseph's PCT detector outputs image with ROIs Dump removed and sink enhanced TabData shows sources Inputs decoupled from Sources - Sources now use composition, not inheritance Comment box for nodes removed (it was never used) Direct multifile loading Direct PDS4 loading - required refactoring of entire PDS4 layer Direct ENVI loading Raw file loading from mono images supporting lots of formats Loader presets for multifile Operator overloading on Datum objects The \"datumfunc\" system replacing hand-registration of functions flip and rotate functions (datumfuncs) String datum objects and strings usable in expr Docs on library usage Changes to nodes so that slow nodes can be disabled and very slow nodes start disabled. This functionality existed before, but was \"ad-hoc\" Document.changed() is now Document.run() and forces disabled nodes to run Most nodes now store data in their outputs rather than a \"node.out\" which is then written to an output Changes to multidot - doc improvements, UX and bug fixes","title":"0.7.0-alpha 2024-05-03 EAST PENTIRE"},{"location":"releases/#061-alpha-2023-10-04-dynas-cove-minor-release","text":"Multifile input can accept BMP files Better multifile documentation Filter specifications are no longer hardwired and are loaded from CSVs PANCAM and AUPE filters are default filter sets loaded in Others can be specified in a config file (and can override PANCAM and AUPE) Filter set no longer required by PDS4 input","title":"0.6.1-alpha 2023-10-04 DYNAS COVE (minor release)"},{"location":"releases/#060-alpha-2023-09-11-drift-stones","text":"uncertainty and error bit propagation in expr and all nodes Testing quality and propagation rules (see Principles ) Test graphs for nodes and other high-level functionality Test nodes for those graphs Tabular output on spectrum and histogram nodes Gen node for test patterns Refactoring of Datum Utility nodes - e.g. roidq for generating an ROI from DQ bits Output enhancements Gradient node can export to PDF Annotations (e.g. text labels) are now drawn on the painter at high res, and have been refactored hugely Annotations use thickness zero by default (the Qt \"cosmetic\" thickness) PCT detector node ROI negation and refactoring of operators roiexpr node for composing ROIs using expressions Crude band depth node (needs work) A lot of bug fixes and regression fixes","title":"0.6.0-alpha 2023-09-11 DRIFT STONES"},{"location":"releases/#050-alpha-2023-03-08-carlenno-round","text":"Data quality and bit viewing on canvas Palette and canvas interface with collapsable sections Annotations (ROIs, legends) are now drawn onto the canvas rather than the image Export to PDF, SVG and PNG with those hi-res annotations gradient is much simpler, can overlay onto the image and can draw a legend","title":"0.5.0-alpha 2023-03-08 CARLENNO ROUND"},{"location":"releases/#040-alpha-2022-11-30-caer-bran","text":"Annotation system entirely rewritten PDF/PNG/SVG exporter Gradient legend annotation Doc updates","title":"0.4.0-alpha 2022-11-30 CAER BRAN"},{"location":"releases/#030-alpha-2022-10-27-beacon-hut","text":"Open source! PDS4 importer with proctools Ad-hoc Spectrum viewer in canvas Significant rewrite of expression execution code, permitting custom types to have operations defined on them Direct input method for library use Improved default RGB mapping in canvas Testing Basics testing Testing of the operating principles (see Principles ) Source rules ROI rules rect node can now be edited numerically circle node can add circular ROIs, which can be edited numerically.","title":"0.3.0-alpha 2022-10-27 BEACON HUT"},{"location":"releases/#020-alpha-2022-04-21-anjarden-spring","text":"\"pixel scanning\" on canvases, shows spectrum of pixel when active custom cursor, pixel under cursor highlighted at high zooms text toggle button (currently unused) fixes to example plugin added macos.spec for pyinstaller archive system shows progress when loading each archive element Issue 1 fix (multiple tab closes when main window reinitialised) dynamic type determination for expr output can connect incompatible node outputs to inputs; indicated as red arrows infinite recursion in ROI nodes fix splash screen for Windows/Linux pyinstaller startup (not yet supported on MacOS pyinstaller) custom Datum and connection brush types now easy expr resizing regression fix multiple input buttons after load/resize fix status bar repaints on ui.msg, so it's updated in load and perform context menu on editable text caused a crash (bug in Qt). Workaround. comment boxes","title":"0.2.0-alpha 2022-04-21 ANJARDEN SPRING"},{"location":"releases/#010-alpha-2022-03-02-alsia-well","text":"Initial alpha release outside Aberystwyth","title":"0.1.0-alpha 2022-03-02 ALSIA WELL"},{"location":"roadmap/","text":"Development roadmap This is a rough guide, and things may change! Next major release: 0.8.0 Reorganise the node palette Obtain user stories and feedback Documentation User guide Page on expr nodes documentation for properties of nodes for library use (e.g. expr nodes have \".expr\") How-to for common tasks (yes, still waiting for these) Future releases Calibration: the PCT detection node is fine, but does nothing! Obtain user stories for analysis of HK data (which could potentially get messy, as these are likely to be time series) Consider a vector type It might be useful if functions such as max(), sd() etc. produced a vector of a values rather than a single value in multiband image contexts. For example, a 4-band image with the first channel set to 1 while all others are zero could produce a mean vector of [1,0,0,0]. We would then perform a max() on this vector to get a single value. Preparing for filter aberration and de-hardwiring cameras: Actual values removed from filters.py and put into a config file PANCAM/AUPE camera types no longer hardwired but got from that config Filter aberration parameters added to this config Filter aberration Node (or func??) to convert aberration to image Calculate and process in canvas spectrum Calculate and process in spectrum node","title":"Roadmap"},{"location":"roadmap/#development-roadmap","text":"This is a rough guide, and things may change!","title":"Development roadmap"},{"location":"roadmap/#next-major-release-080","text":"Reorganise the node palette Obtain user stories and feedback Documentation User guide Page on expr nodes documentation for properties of nodes for library use (e.g. expr nodes have \".expr\") How-to for common tasks (yes, still waiting for these)","title":"Next major release: 0.8.0"},{"location":"roadmap/#future-releases","text":"Calibration: the PCT detection node is fine, but does nothing! Obtain user stories for analysis of HK data (which could potentially get messy, as these are likely to be time series) Consider a vector type It might be useful if functions such as max(), sd() etc. produced a vector of a values rather than a single value in multiband image contexts. For example, a 4-band image with the first channel set to 1 while all others are zero could produce a mean vector of [1,0,0,0]. We would then perform a max() on this vector to get a single value. Preparing for filter aberration and de-hardwiring cameras: Actual values removed from filters.py and put into a config file PANCAM/AUPE camera types no longer hardwired but got from that config Filter aberration parameters added to this config Filter aberration Node (or func??) to convert aberration to image Calculate and process in canvas spectrum Calculate and process in spectrum node","title":"Future releases"},{"location":"autodocs/","text":"Autodocs Below are automatically generated documents for certain entities in PCOT. The text in them is extracted from the Python source code, usually from \"docstring\" comments to classes or functions. They are generated by running the generate_autodocs.py script in the mkdocs directory. Nodes Nodes are the entities which make up a PCOT document's graph, taking inputs from various sources and manipulating them in various ways. PCT Patch Detection banddepth circle comment constant contrast stretch croproi crosscalib curve decorr stretch dqmod dummy edge errortest example expr gen gradient histequal histogram importroi in input 0 input 1 input 2 input 3 inset manual register mergetests multidot normimage offset out painted pct pixtest poly rect roidq roiexpr scalartest sink spectrum stitch stringtest striproi tvl1 autoreg Expr functions Below are functions which can be used in the expression evaluation node, expr . name params opt. params (default in brackets) description abs a Calculate absolute value addroi img,r Add an ROI to an image's ROIs clamp img clamp all channels of an image to 0-1 cos a Calculate cosine of an angle in radians crop img,x,y,w,h Crop an image to a rectangle curve img mul (1),add (0) impose a sigmoid curve on an image, y=1/(1+e^-(m(x-0.5)+a))) where m and a are parameters. Note from that equation that x is biased, so that x=0.5 is the inflection point if c=0. dqset img,bits sets DQ bits fliph img Flip an image horizontally flipv img Flip an image vertically grey img opencv (0) Greyscale conversion. If the optional second argument is nonzero, and the image has 3 channels, we'll use CV's conversion equation rather than just the mean. However, this loses uncertainty information. Otherwise uncertainty is calculated by adding together the channels in quadrature and then dividing the number of channels. interp img,factor w (-1) Using trilinear interpolation, generate an image by interpolating between the bands of an existing image. If an ROI is attached, the image generated will be interpolated from the pixels in the ROI. The width of the image will be either given in an optional parameter, or will be the same as the input image. WARNING - IS VERY SLOW makeunc v1,u1,v2,u2 marksat img mn (0),mx (1.0) mark pixels outside a certain range as SAT or ERROR in the DQ bits. Pixels outside any ROI will be ignored, as will any pixels already marked as BAD. max val... find the maximum of the nominal values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. mean val... find the mean\u00b1sd of the values of a set of images and/or scalars. Uncertainties in the data will be pooled. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. merge img1... merge a number of images into multiple bands of a single image. If the image has multiple bands they will all become bands in the new image. min val... find the minimum of the nominal values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. nominal d If input is an image, create an image made up of the nominal (mean) pixel values for all bands - i.e. an image with no uncertainty; if input is numeric, output the nominal value. Ignores ROIs. norm img splitchans (0) normalize all channels of an image to 0-1, operating on all channels combined (the default) or separately resize img,width,height method (linear) Resize an image to a new size using OpenCV's resize function. The method is one of: \"nearest\", \"linear\", \"cubic\", \"area\", \"lanczos4\" mapping to the OpenCV constants cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_LANCZOS rgb img create a 3-channel image consisting of the current RGB mapping of the input image. NOTE: changing the mapping on a node does NOT cause the downstream nodes to run - you will have to click \"Run All\" to make an expr node with rgb() recalculate itself. roi img Extract a single combined ROI from all ROIs on the image. If no ROIs are present, will return a single rectangular ROI covering the entire image. rotate img,angle Rotate an image anti-clockwise by an angle in degrees. The angle must be a multiple of 90 degrees. sd val... find the standard deviation of the values of a set of images and/or scalars. Uncertainties in the data will be pooled. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. setcwl img,cwl Given a 1-band image, create a 'fake' filter with a given centre wavelength and assign it. The transmission of the filter is 1.0, and the fwhm is 30. The image itself is unchanged. This is used in testing only. sin a Calculate sine of an angle in radians sqrt a Calculate square root striproi img Strip all regions of interest from an image sum val... find the sum of the values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. tan a Calculate tangent of an angle in radians testf arg1,arg2 This \"docstring\" is mandatory in datumfuncs, because it contains the description and argument types. The function calculates a+2*b, correctly combining sources and propagating uncertainty. testf2 a b (2) Calculates a+b and a-b, creating a custom object to store that data. testimg index Load a test image uncertainty d If input is an image, create an image made up of uncertainty data for all bands; if input is numeric, output the uncertainty. Ignores ROIs. v n,u dqbits (0) create a new value with uncertainty by combining two values. These can be either numbers or images. \" Ignores and discards ROIs. Expr properties Below are properties which can be used in the expression evaluation node, expr . Properties are names which can be used as identifiers on the right hand side of a \".\" operator, such as a.w to get the width of an image a . name type of x desc x.w img give the width of an image in pixels (if there are ROIs, give the width of the BB of the ROI union) x.w roi give the width of an ROI in pixels x.h img give the height of an image in pixels (if there are ROIs, give the width of the BB of the ROI union) x.h roi give the width of an ROI in pixels x.n img give the area of an image in pixels (if there are ROIs, give the number of pixels in the ROI union) x.n roi give the number of pixels in an ROI","title":"Autodocs"},{"location":"autodocs/#autodocs","text":"Below are automatically generated documents for certain entities in PCOT. The text in them is extracted from the Python source code, usually from \"docstring\" comments to classes or functions. They are generated by running the generate_autodocs.py script in the mkdocs directory.","title":"Autodocs"},{"location":"autodocs/#nodes","text":"Nodes are the entities which make up a PCOT document's graph, taking inputs from various sources and manipulating them in various ways. PCT Patch Detection banddepth circle comment constant contrast stretch croproi crosscalib curve decorr stretch dqmod dummy edge errortest example expr gen gradient histequal histogram importroi in input 0 input 1 input 2 input 3 inset manual register mergetests multidot normimage offset out painted pct pixtest poly rect roidq roiexpr scalartest sink spectrum stitch stringtest striproi tvl1 autoreg","title":"Nodes"},{"location":"autodocs/#expr-functions","text":"Below are functions which can be used in the expression evaluation node, expr . name params opt. params (default in brackets) description abs a Calculate absolute value addroi img,r Add an ROI to an image's ROIs clamp img clamp all channels of an image to 0-1 cos a Calculate cosine of an angle in radians crop img,x,y,w,h Crop an image to a rectangle curve img mul (1),add (0) impose a sigmoid curve on an image, y=1/(1+e^-(m(x-0.5)+a))) where m and a are parameters. Note from that equation that x is biased, so that x=0.5 is the inflection point if c=0. dqset img,bits sets DQ bits fliph img Flip an image horizontally flipv img Flip an image vertically grey img opencv (0) Greyscale conversion. If the optional second argument is nonzero, and the image has 3 channels, we'll use CV's conversion equation rather than just the mean. However, this loses uncertainty information. Otherwise uncertainty is calculated by adding together the channels in quadrature and then dividing the number of channels. interp img,factor w (-1) Using trilinear interpolation, generate an image by interpolating between the bands of an existing image. If an ROI is attached, the image generated will be interpolated from the pixels in the ROI. The width of the image will be either given in an optional parameter, or will be the same as the input image. WARNING - IS VERY SLOW makeunc v1,u1,v2,u2 marksat img mn (0),mx (1.0) mark pixels outside a certain range as SAT or ERROR in the DQ bits. Pixels outside any ROI will be ignored, as will any pixels already marked as BAD. max val... find the maximum of the nominal values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. mean val... find the mean\u00b1sd of the values of a set of images and/or scalars. Uncertainties in the data will be pooled. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. merge img1... merge a number of images into multiple bands of a single image. If the image has multiple bands they will all become bands in the new image. min val... find the minimum of the nominal values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. nominal d If input is an image, create an image made up of the nominal (mean) pixel values for all bands - i.e. an image with no uncertainty; if input is numeric, output the nominal value. Ignores ROIs. norm img splitchans (0) normalize all channels of an image to 0-1, operating on all channels combined (the default) or separately resize img,width,height method (linear) Resize an image to a new size using OpenCV's resize function. The method is one of: \"nearest\", \"linear\", \"cubic\", \"area\", \"lanczos4\" mapping to the OpenCV constants cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_LANCZOS rgb img create a 3-channel image consisting of the current RGB mapping of the input image. NOTE: changing the mapping on a node does NOT cause the downstream nodes to run - you will have to click \"Run All\" to make an expr node with rgb() recalculate itself. roi img Extract a single combined ROI from all ROIs on the image. If no ROIs are present, will return a single rectangular ROI covering the entire image. rotate img,angle Rotate an image anti-clockwise by an angle in degrees. The angle must be a multiple of 90 degrees. sd val... find the standard deviation of the values of a set of images and/or scalars. Uncertainties in the data will be pooled. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. setcwl img,cwl Given a 1-band image, create a 'fake' filter with a given centre wavelength and assign it. The transmission of the filter is 1.0, and the fwhm is 30. The image itself is unchanged. This is used in testing only. sin a Calculate sine of an angle in radians sqrt a Calculate square root striproi img Strip all regions of interest from an image sum val... find the sum of the values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. tan a Calculate tangent of an angle in radians testf arg1,arg2 This \"docstring\" is mandatory in datumfuncs, because it contains the description and argument types. The function calculates a+2*b, correctly combining sources and propagating uncertainty. testf2 a b (2) Calculates a+b and a-b, creating a custom object to store that data. testimg index Load a test image uncertainty d If input is an image, create an image made up of uncertainty data for all bands; if input is numeric, output the uncertainty. Ignores ROIs. v n,u dqbits (0) create a new value with uncertainty by combining two values. These can be either numbers or images. \" Ignores and discards ROIs.","title":"Expr functions"},{"location":"autodocs/#expr-properties","text":"Below are properties which can be used in the expression evaluation node, expr . Properties are names which can be used as identifiers on the right hand side of a \".\" operator, such as a.w to get the width of an image a . name type of x desc x.w img give the width of an image in pixels (if there are ROIs, give the width of the BB of the ROI union) x.w roi give the width of an ROI in pixels x.h img give the height of an image in pixels (if there are ROIs, give the width of the BB of the ROI union) x.h roi give the width of an ROI in pixels x.n img give the area of an image in pixels (if there are ROIs, give the number of pixels in the ROI union) x.n roi give the number of pixels in an ROI","title":"Expr properties"},{"location":"autodocs/PCT_Patch_Detection/","text":"PCT Patch Detection Description A Node that takes in an image holding the ExoMars Rover PCT and outputs the centre coordinates in the image of each of the PCT patches. Connections Inputs Index Name Type Desc 0 img img (none) Outputs Index Name Type Desc 0 img+rois img (none)","title":"PCT Patch Detection"},{"location":"autodocs/PCT_Patch_Detection/#pct-patch-detection","text":"","title":"PCT Patch Detection"},{"location":"autodocs/PCT_Patch_Detection/#description","text":"A Node that takes in an image holding the ExoMars Rover PCT and outputs the centre coordinates in the image of each of the PCT patches.","title":"Description"},{"location":"autodocs/PCT_Patch_Detection/#connections","text":"","title":"Connections"},{"location":"autodocs/PCT_Patch_Detection/#inputs","text":"Index Name Type Desc 0 img img (none)","title":"Inputs"},{"location":"autodocs/PCT_Patch_Detection/#outputs","text":"Index Name Type Desc 0 img+rois img (none)","title":"Outputs"},{"location":"autodocs/banddepth/","text":"banddepth Description Calculate band depth using a linear weighted mean of the two bands either side. Reference: \"Revised CRISM spectral parameters... \" Viviano, Seelos et al. 2015. Issues: Ignores FWHM (bandwidth) of all bands. can't do weird stuff like Figs. 7c and 7d in the Viviano et al. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"banddepth"},{"location":"autodocs/banddepth/#banddepth","text":"","title":"banddepth"},{"location":"autodocs/banddepth/#description","text":"Calculate band depth using a linear weighted mean of the two bands either side. Reference: \"Revised CRISM spectral parameters... \" Viviano, Seelos et al. 2015. Issues: Ignores FWHM (bandwidth) of all bands. can't do weird stuff like Figs. 7c and 7d in the Viviano et al.","title":"Description"},{"location":"autodocs/banddepth/#connections","text":"","title":"Connections"},{"location":"autodocs/banddepth/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/banddepth/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/circle/","text":"circle Description Add a circular ROI to an image (see multidot for multiple circles). Can edit numerically. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. Connections Inputs Index Name Type Desc 0 input img (none) Outputs Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"circle"},{"location":"autodocs/circle/#circle","text":"","title":"circle"},{"location":"autodocs/circle/#description","text":"Add a circular ROI to an image (see multidot for multiple circles). Can edit numerically. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected.","title":"Description"},{"location":"autodocs/circle/#connections","text":"","title":"Connections"},{"location":"autodocs/circle/#inputs","text":"Index Name Type Desc 0 input img (none)","title":"Inputs"},{"location":"autodocs/circle/#outputs","text":"Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"Outputs"},{"location":"autodocs/comment/","text":"comment Description Comment box Connections","title":"comment"},{"location":"autodocs/comment/#comment","text":"","title":"comment"},{"location":"autodocs/comment/#description","text":"Comment box","title":"Description"},{"location":"autodocs/comment/#connections","text":"","title":"Connections"},{"location":"autodocs/constant/","text":"constant Description Generates a numeric value which can be typed directly into the node's box in the graph Connections Outputs Index Name Type Desc 0 (none) number (none)","title":"constant"},{"location":"autodocs/constant/#constant","text":"","title":"constant"},{"location":"autodocs/constant/#description","text":"Generates a numeric value which can be typed directly into the node's box in the graph","title":"Description"},{"location":"autodocs/constant/#connections","text":"","title":"Connections"},{"location":"autodocs/constant/#outputs","text":"Index Name Type Desc 0 (none) number (none)","title":"Outputs"},{"location":"autodocs/contrast_stretch/","text":"contrast stretch Description Perform a simple contrast stretch separately on each channel. The stretch is linear around the midpoint and excessive values are clamped. The knob controls the amount of stretch applied. Uncertainty is discarded. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"contrast stretch"},{"location":"autodocs/contrast_stretch/#contrast-stretch","text":"","title":"contrast stretch"},{"location":"autodocs/contrast_stretch/#description","text":"Perform a simple contrast stretch separately on each channel. The stretch is linear around the midpoint and excessive values are clamped. The knob controls the amount of stretch applied. Uncertainty is discarded.","title":"Description"},{"location":"autodocs/contrast_stretch/#connections","text":"","title":"Connections"},{"location":"autodocs/contrast_stretch/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/contrast_stretch/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/croproi/","text":"croproi Description Crops an image to a rectangle which is the union of its regions of interest. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"croproi"},{"location":"autodocs/croproi/#croproi","text":"","title":"croproi"},{"location":"autodocs/croproi/#description","text":"Crops an image to a rectangle which is the union of its regions of interest.","title":"Description"},{"location":"autodocs/croproi/#connections","text":"","title":"Connections"},{"location":"autodocs/croproi/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/croproi/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/crosscalib/","text":"crosscalib Description \"Cross-calibrate\" two images: given points S in a source image and corresponding points D in a destination image, find a vector of factors v for the bands such that S=vD, and transform S accordingly. Essentially, and crudely speaking, make the colours in S match those in D by sampling the same points in each. Bad pixels in the parent image will be ignored for getting the colours, and the new image will have the DQ bits from S. Uncertainty is not propagated through this node. DQ is propagated from the source image. Connections Inputs Index Name Type Desc 0 source img (none) 1 dest img (none) Outputs Index Name Type Desc 0 out img (none)","title":"crosscalib"},{"location":"autodocs/crosscalib/#crosscalib","text":"","title":"crosscalib"},{"location":"autodocs/crosscalib/#description","text":"\"Cross-calibrate\" two images: given points S in a source image and corresponding points D in a destination image, find a vector of factors v for the bands such that S=vD, and transform S accordingly. Essentially, and crudely speaking, make the colours in S match those in D by sampling the same points in each. Bad pixels in the parent image will be ignored for getting the colours, and the new image will have the DQ bits from S. Uncertainty is not propagated through this node. DQ is propagated from the source image.","title":"Description"},{"location":"autodocs/crosscalib/#connections","text":"","title":"Connections"},{"location":"autodocs/crosscalib/#inputs","text":"Index Name Type Desc 0 source img (none) 1 dest img (none)","title":"Inputs"},{"location":"autodocs/crosscalib/#outputs","text":"Index Name Type Desc 0 out img (none)","title":"Outputs"},{"location":"autodocs/curve/","text":"curve Description Maps the image channel intensities to a logistic sigmoid curve, y=1/(1+e^-(ax+b)), where a is \"mul\" and b is \"add\". Honours regions of interest. Ignores DQ and uncertainty Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"curve"},{"location":"autodocs/curve/#curve","text":"","title":"curve"},{"location":"autodocs/curve/#description","text":"Maps the image channel intensities to a logistic sigmoid curve, y=1/(1+e^-(ax+b)), where a is \"mul\" and b is \"add\". Honours regions of interest. Ignores DQ and uncertainty","title":"Description"},{"location":"autodocs/curve/#connections","text":"","title":"Connections"},{"location":"autodocs/curve/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/curve/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/decorr_stretch/","text":"decorr stretch Description Perform a decorrelation stretch on an RGB image Ignores DQ and uncertainty Connections Inputs Index Name Type Desc 0 rgb img (none) Outputs Index Name Type Desc 0 rgb img (none)","title":"decorr stretch"},{"location":"autodocs/decorr_stretch/#decorr-stretch","text":"","title":"decorr stretch"},{"location":"autodocs/decorr_stretch/#description","text":"Perform a decorrelation stretch on an RGB image Ignores DQ and uncertainty","title":"Description"},{"location":"autodocs/decorr_stretch/#connections","text":"","title":"Connections"},{"location":"autodocs/decorr_stretch/#inputs","text":"Index Name Type Desc 0 rgb img (none)","title":"Inputs"},{"location":"autodocs/decorr_stretch/#outputs","text":"Index Name Type Desc 0 rgb img (none)","title":"Outputs"},{"location":"autodocs/dqmod/","text":"dqmod Description Modify DQ bits based on conditions in the existing nominal or uncertainty for all bands or just a single band. **WARNING**: This may set \"bad\" bits which will be masked in any calculation. For some settings, these bad bits can mask bands other than those from which they are derived. Calculations involving pixels with these bits will be partially derived from the mask, but this information will not be tracked by the source mechanism. This means that some source tracking information can be lost. (Issue #69) Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"dqmod"},{"location":"autodocs/dqmod/#dqmod","text":"","title":"dqmod"},{"location":"autodocs/dqmod/#description","text":"Modify DQ bits based on conditions in the existing nominal or uncertainty for all bands or just a single band. **WARNING**: This may set \"bad\" bits which will be masked in any calculation. For some settings, these bad bits can mask bands other than those from which they are derived. Calculations involving pixels with these bits will be partially derived from the mask, but this information will not be tracked by the source mechanism. This means that some source tracking information can be lost. (Issue #69)","title":"Description"},{"location":"autodocs/dqmod/#connections","text":"","title":"Connections"},{"location":"autodocs/dqmod/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/dqmod/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/dummy/","text":"dummy Description A dummy node type used when the node type specified in a loaded file cannot be found - perhaps it is from an older PCOT version and is now deprecated, or it's part of a plugin? Connections","title":"dummy"},{"location":"autodocs/dummy/#dummy","text":"","title":"dummy"},{"location":"autodocs/dummy/#description","text":"A dummy node type used when the node type specified in a loaded file cannot be found - perhaps it is from an older PCOT version and is now deprecated, or it's part of a plugin?","title":"Description"},{"location":"autodocs/dummy/#connections","text":"","title":"Connections"},{"location":"autodocs/edge/","text":"edge Description This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab. DQ bits of the bands in the source are combined together for the single band of the result. Uncertainty is discarded. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"edge"},{"location":"autodocs/edge/#edge","text":"","title":"edge"},{"location":"autodocs/edge/#description","text":"This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab. DQ bits of the bands in the source are combined together for the single band of the result. Uncertainty is discarded.","title":"Description"},{"location":"autodocs/edge/#connections","text":"","title":"Connections"},{"location":"autodocs/edge/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/edge/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/errortest/","text":"errortest Description Check that a node produces an error. This node will run after all other nodes, but before its children. It checks that the string is the error code (e.g. 'DATA') Connections Inputs Index Name Type Desc 0 (none) any (none) Outputs Index Name Type Desc 0 (none) testresult (none)","title":"errortest"},{"location":"autodocs/errortest/#errortest","text":"","title":"errortest"},{"location":"autodocs/errortest/#description","text":"Check that a node produces an error. This node will run after all other nodes, but before its children. It checks that the string is the error code (e.g. 'DATA')","title":"Description"},{"location":"autodocs/errortest/#connections","text":"","title":"Connections"},{"location":"autodocs/errortest/#inputs","text":"Index Name Type Desc 0 (none) any (none)","title":"Inputs"},{"location":"autodocs/errortest/#outputs","text":"Index Name Type Desc 0 (none) testresult (none)","title":"Outputs"},{"location":"autodocs/example/","text":"example Description This object is not a node, but the singleton to which nodes of this type point to determine their behaviour. This docstring will form the help text for the node in the UI. Markdown is permitted and processed into HTML. Look at (say) XFormGradient for an example of how to write this. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"example"},{"location":"autodocs/example/#example","text":"","title":"example"},{"location":"autodocs/example/#description","text":"This object is not a node, but the singleton to which nodes of this type point to determine their behaviour. This docstring will form the help text for the node in the UI. Markdown is permitted and processed into HTML. Look at (say) XFormGradient for an example of how to write this.","title":"Description"},{"location":"autodocs/example/#connections","text":"","title":"Connections"},{"location":"autodocs/example/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/example/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/expr/","text":"expr Description Expression evaluator. The node box will show the text of the expression. The \"run\" button must be clicked to set the node to the new expression and perform it. The input can accept any type of data and the output type is determined when the node is run. The four inputs are assigned to the variables a, b, c, and d. They are typically (but not necessarily) images or scalar values. The standard operators +,/,*,- and ^ all have their usual meanings. When applied to images they work in a pixel-wise fashion, so if a is an image, 2*a will double the brightness. If b is also an image, a+b will add the two images, pixel by pixel. There are two non-standard operators: . for properties and $ for band extraction. These are described below. Image/numeric operators: operator description precedence (higher binds tighter) A + B add A to B (can act on ROIs) 10 A - B subtract A from B (can act on ROIs) 10 A / B divide A by B (can act on ROIs) 20 A * B multiply A by B (can act on ROIs) 20 A ^ B exponentiate A to the power B (can act on ROIs) 30 -A element-wise negation of A (can act on ROIs) 50 A.B property B of entity A (e.g. a.h is height of image a) 80 A$546 extract single band image of wavelength 546 100 A$_2 extract single band image from band 2 explicitly 100 A&B element-wise minimum of A and B (Zadeh's AND operator) 20 A|B element-wise maximum of A and B (Zadeh's OR operator) 20 !A element-wise 1-A (Zadeh's NOT operator) 50 All operators can act on images and scalars (numeric values), with the exception of . and $ which have images on the left-hand side and identifiers or integers on the right-hand side. Those operators marked with (can act on ROIs) can also act on pairs of ROIs (regions of interest, see below). Binary operations on image pairs These act by performing the binary operation on the two underlying Numpy arrays. This means you may need to be careful about the ordering of the bands in the two images, because they will simply be operated on in the order they appear. For example, consider adding two images $a$ and $b$ with the same bands in a slightly different order: image a image b result of addition 480nm 480nm sum of 480nm bands 500nm 500nm sum of 500nm bands 610nm 670nm a 's 610nm band plus b 's 670nm band 670nm 610nm copy of previous band (addition being commutative) This probably isn't what you wanted. Note that this is obviously not an issue when an operation is being performed on bands in a single image. binary operators on images with regions of interest If one of the two images has an ROI, the operation is only performed on that ROI; the remaining area of output is taken from the image without an ROI. If both images have an ROI an error will result - it is likely that this is a mistake on the user's part, and doing something more \"intelligent\" might conceal this. The desired result can be achieved using expr nodes on ROIs and an importroi node. Operators on ROIs themselves (as opposed to images with ROIs) operator description a+b union a*b intersection a-b difference You can source ROIs from the \"roi\" output of ROI nodes, and impose resulting ROIs on images with \"importroi\" node. Band extraction The notation $name or $wavelength takes an image on the left-hand side and extracts a single band, generating a new monochrome image. The right-hand side is either a filter name, a filter position, a wavelength or a band index preceded by \"_\". Depending on the camera, all these could be valid: expression meaning a$780 the 780nm band in image a a$_2 band 2 in the image a (a+b)$G0 the band named G0 in the image formed by adding images a and b ((a+b)/2)$780 the average of the 780nm bands of images a and b Be aware of caveats in the \"binary operations on image pairs\" section above: it may be better to extract the band before performing the operation, thus: old expression better expression (a+b)$G0 a$G0 + b$G0 ((a+b)/2)$780 (a$780+b$780)/2 Properties Properties are indicated by the . operator, e.g. a.w to find an image's width. Help on functions and properties A list of functions can be obtained by right-clicking on either the log pane or function entry pane and selecting \"List all functions.\" Help on an individual function can be found by hovering over the name of a function, right-clicking and selecting \"Get help on 'somefunction'\". Similar actions are supported for properties. Uncertainties are assumed to be independent in all binary operations While uncertainty is propagated through operations (as standard deviation) all quantities are assumed to be independent (calculating covariances is beyond the scope of this system). Be very careful here. For example, the uncertainty for the expression tan(a) will be calculated correctly, but if you try to use sin(a)/cos(a) the uncertainty will be incorrect because the nominator and denominator are not independent. Connections Inputs Index Name Type Desc 0 a any (none) 1 b any (none) 2 c any (none) 3 d any (none) Outputs Index Name Type Desc 0 (none) none (none)","title":"expr"},{"location":"autodocs/expr/#expr","text":"","title":"expr"},{"location":"autodocs/expr/#description","text":"Expression evaluator. The node box will show the text of the expression. The \"run\" button must be clicked to set the node to the new expression and perform it. The input can accept any type of data and the output type is determined when the node is run. The four inputs are assigned to the variables a, b, c, and d. They are typically (but not necessarily) images or scalar values. The standard operators +,/,*,- and ^ all have their usual meanings. When applied to images they work in a pixel-wise fashion, so if a is an image, 2*a will double the brightness. If b is also an image, a+b will add the two images, pixel by pixel. There are two non-standard operators: . for properties and $ for band extraction. These are described below.","title":"Description"},{"location":"autodocs/expr/#connections","text":"","title":"Connections"},{"location":"autodocs/expr/#inputs","text":"Index Name Type Desc 0 a any (none) 1 b any (none) 2 c any (none) 3 d any (none)","title":"Inputs"},{"location":"autodocs/expr/#outputs","text":"Index Name Type Desc 0 (none) none (none)","title":"Outputs"},{"location":"autodocs/gen/","text":"gen Description Generate an image with given channel values. Can also generate patterns. Each band is given a nominal value and uncertainty, along with a centre frequency and a mode (for patterns). Modes are: flat : N and U are used to fill the entire band ripple-n: the N value is not a value, but a multiplier applied to distance from centre - the sine of this gives the value. The U value is generated as in 'flat' ripple-u: as ripple-n, but this time U is used as a multiplier to generate the ripple pattern in uncertainty, while N is generated as in 'flat' ripple-un: both values are ripple multipliers. half: nominal is N on the left, U on the right. Uncertainty is 0.1. (Test value) checkx: nominal is a checquered pattern with each square of size N, offset by U in the x-axis. uncertainty=nominal. checky: nominal is a checquered pattern with each square of size N, offset by U in the y-axis. uncertainty=nominal. rand: both nom. and unc. are filled with non-negative pseudorandom uniform noise multiplied by N and U respectively gaussian: nom. is filled with gaussian noise centered around N with a std. dev. of U. U is zero. The RNG is seeded from the CWL. gradient-x: nom. is filled with a gradient from 0-1, U is zero. Number of steps is N (zero means smooth). gradient-y: nom. is filled with a gradient from 0-1, U is zero. Number of steps is N (zero means smooth). A useful pattern might be something like this: Chan 0: checkx, N=8, U=0 Chan 1: checkx, N=8, U=4 Chan 2: checky, N=8, U=4 To get variation in uncertainty, create a similar pattern but with a longer period using another gen node: Chan 0: checkx, N=16, U=0 Chan 1: checkx, N=16, U=8 Chan 2: checky, N=16, U=8 and merge the two together, using the first gen to create nominal values and the second to create uncertainty values, with an expr node using the expression v(a,b) . Connections Outputs Index Name Type Desc 0 (none) img (none)","title":"gen"},{"location":"autodocs/gen/#gen","text":"","title":"gen"},{"location":"autodocs/gen/#description","text":"Generate an image with given channel values. Can also generate patterns. Each band is given a nominal value and uncertainty, along with a centre frequency and a mode (for patterns). Modes are: flat : N and U are used to fill the entire band ripple-n: the N value is not a value, but a multiplier applied to distance from centre - the sine of this gives the value. The U value is generated as in 'flat' ripple-u: as ripple-n, but this time U is used as a multiplier to generate the ripple pattern in uncertainty, while N is generated as in 'flat' ripple-un: both values are ripple multipliers. half: nominal is N on the left, U on the right. Uncertainty is 0.1. (Test value) checkx: nominal is a checquered pattern with each square of size N, offset by U in the x-axis. uncertainty=nominal. checky: nominal is a checquered pattern with each square of size N, offset by U in the y-axis. uncertainty=nominal. rand: both nom. and unc. are filled with non-negative pseudorandom uniform noise multiplied by N and U respectively gaussian: nom. is filled with gaussian noise centered around N with a std. dev. of U. U is zero. The RNG is seeded from the CWL. gradient-x: nom. is filled with a gradient from 0-1, U is zero. Number of steps is N (zero means smooth). gradient-y: nom. is filled with a gradient from 0-1, U is zero. Number of steps is N (zero means smooth). A useful pattern might be something like this: Chan 0: checkx, N=8, U=0 Chan 1: checkx, N=8, U=4 Chan 2: checky, N=8, U=4 To get variation in uncertainty, create a similar pattern but with a longer period using another gen node: Chan 0: checkx, N=16, U=0 Chan 1: checkx, N=16, U=8 Chan 2: checky, N=16, U=8 and merge the two together, using the first gen to create nominal values and the second to create uncertainty values, with an expr node using the expression v(a,b) .","title":"Description"},{"location":"autodocs/gen/#connections","text":"","title":"Connections"},{"location":"autodocs/gen/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/gradient/","text":"gradient Description Convert a mono image to an RGB gradient image for better visibility. If the \"insetinto\" input has an image AND there is a valid ROI in the mono image, the image will be inset into the RGB of the insetinto image. NOTE: if you change the \"insetinto\" image's RGB mapping you may need to \"run all\" to see the the change reflected. Ignores DQ and uncertainty The gradient widget has the following behaviour: click and drag to move a colour point doubleclick to delete an existing colour point doubleclick to add a new colour point right click to edit an existing colour point Node parameters: gradient: utils.Gradient object containing gradient info colour: (r,g,b) [0:1] colour of text and border for in-image legend legendrect: (x,y,w,h) rectangle for in-image legend vertical: true if vertical legend fontscale: size of font thickness: border thickness legendPos: string describing position: 'In image', 'Top margin', 'Bottom margin', 'Left margin', 'Right margin', 'None' These are also defined as constants LEFT_MARGIN... IN_IMAGE (and None) Connections Inputs Index Name Type Desc 0 mono img (none) 1 background img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"gradient"},{"location":"autodocs/gradient/#gradient","text":"","title":"gradient"},{"location":"autodocs/gradient/#description","text":"Convert a mono image to an RGB gradient image for better visibility. If the \"insetinto\" input has an image AND there is a valid ROI in the mono image, the image will be inset into the RGB of the insetinto image. NOTE: if you change the \"insetinto\" image's RGB mapping you may need to \"run all\" to see the the change reflected. Ignores DQ and uncertainty The gradient widget has the following behaviour: click and drag to move a colour point doubleclick to delete an existing colour point doubleclick to add a new colour point right click to edit an existing colour point Node parameters: gradient: utils.Gradient object containing gradient info colour: (r,g,b) [0:1] colour of text and border for in-image legend legendrect: (x,y,w,h) rectangle for in-image legend vertical: true if vertical legend fontscale: size of font thickness: border thickness legendPos: string describing position: 'In image', 'Top margin', 'Bottom margin', 'Left margin', 'Right margin', 'None' These are also defined as constants LEFT_MARGIN... IN_IMAGE (and None)","title":"Description"},{"location":"autodocs/gradient/#connections","text":"","title":"Connections"},{"location":"autodocs/gradient/#inputs","text":"Index Name Type Desc 0 mono img (none) 1 background img (none)","title":"Inputs"},{"location":"autodocs/gradient/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/histequal/","text":"histequal Description Perform histogram equalisation on all channels of the image separately. Honours ROIs. Currently set to 2000 bins, but I may add a control for that. Ignores DQ bits and uncertainty Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"histequal"},{"location":"autodocs/histequal/#histequal","text":"","title":"histequal"},{"location":"autodocs/histequal/#description","text":"Perform histogram equalisation on all channels of the image separately. Honours ROIs. Currently set to 2000 bins, but I may add a control for that. Ignores DQ bits and uncertainty","title":"Description"},{"location":"autodocs/histequal/#connections","text":"","title":"Connections"},{"location":"autodocs/histequal/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/histequal/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/histogram/","text":"histogram Description Produce a histogram of intensities for each channel in the data - will be very messy if used on a multispectral image. Will only be performed on ROIs if there are active ROIs. BAD pixels in bands will be discounted. The output carries a table - columns are frequencies, rows are bands. Uncertainty is ignored. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 data data a CSV output (use 'dump' or 'sink' to read it)","title":"histogram"},{"location":"autodocs/histogram/#histogram","text":"","title":"histogram"},{"location":"autodocs/histogram/#description","text":"Produce a histogram of intensities for each channel in the data - will be very messy if used on a multispectral image. Will only be performed on ROIs if there are active ROIs. BAD pixels in bands will be discounted. The output carries a table - columns are frequencies, rows are bands. Uncertainty is ignored.","title":"Description"},{"location":"autodocs/histogram/#connections","text":"","title":"Connections"},{"location":"autodocs/histogram/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/histogram/#outputs","text":"Index Name Type Desc 0 data data a CSV output (use 'dump' or 'sink' to read it)","title":"Outputs"},{"location":"autodocs/importroi/","text":"importroi Description Import a ROI into an image which was originally set on another image. The 'roi' input takes either an ROI or an image. If the former, that ROI is imposed on the image passed into the main input. If the latter, all the ROIs from the 'roi' input image are imposed on the image input image. Connections Inputs Index Name Type Desc 0 (none) img (none) 1 roi any (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"importroi"},{"location":"autodocs/importroi/#importroi","text":"","title":"importroi"},{"location":"autodocs/importroi/#description","text":"Import a ROI into an image which was originally set on another image. The 'roi' input takes either an ROI or an image. If the former, that ROI is imposed on the image passed into the main input. If the latter, all the ROIs from the 'roi' input image are imposed on the image input image.","title":"Description"},{"location":"autodocs/importroi/#connections","text":"","title":"Connections"},{"location":"autodocs/importroi/#inputs","text":"Index Name Type Desc 0 (none) img (none) 1 roi any (none)","title":"Inputs"},{"location":"autodocs/importroi/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/in/","text":"in Description The macro input connector (used inside macro prototypes) Connections Outputs Index Name Type Desc 0 (none) variant (none)","title":"in"},{"location":"autodocs/in/#in","text":"","title":"in"},{"location":"autodocs/in/#description","text":"The macro input connector (used inside macro prototypes)","title":"Description"},{"location":"autodocs/in/#connections","text":"","title":"Connections"},{"location":"autodocs/in/#outputs","text":"Index Name Type Desc 0 (none) variant (none)","title":"Outputs"},{"location":"autodocs/input_0/","text":"input 0 Description Imports Input 0's data into the graph Connections Outputs Index Name Type Desc 0 (none) none (none)","title":"input 0"},{"location":"autodocs/input_0/#input-0","text":"","title":"input 0"},{"location":"autodocs/input_0/#description","text":"Imports Input 0's data into the graph","title":"Description"},{"location":"autodocs/input_0/#connections","text":"","title":"Connections"},{"location":"autodocs/input_0/#outputs","text":"Index Name Type Desc 0 (none) none (none)","title":"Outputs"},{"location":"autodocs/input_1/","text":"input 1 Description Imports Input 1's data into the graph Connections Outputs Index Name Type Desc 0 (none) none (none)","title":"input 1"},{"location":"autodocs/input_1/#input-1","text":"","title":"input 1"},{"location":"autodocs/input_1/#description","text":"Imports Input 1's data into the graph","title":"Description"},{"location":"autodocs/input_1/#connections","text":"","title":"Connections"},{"location":"autodocs/input_1/#outputs","text":"Index Name Type Desc 0 (none) none (none)","title":"Outputs"},{"location":"autodocs/input_2/","text":"input 2 Description Imports Input 2's data into the graph Connections Outputs Index Name Type Desc 0 (none) none (none)","title":"input 2"},{"location":"autodocs/input_2/#input-2","text":"","title":"input 2"},{"location":"autodocs/input_2/#description","text":"Imports Input 2's data into the graph","title":"Description"},{"location":"autodocs/input_2/#connections","text":"","title":"Connections"},{"location":"autodocs/input_2/#outputs","text":"Index Name Type Desc 0 (none) none (none)","title":"Outputs"},{"location":"autodocs/input_3/","text":"input 3 Description Imports Input 3's data into the graph Connections Outputs Index Name Type Desc 0 (none) none (none)","title":"input 3"},{"location":"autodocs/input_3/#input-3","text":"","title":"input 3"},{"location":"autodocs/input_3/#description","text":"Imports Input 3's data into the graph","title":"Description"},{"location":"autodocs/input_3/#connections","text":"","title":"Connections"},{"location":"autodocs/input_3/#outputs","text":"Index Name Type Desc 0 (none) none (none)","title":"Outputs"},{"location":"autodocs/inset/","text":"inset Description Inset an image inside another. Uses RGB versions of both images, as defined by the RGB mapping set in the previous nodes. Does not honour regions of interest. Note that there is no RGB mapping in the canvas for the tab - RGB mappings should be set in the input nodes. Ignores DQ and uncertainty Connections Inputs Index Name Type Desc 0 img img (none) 1 inset img (none) 2 roi roi (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"inset"},{"location":"autodocs/inset/#inset","text":"","title":"inset"},{"location":"autodocs/inset/#description","text":"Inset an image inside another. Uses RGB versions of both images, as defined by the RGB mapping set in the previous nodes. Does not honour regions of interest. Note that there is no RGB mapping in the canvas for the tab - RGB mappings should be set in the input nodes. Ignores DQ and uncertainty","title":"Description"},{"location":"autodocs/inset/#connections","text":"","title":"Connections"},{"location":"autodocs/inset/#inputs","text":"Index Name Type Desc 0 img img (none) 1 inset img (none) 2 roi roi (none)","title":"Inputs"},{"location":"autodocs/inset/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/manual_register/","text":"manual register Description Perform manual registration of two images. The output is a version of the 'moving' image with a projective transform applied to map points onto corresponding points in the 'fixed' image. The canvas view can show the moving input (also referred to as the \"source\"), the fixed image (also referred to as the \"destination\"), a blend of the two, or the result. All images are shown as greyscale (since the fixed and moving images will likely have different frequency bands). The transform will map a set of points in the moving image onto a set in the fixed image. Both sets of points can be changed, or a single set. Points are mapped onto the correspondingly numbered point. In \"translate\" mode only a single point is required (and only a single point will be shown from each set). Points are added to the source (moving) image by clicking with shift. Points are adding to the dest (fixed) image by clicking with ctrl. Note that this node does not currently display DQ or uncertainty data in its canvas If only the source or dest points are shown, either shift- or ctrl-clicking will add to the appropriate point set. The selected point can be deleted with the Delete key (but this will modify the numbering!) A point can be selected and dragged by clicking on it. This may be slow because the warping operation will take place every update; disabling 'auto-run on change' is a good idea! Uncertainty is warped along with the original image, as is DQ using nearest-neighbour ( which may not be sufficient ). Connections Inputs Index Name Type Desc 0 moving img (none) 1 fixed img (none) Outputs Index Name Type Desc 0 moved img (none)","title":"manual register"},{"location":"autodocs/manual_register/#manual-register","text":"","title":"manual register"},{"location":"autodocs/manual_register/#description","text":"Perform manual registration of two images. The output is a version of the 'moving' image with a projective transform applied to map points onto corresponding points in the 'fixed' image. The canvas view can show the moving input (also referred to as the \"source\"), the fixed image (also referred to as the \"destination\"), a blend of the two, or the result. All images are shown as greyscale (since the fixed and moving images will likely have different frequency bands). The transform will map a set of points in the moving image onto a set in the fixed image. Both sets of points can be changed, or a single set. Points are mapped onto the correspondingly numbered point. In \"translate\" mode only a single point is required (and only a single point will be shown from each set). Points are added to the source (moving) image by clicking with shift. Points are adding to the dest (fixed) image by clicking with ctrl. Note that this node does not currently display DQ or uncertainty data in its canvas If only the source or dest points are shown, either shift- or ctrl-clicking will add to the appropriate point set. The selected point can be deleted with the Delete key (but this will modify the numbering!) A point can be selected and dragged by clicking on it. This may be slow because the warping operation will take place every update; disabling 'auto-run on change' is a good idea! Uncertainty is warped along with the original image, as is DQ using nearest-neighbour ( which may not be sufficient ).","title":"Description"},{"location":"autodocs/manual_register/#connections","text":"","title":"Connections"},{"location":"autodocs/manual_register/#inputs","text":"Index Name Type Desc 0 moving img (none) 1 fixed img (none)","title":"Inputs"},{"location":"autodocs/manual_register/#outputs","text":"Index Name Type Desc 0 moved img (none)","title":"Outputs"},{"location":"autodocs/mergetests/","text":"mergetests Description Merge the results of many tests into a single list of failures. Test results are always lists of test failures, this simply concatenates those lists. Connections Inputs Index Name Type Desc 0 (none) testresult (none) 1 (none) testresult (none) 2 (none) testresult (none) 3 (none) testresult (none) 4 (none) testresult (none) 5 (none) testresult (none) 6 (none) testresult (none) 7 (none) testresult (none) Outputs Index Name Type Desc 0 results testresult (none)","title":"mergetests"},{"location":"autodocs/mergetests/#mergetests","text":"","title":"mergetests"},{"location":"autodocs/mergetests/#description","text":"Merge the results of many tests into a single list of failures. Test results are always lists of test failures, this simply concatenates those lists.","title":"Description"},{"location":"autodocs/mergetests/#connections","text":"","title":"Connections"},{"location":"autodocs/mergetests/#inputs","text":"Index Name Type Desc 0 (none) testresult (none) 1 (none) testresult (none) 2 (none) testresult (none) 3 (none) testresult (none) 4 (none) testresult (none) 5 (none) testresult (none) 6 (none) testresult (none) 7 (none) testresult (none)","title":"Inputs"},{"location":"autodocs/mergetests/#outputs","text":"Index Name Type Desc 0 results testresult (none)","title":"Outputs"},{"location":"autodocs/multidot/","text":"multidot Description Add multiple small ROIs which are either circular or painted. Painted modes can be created and edited with a circular brush or a flood fill. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROIs on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. This can also \"capture\" ROIs from the incoming image, so that they can be edited. This copies the ROIs from the image into the node, and suppresses the image's original ROIs. In addition to this, the \"convert circles\" button will convert all circular ROIs in the node into painted ROIs. Quick guide: To add and edit circular ROIs: select \"Circles\" on the left-hand side set the dot size to the desired radius shift-click to add and select a new ROI click to select an existing ROI (or deselect) drag to move the centre of the circle edit parameters like dot size, name, colour, etc. to change the current ROI or next created ROI To add and edit painted ROIs: select \"Painted\" on the left-hand side set the dot size to the desired radius set add/create mode to Brush shift-click to add and select a new painted ROI click inside an ROI to select it ctrl-click to add a circle to a selected painted ROI alt-click to \"unpaint\" a circle from a selected painted ROI To add and edit filled ROIs: select \"Painted\" on the left-hand side set add/create mode to Fill set tolerance to a low number (e.g. 0.1) shift-click to add and select a new filled ROI possibly undo (ctrl-Z) to remove the last fill, then change the tolerance! click inside an ROI to select it ctrl-click to add more flood fill to a selected ROI set add/create mode to \"Brush\" to paint circular brushstrokes on a ROI alt-click to \"unpaint\" a circle from a selected ROI General controls: Circles or Painted selects the type of new ROIs click inside an ROI (or very near a circle) to show and edit its properties Dot size is the size of the circle used for both creating circle ROIs and for circular painting in Painted mode. Scale is the font size for all annotations created by this node Thickness is the border size for (currently) all circles only Colour is the colour of the current ROI's annotation Recolour all will select random colours for all ROIs Name is the name of the current ROI Background is whether a background rectangle is used to make the name clearer for all ROIs Capture captures the ROIs from the incoming image, and suppresses the image's original ROIs Convert circles will convert all circular ROIs in the node into painted ROIs. tolerance is the colour difference between the current pixel and surrounding pixels required to stop flood filling. PICK CAREFULLY - it may need to be very small. add/create mode is whether we are new ROIs are created with a circular brush or flood fill in Painted mode Circle mode: shift-click to add a new ROI drag to move centre of circle Painted mode: shift-click to add a new painted ROI. Will use a circle if \"Paint Mode\" is circle, or a flood fill with the given tolerance if the mode is \"Fill\". ctrl-click to add a circle or flood fill to a selected painted ROI, provided we are in the same mode as the selected ROI. Circle or fill depends on Paint Mode. alt-click to \"unpaint\" a circle from a selected painted ROI (Internal: Note that this type doesn't inherit from XFormROI.) Connections Inputs Index Name Type Desc 0 input img (none) Outputs Index Name Type Desc 0 img img image with ROIs","title":"multidot"},{"location":"autodocs/multidot/#multidot","text":"","title":"multidot"},{"location":"autodocs/multidot/#description","text":"Add multiple small ROIs which are either circular or painted. Painted modes can be created and edited with a circular brush or a flood fill. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROIs on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. This can also \"capture\" ROIs from the incoming image, so that they can be edited. This copies the ROIs from the image into the node, and suppresses the image's original ROIs. In addition to this, the \"convert circles\" button will convert all circular ROIs in the node into painted ROIs.","title":"Description"},{"location":"autodocs/multidot/#connections","text":"","title":"Connections"},{"location":"autodocs/multidot/#inputs","text":"Index Name Type Desc 0 input img (none)","title":"Inputs"},{"location":"autodocs/multidot/#outputs","text":"Index Name Type Desc 0 img img image with ROIs","title":"Outputs"},{"location":"autodocs/normimage/","text":"normimage Description Normalise the image to a single range taken from all channels. Honours ROIs. If you need to normalise each channel separately, use the norm() function in the \"expr\" node which has an optional argument for this. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"normimage"},{"location":"autodocs/normimage/#normimage","text":"","title":"normimage"},{"location":"autodocs/normimage/#description","text":"Normalise the image to a single range taken from all channels. Honours ROIs. If you need to normalise each channel separately, use the norm() function in the \"expr\" node which has an optional argument for this.","title":"Description"},{"location":"autodocs/normimage/#connections","text":"","title":"Connections"},{"location":"autodocs/normimage/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/normimage/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/offset/","text":"offset Description offset an image. Will create a zero band on one edge and clip on the opposite. ROIs are not honoured, but are passed through. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"offset"},{"location":"autodocs/offset/#offset","text":"","title":"offset"},{"location":"autodocs/offset/#description","text":"offset an image. Will create a zero band on one edge and clip on the opposite. ROIs are not honoured, but are passed through.","title":"Description"},{"location":"autodocs/offset/#connections","text":"","title":"Connections"},{"location":"autodocs/offset/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/offset/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/out/","text":"out Description The macro output connector (used inside macro prototypes) Connections Inputs Index Name Type Desc 0 (none) variant (none)","title":"out"},{"location":"autodocs/out/#out","text":"","title":"out"},{"location":"autodocs/out/#description","text":"The macro output connector (used inside macro prototypes)","title":"Description"},{"location":"autodocs/out/#connections","text":"","title":"Connections"},{"location":"autodocs/out/#inputs","text":"Index Name Type Desc 0 (none) variant (none)","title":"Inputs"},{"location":"autodocs/painted/","text":"painted Description Add a painted ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. Connections Inputs Index Name Type Desc 0 input img (none) Outputs Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"painted"},{"location":"autodocs/painted/#painted","text":"","title":"painted"},{"location":"autodocs/painted/#description","text":"Add a painted ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected.","title":"Description"},{"location":"autodocs/painted/#connections","text":"","title":"Connections"},{"location":"autodocs/painted/#inputs","text":"Index Name Type Desc 0 input img (none)","title":"Inputs"},{"location":"autodocs/painted/#outputs","text":"Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"Outputs"},{"location":"autodocs/pct/","text":"pct Description Locates the PCT and generates calibration coefficients **Very incomplete at the moment** Connections Inputs Index Name Type Desc 0 img img (none)","title":"pct"},{"location":"autodocs/pct/#pct","text":"","title":"pct"},{"location":"autodocs/pct/#description","text":"Locates the PCT and generates calibration coefficients **Very incomplete at the moment**","title":"Description"},{"location":"autodocs/pct/#connections","text":"","title":"Connections"},{"location":"autodocs/pct/#inputs","text":"Index Name Type Desc 0 img img (none)","title":"Inputs"},{"location":"autodocs/pixtest/","text":"pixtest Description Used in testing, but may be useful for running automated tests for users. Contains a table of pixel positions and values and checks them in the input image, flagging any errors. The output is numeric, and is the number of failing tests. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 results testresult (none)","title":"pixtest"},{"location":"autodocs/pixtest/#pixtest","text":"","title":"pixtest"},{"location":"autodocs/pixtest/#description","text":"Used in testing, but may be useful for running automated tests for users. Contains a table of pixel positions and values and checks them in the input image, flagging any errors. The output is numeric, and is the number of failing tests.","title":"Description"},{"location":"autodocs/pixtest/#connections","text":"","title":"Connections"},{"location":"autodocs/pixtest/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/pixtest/#outputs","text":"Index Name Type Desc 0 results testresult (none)","title":"Outputs"},{"location":"autodocs/poly/","text":"poly Description Add a polygonal ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. Connections Inputs Index Name Type Desc 0 input img (none) Outputs Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"poly"},{"location":"autodocs/poly/#poly","text":"","title":"poly"},{"location":"autodocs/poly/#description","text":"Add a polygonal ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected.","title":"Description"},{"location":"autodocs/poly/#connections","text":"","title":"Connections"},{"location":"autodocs/poly/#inputs","text":"Index Name Type Desc 0 input img (none)","title":"Inputs"},{"location":"autodocs/poly/#outputs","text":"Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"Outputs"},{"location":"autodocs/rect/","text":"rect Description Add a rectangular ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. Connections Inputs Index Name Type Desc 0 input img (none) Outputs Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"rect"},{"location":"autodocs/rect/#rect","text":"","title":"rect"},{"location":"autodocs/rect/#description","text":"Add a rectangular ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected.","title":"Description"},{"location":"autodocs/rect/#connections","text":"","title":"Connections"},{"location":"autodocs/rect/#inputs","text":"Index Name Type Desc 0 input img (none)","title":"Inputs"},{"location":"autodocs/rect/#outputs","text":"Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"Outputs"},{"location":"autodocs/roidq/","text":"roidq Description Automatically generate an ROI from DQ bits in a band or in all bands. **WARNING**: the ROI will be generated from DQ data from any bands in this image. It can then be applied to any other image or band - but this information is not tracked by the source mechanism. This means that some source tracking information can be lost. (Issue #68) Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 img img (none) 1 roi roi (none)","title":"roidq"},{"location":"autodocs/roidq/#roidq","text":"","title":"roidq"},{"location":"autodocs/roidq/#description","text":"Automatically generate an ROI from DQ bits in a band or in all bands. **WARNING**: the ROI will be generated from DQ data from any bands in this image. It can then be applied to any other image or band - but this information is not tracked by the source mechanism. This means that some source tracking information can be lost. (Issue #68)","title":"Description"},{"location":"autodocs/roidq/#connections","text":"","title":"Connections"},{"location":"autodocs/roidq/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/roidq/#outputs","text":"Index Name Type Desc 0 img img (none) 1 roi roi (none)","title":"Outputs"},{"location":"autodocs/roiexpr/","text":"roiexpr Description This node allows a region of interest to be composed from several regions of interest using an expression and imposed on an image. Several ROIs can be created within the node itself by using the \"Add ROI\" button. These will be assigned to the variables a,b,c.. within the expression, and can be edited by: clicking on their label in the left-most column of the table (to select the entire row) and then clicking and dragging on the canvas, double clicking on the description text in the table to open a numerical editor (not for poly or painted). Additional ROIs can be connected to the p, q, r inputs; these will be assigned to those variables within the expression. The input image is available as the variable 'img', so it is possible to access the image's original region of interest (or the union of all ROIs if it has more than one) by using roi(img). Other properties of the image are available and other calculations may be made, but the result of the expression must be an ROI. Examples: a+b : the union of ROIs 'a' and 'b' from the node's ROI list a*b : the intersection of ROIs 'a' and 'b' a-b : ROI 'a' with ROI 'b' removed -a : the negative of ROI 'a' (i.e. the entire image area as an ROI but with a hole in it) roi(img) - p : any ROIs on the image already, but with the ROI on input 'p' cut out Connections Inputs Index Name Type Desc 0 (none) img Image input 1 p roi ROI which appears as 'p' in expression 2 q roi ROI which appears as 'q' in expression 3 r roi ROI which appears as 'r' in expression Outputs Index Name Type Desc 0 (none) img Output image with ROI from expression result imposed 1 (none) roi The ROI generated from the expression","title":"roiexpr"},{"location":"autodocs/roiexpr/#roiexpr","text":"","title":"roiexpr"},{"location":"autodocs/roiexpr/#description","text":"This node allows a region of interest to be composed from several regions of interest using an expression and imposed on an image. Several ROIs can be created within the node itself by using the \"Add ROI\" button. These will be assigned to the variables a,b,c.. within the expression, and can be edited by: clicking on their label in the left-most column of the table (to select the entire row) and then clicking and dragging on the canvas, double clicking on the description text in the table to open a numerical editor (not for poly or painted). Additional ROIs can be connected to the p, q, r inputs; these will be assigned to those variables within the expression. The input image is available as the variable 'img', so it is possible to access the image's original region of interest (or the union of all ROIs if it has more than one) by using roi(img). Other properties of the image are available and other calculations may be made, but the result of the expression must be an ROI. Examples: a+b : the union of ROIs 'a' and 'b' from the node's ROI list a*b : the intersection of ROIs 'a' and 'b' a-b : ROI 'a' with ROI 'b' removed -a : the negative of ROI 'a' (i.e. the entire image area as an ROI but with a hole in it) roi(img) - p : any ROIs on the image already, but with the ROI on input 'p' cut out","title":"Description"},{"location":"autodocs/roiexpr/#connections","text":"","title":"Connections"},{"location":"autodocs/roiexpr/#inputs","text":"Index Name Type Desc 0 (none) img Image input 1 p roi ROI which appears as 'p' in expression 2 q roi ROI which appears as 'q' in expression 3 r roi ROI which appears as 'r' in expression","title":"Inputs"},{"location":"autodocs/roiexpr/#outputs","text":"Index Name Type Desc 0 (none) img Output image with ROI from expression result imposed 1 (none) roi The ROI generated from the expression","title":"Outputs"},{"location":"autodocs/scalartest/","text":"scalartest Description Test a scalar against a value Connections Inputs Index Name Type Desc 0 (none) number (none) Outputs Index Name Type Desc 0 results testresult (none)","title":"scalartest"},{"location":"autodocs/scalartest/#scalartest","text":"","title":"scalartest"},{"location":"autodocs/scalartest/#description","text":"Test a scalar against a value","title":"Description"},{"location":"autodocs/scalartest/#connections","text":"","title":"Connections"},{"location":"autodocs/scalartest/#inputs","text":"Index Name Type Desc 0 (none) number (none)","title":"Inputs"},{"location":"autodocs/scalartest/#outputs","text":"Index Name Type Desc 0 results testresult (none)","title":"Outputs"},{"location":"autodocs/sink/","text":"sink Description This provides a simple way to view any kind of data - images will be shown on a canvas, other data will be converted to text. Connections Inputs Index Name Type Desc 0 (none) any (none)","title":"sink"},{"location":"autodocs/sink/#sink","text":"","title":"sink"},{"location":"autodocs/sink/#description","text":"This provides a simple way to view any kind of data - images will be shown on a canvas, other data will be converted to text.","title":"Description"},{"location":"autodocs/sink/#connections","text":"","title":"Connections"},{"location":"autodocs/sink/#inputs","text":"Index Name Type Desc 0 (none) any (none)","title":"Inputs"},{"location":"autodocs/spectrum/","text":"spectrum Description Show the mean intensities for each frequency band in each region of interest (ROI) in each input. If an input has no ROI, the intensities of all the pixels in the input are used. It's quite possible for the different inputs to be different images, to permit comparison. Each region (or input) has a separate line in the resulting plot, labelled with the annotation for the ROI (or \"inputN\" for an input with no ROI). If ROIs in different inputs have the same annotation, they are labelled as \"inN:annotation\" where N is the input number. Each pixel has its own variance, so the shown variance is the pooled variance of all the pixels in the region. This is calculated as the variance of the means, plus the mean of the variances. If a point has data with BAD DQ bits in a band, those pixels are ignored in that band. If there are no good points, the point is not plotted for that band. A table of the values is also produced, and this output as CSV text. The table has one row per ROI or input, and the columns name - the name of the ROI or input m wavelength - the mean intensity for the given wavelength band s wavelength - the standard deviation of the mean intensity for the given wavelength band p wavelength - the number of pixels in the given wavelength band (usually the same as the number of pixels in the ROI, but may be fewer if the ROI has \"bad\" pixels in that band) The last two columns are repeated for each wavelength band. Connections Inputs Index Name Type Desc 0 0 img a single line in the plot 1 1 img a single line in the plot 2 2 img a single line in the plot 3 3 img a single line in the plot 4 4 img a single line in the plot 5 5 img a single line in the plot 6 6 img a single line in the plot 7 7 img a single line in the plot Outputs Index Name Type Desc 0 data data a CSV output (use 'dump' or 'sink' to read it)","title":"spectrum"},{"location":"autodocs/spectrum/#spectrum","text":"","title":"spectrum"},{"location":"autodocs/spectrum/#description","text":"Show the mean intensities for each frequency band in each region of interest (ROI) in each input. If an input has no ROI, the intensities of all the pixels in the input are used. It's quite possible for the different inputs to be different images, to permit comparison. Each region (or input) has a separate line in the resulting plot, labelled with the annotation for the ROI (or \"inputN\" for an input with no ROI). If ROIs in different inputs have the same annotation, they are labelled as \"inN:annotation\" where N is the input number. Each pixel has its own variance, so the shown variance is the pooled variance of all the pixels in the region. This is calculated as the variance of the means, plus the mean of the variances. If a point has data with BAD DQ bits in a band, those pixels are ignored in that band. If there are no good points, the point is not plotted for that band. A table of the values is also produced, and this output as CSV text. The table has one row per ROI or input, and the columns name - the name of the ROI or input m wavelength - the mean intensity for the given wavelength band s wavelength - the standard deviation of the mean intensity for the given wavelength band p wavelength - the number of pixels in the given wavelength band (usually the same as the number of pixels in the ROI, but may be fewer if the ROI has \"bad\" pixels in that band) The last two columns are repeated for each wavelength band.","title":"Description"},{"location":"autodocs/spectrum/#connections","text":"","title":"Connections"},{"location":"autodocs/spectrum/#inputs","text":"Index Name Type Desc 0 0 img a single line in the plot 1 1 img a single line in the plot 2 2 img a single line in the plot 3 3 img a single line in the plot 4 4 img a single line in the plot 5 5 img a single line in the plot 6 6 img a single line in the plot 7 7 img a single line in the plot","title":"Inputs"},{"location":"autodocs/spectrum/#outputs","text":"Index Name Type Desc 0 data data a CSV output (use 'dump' or 'sink' to read it)","title":"Outputs"},{"location":"autodocs/stitch/","text":"stitch Description This node performs manual stitching of multiple images into a single image. Connections Inputs Index Name Type Desc 0 0 img Input image 0 1 1 img Input image 1 2 2 img Input image 2 3 3 img Input image 3 4 4 img Input image 4 5 5 img Input image 5 6 6 img Input image 6 7 7 img Input image 7 Outputs Index Name Type Desc 0 (none) img Output image","title":"stitch"},{"location":"autodocs/stitch/#stitch","text":"","title":"stitch"},{"location":"autodocs/stitch/#description","text":"This node performs manual stitching of multiple images into a single image.","title":"Description"},{"location":"autodocs/stitch/#connections","text":"","title":"Connections"},{"location":"autodocs/stitch/#inputs","text":"Index Name Type Desc 0 0 img Input image 0 1 1 img Input image 1 2 2 img Input image 2 3 3 img Input image 3 4 4 img Input image 4 5 5 img Input image 5 6 6 img Input image 6 7 7 img Input image 7","title":"Inputs"},{"location":"autodocs/stitch/#outputs","text":"Index Name Type Desc 0 (none) img Output image","title":"Outputs"},{"location":"autodocs/stringtest/","text":"stringtest Description Convert the output of a node into string. Assert that this matches a given string. Both strings are stripped of whitespace and CRLF is converted to LF. Connections Inputs Index Name Type Desc 0 (none) any (none) Outputs Index Name Type Desc 0 (none) testresult (none)","title":"stringtest"},{"location":"autodocs/stringtest/#stringtest","text":"","title":"stringtest"},{"location":"autodocs/stringtest/#description","text":"Convert the output of a node into string. Assert that this matches a given string. Both strings are stripped of whitespace and CRLF is converted to LF.","title":"Description"},{"location":"autodocs/stringtest/#connections","text":"","title":"Connections"},{"location":"autodocs/stringtest/#inputs","text":"Index Name Type Desc 0 (none) any (none)","title":"Inputs"},{"location":"autodocs/stringtest/#outputs","text":"Index Name Type Desc 0 (none) testresult (none)","title":"Outputs"},{"location":"autodocs/striproi/","text":"striproi Description Strip ROIs from an image Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"striproi"},{"location":"autodocs/striproi/#striproi","text":"","title":"striproi"},{"location":"autodocs/striproi/#description","text":"Strip ROIs from an image","title":"Description"},{"location":"autodocs/striproi/#connections","text":"","title":"Connections"},{"location":"autodocs/striproi/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/striproi/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/tvl1_autoreg/","text":"tvl1 autoreg Description Use the TV-L1 solver to find an optical flow field for transforming one image into another. Not generally advised, and very slow. The node will output a version of the 'moving' image, distorted to map onto the 'fixed' image. Propagates uncertainty of the moving image by distorting that of the source image, and propagates DQs using nearest neighbour. Connections Inputs Index Name Type Desc 0 moving img (none) 1 fixed img (none) Outputs Index Name Type Desc 0 moved img (none)","title":"tvl1 autoreg"},{"location":"autodocs/tvl1_autoreg/#tvl1-autoreg","text":"","title":"tvl1 autoreg"},{"location":"autodocs/tvl1_autoreg/#description","text":"Use the TV-L1 solver to find an optical flow field for transforming one image into another. Not generally advised, and very slow. The node will output a version of the 'moving' image, distorted to map onto the 'fixed' image. Propagates uncertainty of the moving image by distorting that of the source image, and propagates DQs using nearest neighbour.","title":"Description"},{"location":"autodocs/tvl1_autoreg/#connections","text":"","title":"Connections"},{"location":"autodocs/tvl1_autoreg/#inputs","text":"Index Name Type Desc 0 moving img (none) 1 fixed img (none)","title":"Inputs"},{"location":"autodocs/tvl1_autoreg/#outputs","text":"Index Name Type Desc 0 moved img (none)","title":"Outputs"},{"location":"devguide/","text":"Overview These pages describe how to develop plugins for PCOT and use PCOT as a library, and may also contain various internal developer notes. Much of this documentation is quite brief - for developing plugins and libraries it's a good idea to supplement your knowledge by looking at examples and reading the source code! Important classes - probably read this first. Using PCOT as a library Writing PCOT plugins Miscellaneous notes on type internals and adding new types . notes on how operations/functions on Datum and Value work test/scratchpad of LaTeX support .","title":"Overview"},{"location":"devguide/#overview","text":"These pages describe how to develop plugins for PCOT and use PCOT as a library, and may also contain various internal developer notes. Much of this documentation is quite brief - for developing plugins and libraries it's a good idea to supplement your knowledge by looking at examples and reading the source code! Important classes - probably read this first. Using PCOT as a library Writing PCOT plugins","title":"Overview"},{"location":"devguide/#miscellaneous","text":"notes on type internals and adding new types . notes on how operations/functions on Datum and Value work test/scratchpad of LaTeX support .","title":"Miscellaneous"},{"location":"devguide/classes/","text":"Important classes pcot.document.Document, and an overview PCOT keeps all its user data in a pcot.document.Document : This contains: a pcot.documentsettings.DocumentSettings object a pcot.inputs.inp.InputManager object handling the inputs a pcot.document.UndoRedoStore which uses the serialisation/deserialisation system to handle an undo stack A dictionary of pcot.macros.XFormMacro objects - the user macros most importantly a pcot.xform.XFormGraph - a set of nodes of type pcot.xform.XForm connected together to do things. Each XForm points to a pcot.xform.XFormType singleton which controls its behaviour. Nodes (XForms) communicate by passing pcot.datum.Datum objects. Each Datum points to a pcot.datumtype.Type subclass singleton providing serialisation, copy, and display facilities. They also have values - two common value classes are pcot.imagecube.ImageCube for images, and pcot.value.Value objects for other arrays and scalars. ImageCubes can generate SubImageCube objects which are subsets of the image covered by ROIs and with bad pixels masked out (\"bad\" according to the data quality bits). pcot.xform.XFormGraph This represents the graph of nodes which take data from inputs and perform operations on them. There is one inside the document, and instances of macros also contain their own graphs (and the macro itself contains a \"template\" graph from which these are created). A graph contains: A set of pcot.xform.XForm objects (usually called nodes ) connected together by their inputs fields, which are tuples of (source node, index of output). A graph runs by finding those nodes which have no inputs and performing them; the entire graph will be recursively traversed. Nodes are only run if their inputs have data (if their parent nodes have run). pcot.xform.XForm All nodes are of the same type. Polymorphism - different nodes behaving differently - is accomplished through each node having a reference to a pcot.xform.XFormType object in its type member that controls its behaviour. Nodes communicate by passing pcot.datum.Datum objects. When a node runs its perform method It reads the inputs by deferencing the inputs fields and getting the input node and index of the output of that node, and reading the Datum stored in that output. It processes the data and stores the results in its outputs field as Datum objects. It then performs its \"child\" nodes. pcot.datum.Datum This is the fundamental data type, used both for communicating between nodes and handling values in the expr node. A Datum has a type , which is a pcot.datumtypes.Type object a value , whose type depends on the type field a source indicating where the data came from If the value is None , the Datum is null. There is a constant Datum.null for null data. pcot.datumtypes.Type The DatumType object provides methods for serialisation, copying, and display. Each is a singleton. It's easy to create custom DatumTypes. The most commonly used builtins are: Datum.IMG : contains an ImageCube Datum.NUMBER : contains a Value (these names are for the singleton objects, not their types - for example, Datum.IMG has the type pcot.datumtypes.ImgType .) pcot.imagecube.ImageCube This is the fundamental image type, consisting of image data - 2D (H x W) if there is only one band (channel), 3D otherwise (H x W x D). Type is float32. uncertainty data , same shape and type as image data. This is the standard deviation of each pixel. DQ data . This a 16-bit bitfield for each pixel. Some of these are considered errors - these are called \"bad\" DQ bits (e.g. no uncertainty, saturated, results from a division by zero). regions of interest annotations that have been added to the image mapping used to render the image as RGB sources for each band pcot.imagecube.SubImageCube These objects can be generated by calling the subimg() method on an ImageCube. They are the subset of an image covered by the regions of interest it has, along with a mask for those regions. Additionally, \"bad\" parts of the image (which can be different in different bands) can be masked out. Operations on images are typically done on these subimages, and then modifyWithSub is called on the imagecube to return a copy of that imagecube with the modified subimage spliced in. Useful subimage methods include: masked() : return the masked nominal image data maskedUncertainty() : return the masked uncertainty data maskedDQ() : return the masked DQ bits All these return numpy masked arrays. pcot.value.Value This is the fundamental numeric type, consisting of nominal value uncertainty value (standard deviation) DQ bits It's usually used for scalars but can also hold array data - internally ImageCubes (or parts of them) are converted into array Values for maths. If it does hold array data, the three elements must be the same shape. This type supports mathematical operations which propagate uncertainty and DQ. More on how Values work here Note : You may wonder why ImageCube and SubImageCube don't use Value internally. The answer is simply historical reasons: they were created a very long time before Value, and refactoring now could cause huge problems.","title":"Important classes"},{"location":"devguide/classes/#important-classes","text":"","title":"Important classes"},{"location":"devguide/classes/#pcotdocumentdocument-and-an-overview","text":"PCOT keeps all its user data in a pcot.document.Document : This contains: a pcot.documentsettings.DocumentSettings object a pcot.inputs.inp.InputManager object handling the inputs a pcot.document.UndoRedoStore which uses the serialisation/deserialisation system to handle an undo stack A dictionary of pcot.macros.XFormMacro objects - the user macros most importantly a pcot.xform.XFormGraph - a set of nodes of type pcot.xform.XForm connected together to do things. Each XForm points to a pcot.xform.XFormType singleton which controls its behaviour. Nodes (XForms) communicate by passing pcot.datum.Datum objects. Each Datum points to a pcot.datumtype.Type subclass singleton providing serialisation, copy, and display facilities. They also have values - two common value classes are pcot.imagecube.ImageCube for images, and pcot.value.Value objects for other arrays and scalars. ImageCubes can generate SubImageCube objects which are subsets of the image covered by ROIs and with bad pixels masked out (\"bad\" according to the data quality bits).","title":"pcot.document.Document, and an overview"},{"location":"devguide/classes/#pcotxformxformgraph","text":"This represents the graph of nodes which take data from inputs and perform operations on them. There is one inside the document, and instances of macros also contain their own graphs (and the macro itself contains a \"template\" graph from which these are created). A graph contains: A set of pcot.xform.XForm objects (usually called nodes ) connected together by their inputs fields, which are tuples of (source node, index of output). A graph runs by finding those nodes which have no inputs and performing them; the entire graph will be recursively traversed. Nodes are only run if their inputs have data (if their parent nodes have run).","title":"pcot.xform.XFormGraph"},{"location":"devguide/classes/#pcotxformxform","text":"All nodes are of the same type. Polymorphism - different nodes behaving differently - is accomplished through each node having a reference to a pcot.xform.XFormType object in its type member that controls its behaviour. Nodes communicate by passing pcot.datum.Datum objects. When a node runs its perform method It reads the inputs by deferencing the inputs fields and getting the input node and index of the output of that node, and reading the Datum stored in that output. It processes the data and stores the results in its outputs field as Datum objects. It then performs its \"child\" nodes.","title":"pcot.xform.XForm"},{"location":"devguide/classes/#pcotdatumdatum","text":"This is the fundamental data type, used both for communicating between nodes and handling values in the expr node. A Datum has a type , which is a pcot.datumtypes.Type object a value , whose type depends on the type field a source indicating where the data came from If the value is None , the Datum is null. There is a constant Datum.null for null data.","title":"pcot.datum.Datum"},{"location":"devguide/classes/#pcotdatumtypestype","text":"The DatumType object provides methods for serialisation, copying, and display. Each is a singleton. It's easy to create custom DatumTypes. The most commonly used builtins are: Datum.IMG : contains an ImageCube Datum.NUMBER : contains a Value (these names are for the singleton objects, not their types - for example, Datum.IMG has the type pcot.datumtypes.ImgType .)","title":"pcot.datumtypes.Type"},{"location":"devguide/classes/#pcotimagecubeimagecube","text":"This is the fundamental image type, consisting of image data - 2D (H x W) if there is only one band (channel), 3D otherwise (H x W x D). Type is float32. uncertainty data , same shape and type as image data. This is the standard deviation of each pixel. DQ data . This a 16-bit bitfield for each pixel. Some of these are considered errors - these are called \"bad\" DQ bits (e.g. no uncertainty, saturated, results from a division by zero). regions of interest annotations that have been added to the image mapping used to render the image as RGB sources for each band","title":"pcot.imagecube.ImageCube"},{"location":"devguide/classes/#pcotimagecubesubimagecube","text":"These objects can be generated by calling the subimg() method on an ImageCube. They are the subset of an image covered by the regions of interest it has, along with a mask for those regions. Additionally, \"bad\" parts of the image (which can be different in different bands) can be masked out. Operations on images are typically done on these subimages, and then modifyWithSub is called on the imagecube to return a copy of that imagecube with the modified subimage spliced in. Useful subimage methods include: masked() : return the masked nominal image data maskedUncertainty() : return the masked uncertainty data maskedDQ() : return the masked DQ bits All these return numpy masked arrays.","title":"pcot.imagecube.SubImageCube"},{"location":"devguide/classes/#pcotvaluevalue","text":"This is the fundamental numeric type, consisting of nominal value uncertainty value (standard deviation) DQ bits It's usually used for scalars but can also hold array data - internally ImageCubes (or parts of them) are converted into array Values for maths. If it does hold array data, the three elements must be the same shape. This type supports mathematical operations which propagate uncertainty and DQ. More on how Values work here Note : You may wonder why ImageCube and SubImageCube don't use Value internally. The answer is simply historical reasons: they were created a very long time before Value, and refactoring now could cause huge problems.","title":"pcot.value.Value"},{"location":"devguide/foo/","text":"Writing PCOT plugins The plugin path PCOT can be extended by adding Python scripts to a directory in the plugin path. By default this is just pcotplugins in the user's home directory, but it can be changed by editing the .pcot.ini file in that directory and modifying the pluginpath value in the Locations section. This should be a semicolon-separated list of directories. You may be tempted to use quotes in the names of you plugin directories: don't. It should work fine, even if your directories have spaces in them. For example, use ~/blah/RIM Dewarping and not ~/blah/\"RIM Dewarping\" Adding new types This is covered in a separate document , as it's not often done and is a little involved. Adding new Datum functions (for use in expr and Python code) The functions used in the expr expression node can also be used in Python programs which use PCOT as a library (this is covered here ). These are called Datum functions because they both take and return Datum objects. To create a Datum function: use the @datumfunc decorator. This will register the function and wrap it in two separate wrappers: one for use in expr , the other for use in Python. write a docstring in the correct format, as illustrated below. Here's an example which declares a function to take two numbers a,b and calculate a+b*2. Note that as in all PCOT code we have to make sure the sources are handled correctly. @datumfunc def example_func(a, b): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param a: number: first number @param b: number: second number \"\"\" return a + b * Datum.k(2) This is a trivial example that relies on Datum objects having operator overloads, but note that we need to multiply b by a Datum, not a number. To do this we use Datum.k to create a scalar constant. The docstring This should consist of a number of lines describing the function followed by a number of @param lines, one for each parameter. These contain the following, separated by colons: The string `@param' A Datum type name - these can be found in the constructors of datum type objects in datumtypes.py , but the most common are number , img , roi , string . A description of the parameter Optional numeric/string arguments Optional arguments with defaults can be provided, but only if they are numeric or strings (because these are the only types which make sense for the default values). In this case the defaults will be converted to Datum objects if they are used. Here is an example of a function which multiplies an image by a constant, with the default being 2: @datumfunc def example_func(img, k=2): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param img:img:the image @param k:number:the multiplier \"\"\" # no need to construct a Datum with Datum.k(), because k is already # a Datum. return img * k Here's another example which adds two numbers or multiplies them, depending on a string - and the default is to add: @datumfunc def stringexample(a, b, op='add'): \"\"\" String argument example @param a: number: first number @param b: number: second number @param op: string: operation to perform \"\"\" if op.get(Datum.STRING) == 'add': return a + b elif op.get(Datum.STRING) == 'sub': return a - b else: raise ValueError(\"Unknown operation\") Note that you usually have to extract the actual value from the Datum objects, as we do with the op argument above. In previous examples, we take advantage of Datum's extensive operator overloading. Variadic arguments For a variable number of arguments, use the *args keyword. Here, you have to check the types by hand. For example, this function will sum numbers: @datumfunc def sumall(*args): \"\"\" Sum all arguments \"\"\" s = sum([x.get(Datum.NUMBER).n for x in args]) return Datum(Datum.NUMBER, Value(s, 0, NOUNCERTAINTY), nullSourceSet) Note the use of Value here to construct a scalar value with standard deviation (zero here) and DQ bits (indicating no uncertainty data). Also note the mandatory use of a source set - just the nullSourceSet here to indicate there is no source; this is just a test function. In a real function we would combine the input sources. For more examples of functions, look at the ExpressionEvaluator constructor in the pcot.expressions.eval module. Adding new menu items This is done by adding a function to a list of functions called when a a main window is opened. Writing code here will require some knowledge of Qt. Here is a menu option added to the File menu which will look for an input 0 node and save its output image (if there is one) to an ENVI file. import pcot import os from PySide2 import QtWidgets from PySide2.QtWidgets import QAction, QMessageBox from pcot.dataformats import envi def saveEnvi(w): \"\"\"Function takes a PCOT main window. It finds the input 0 if it can, and then saves an ENVI from that image.\"\"\" try: node = w.doc.getNodeByName(\"input 0\") except NameError: print(\"cannot find node\") return res = QtWidgets.QFileDialog.getSaveFileName(w, \"ENVI file \", os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')), \"ENVI files (*.hdr)\") if res[0] != '': (root, ext) = os.path.splitext(res[0]) # get the output of that input 0 node img = node.getOutput(0,pcot.datum.Datum.IMG) envi.write(root,img) # the function to add the new menu item. This will take a single parameter: # the window to which the menu should be added. def addMenus(w): \"\"\"Add an item to the Edit menu\"\"\" # create the menu action (i.e. the item) act = QAction(\"save to ENVI\",parent=w) # find (or create) the Edit menu and add the action to it w.findOrAddMenu(\"Edit\").addAction(act) # link the action to the saveEnvi function, using a closure to # make sure the window argument is passed into that function. act.triggered.connect(lambda: saveEnvi(w)) # Add the addMenus functions to the list of functions called as the # main window opens. pcot.config.addMainWindowHook(addMenus) Adding new node types Node types are represented by singletons of subclasses of XFormType (Nodes themselves are of type XForm , which stands for transform for historical reasons). Developing new XFormType subclasses is largely beyond the scope of this document, but you can learn a lot from looking at the pcot.xforms package and the modules therein. To create a new node type, declare a new subclass of XFormType and decorate it with the @xformtype decorator. This will make the type autoregister: the singleton will be constructed and added to the internal type registry. You will need to write the following methods in your subclass: __init__(self) to construct the type object (NOT the individual nodes). This will call the superconstructor to set the type's name, group (for the palette), and version. It will add the input and output connectors. init(self, node) will initialise any private data in the node itself (which is an XForm ). Don't confuse this with __init__ ! createTab(self, node, window) will create a new node area (i.e. tab). Often, this can be a TabData which will look at the node's out attribute, which should be a Datum. perform(self, node) will actually perform the node's action, reading inputs and setting outputs. Remember: there is only one XFormType object for each node type. All nodes are of type XForm , and they link to an XFormType object to tell them how to behave. This might seem a really odd way to do things, but it follows \"favour composition over inheritance\" and saves messiness elsewhere. Here is an example which does edge detection with OpenCV: import cv2 as cv import numpy as np from pcot.sources import SourceSet from pcot.xform import XFormType, xformtype, Datum from pcot.xforms.tabdata import TabData from pcot.imagecube import ImageCube import pcot.config # The first part of the plugin creates a new type of node. # this decorator will cause the node to auto-register. @xformtype class XFormEdgeDetect(XFormType): \"\"\"This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab.\"\"\" def __init__(self): # this node should appear in the maths group. super().__init__(\"edge\", \"maths\", \"0.0.0\") # set input and output - they are images and are unnamed. self.addInputConnector(\"\", Datum.IMG) self.addOutputConnector(\"\", Datum.IMG) def createTab(self, n, w): # there is no custom tab, we just use a data canvas. This expects \"node.out\" to be set to # either None or a Datum. return TabData(n, w) def init(self, n): # No initialisation required. pass def perform(self, node): # get the input image img = node.getInput(0, Datum.IMG) if img is not None: # find mean of all channels - construct a transform array and then use it. mat = np.array([1 / img.channels] * img.channels).reshape((1, img.channels)) grey = cv.transform(img.img, mat) # convert to 8-bit integer from 32-bit float img8 = (grey * 255).astype('uint8') # Perform edge detection out = cv.Canny(img8, 100, 200) # Convert back to 32-bit float out = (out / 255).astype('float32') # create the imagecube and set node.out for the canvas in the tab img = ImageCube(out, None, img.sources) node.out = Datum(Datum.IMG, img) else: # no image on the input, set node.out to None node.out = Datum.null # output node.out node.setOutput(0, node.out) Writing custom Tabs As noted above, a new XFormType subclass (i.e. a new node type) can often just use TabData , which will display the Datum stored on its first output (output 0). Sometimes, however, a custom tab needs to be written. This can be a complex task, but an example is given in xformexample.py in the xforms package. All the standard XFormTypes are in this package, so you can also look at them. The basic idea is: Create a subclass of pcot.ui.tabs.Tab Write the constructor to call the superclass constructor and create the UI (or load a Designer-created UI by passing an argument to the superclass. constructor), and call self.nodeChanged() at the end to refresh the tab from the node. Override onNodeChanged() to update the tab from the node. Use the Qt signal/slot mechanism to connect the tab's controls to methods in the tab class and write code to update the node from the tab in these methods, calling self.changed() at the end of each method. The tab will have a node field which addresses the node it is viewing (but see below for a \"gotcha\" - the value of this field will change after an undo operation!) Using Canvas in custom tabs Creating a Canvas programmatically is straightforward, and there is an example of this in xformexample.py . If you are creating a tab in Designer, you need to add a canvas as a QWidget which you then \"promote\" to a custom control (the canvas). In the promote dialog, the class should be Canvas and the header file pcot.ui.canvas (i.e. the package name). In your onNodeChanged() method you will need to update the canvas. This involves doing a little setup, then getting the image we want to display - usually the output - and telling the canvas to display it: # do some setup self.canvas.setNode(self.node) # then display the image img = self.node.getOutput(0, Datum.IMG) self.canvas.display(img) The setup synchronises the canvas with the node, telling the canvas about the RGB mappings and that it should store data in the node for serialisation. We have to set that up each time because of how undo works, which is discussed in the next section. Undo and references to data in nodes This is a major \"gotcha.\" Whenever an undo occurs, the old node is discarded and a new node created from a previously archived version in memory. This means that the node field changes. Because of this, your tab must not store references to objects inside the node, because after an undo those references will be stale. Instead, always use self.node... to access data. It is OK to use the tab to store UI-only data which is not persisted (saved to a file or to the undo mechanism).","title":"Writing PCOT plugins"},{"location":"devguide/foo/#writing-pcot-plugins","text":"","title":"Writing PCOT plugins"},{"location":"devguide/foo/#the-plugin-path","text":"PCOT can be extended by adding Python scripts to a directory in the plugin path. By default this is just pcotplugins in the user's home directory, but it can be changed by editing the .pcot.ini file in that directory and modifying the pluginpath value in the Locations section. This should be a semicolon-separated list of directories. You may be tempted to use quotes in the names of you plugin directories: don't. It should work fine, even if your directories have spaces in them. For example, use ~/blah/RIM Dewarping and not ~/blah/\"RIM Dewarping\"","title":"The plugin path"},{"location":"devguide/foo/#adding-new-types","text":"This is covered in a separate document , as it's not often done and is a little involved.","title":"Adding new types"},{"location":"devguide/foo/#adding-new-datum-functions-for-use-in-expr-and-python-code","text":"The functions used in the expr expression node can also be used in Python programs which use PCOT as a library (this is covered here ). These are called Datum functions because they both take and return Datum objects. To create a Datum function: use the @datumfunc decorator. This will register the function and wrap it in two separate wrappers: one for use in expr , the other for use in Python. write a docstring in the correct format, as illustrated below. Here's an example which declares a function to take two numbers a,b and calculate a+b*2. Note that as in all PCOT code we have to make sure the sources are handled correctly. @datumfunc def example_func(a, b): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param a: number: first number @param b: number: second number \"\"\" return a + b * Datum.k(2) This is a trivial example that relies on Datum objects having operator overloads, but note that we need to multiply b by a Datum, not a number. To do this we use Datum.k to create a scalar constant.","title":"Adding new Datum functions (for use in expr and Python code)"},{"location":"devguide/foo/#the-docstring","text":"This should consist of a number of lines describing the function followed by a number of @param lines, one for each parameter. These contain the following, separated by colons: The string `@param' A Datum type name - these can be found in the constructors of datum type objects in datumtypes.py , but the most common are number , img , roi , string . A description of the parameter","title":"The docstring"},{"location":"devguide/foo/#optional-numericstring-arguments","text":"Optional arguments with defaults can be provided, but only if they are numeric or strings (because these are the only types which make sense for the default values). In this case the defaults will be converted to Datum objects if they are used. Here is an example of a function which multiplies an image by a constant, with the default being 2: @datumfunc def example_func(img, k=2): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param img:img:the image @param k:number:the multiplier \"\"\" # no need to construct a Datum with Datum.k(), because k is already # a Datum. return img * k Here's another example which adds two numbers or multiplies them, depending on a string - and the default is to add: @datumfunc def stringexample(a, b, op='add'): \"\"\" String argument example @param a: number: first number @param b: number: second number @param op: string: operation to perform \"\"\" if op.get(Datum.STRING) == 'add': return a + b elif op.get(Datum.STRING) == 'sub': return a - b else: raise ValueError(\"Unknown operation\") Note that you usually have to extract the actual value from the Datum objects, as we do with the op argument above. In previous examples, we take advantage of Datum's extensive operator overloading.","title":"Optional numeric/string arguments"},{"location":"devguide/foo/#variadic-arguments","text":"For a variable number of arguments, use the *args keyword. Here, you have to check the types by hand. For example, this function will sum numbers: @datumfunc def sumall(*args): \"\"\" Sum all arguments \"\"\" s = sum([x.get(Datum.NUMBER).n for x in args]) return Datum(Datum.NUMBER, Value(s, 0, NOUNCERTAINTY), nullSourceSet) Note the use of Value here to construct a scalar value with standard deviation (zero here) and DQ bits (indicating no uncertainty data). Also note the mandatory use of a source set - just the nullSourceSet here to indicate there is no source; this is just a test function. In a real function we would combine the input sources. For more examples of functions, look at the ExpressionEvaluator constructor in the pcot.expressions.eval module.","title":"Variadic arguments"},{"location":"devguide/foo/#adding-new-menu-items","text":"This is done by adding a function to a list of functions called when a a main window is opened. Writing code here will require some knowledge of Qt. Here is a menu option added to the File menu which will look for an input 0 node and save its output image (if there is one) to an ENVI file. import pcot import os from PySide2 import QtWidgets from PySide2.QtWidgets import QAction, QMessageBox from pcot.dataformats import envi def saveEnvi(w): \"\"\"Function takes a PCOT main window. It finds the input 0 if it can, and then saves an ENVI from that image.\"\"\" try: node = w.doc.getNodeByName(\"input 0\") except NameError: print(\"cannot find node\") return res = QtWidgets.QFileDialog.getSaveFileName(w, \"ENVI file \", os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')), \"ENVI files (*.hdr)\") if res[0] != '': (root, ext) = os.path.splitext(res[0]) # get the output of that input 0 node img = node.getOutput(0,pcot.datum.Datum.IMG) envi.write(root,img) # the function to add the new menu item. This will take a single parameter: # the window to which the menu should be added. def addMenus(w): \"\"\"Add an item to the Edit menu\"\"\" # create the menu action (i.e. the item) act = QAction(\"save to ENVI\",parent=w) # find (or create) the Edit menu and add the action to it w.findOrAddMenu(\"Edit\").addAction(act) # link the action to the saveEnvi function, using a closure to # make sure the window argument is passed into that function. act.triggered.connect(lambda: saveEnvi(w)) # Add the addMenus functions to the list of functions called as the # main window opens. pcot.config.addMainWindowHook(addMenus)","title":"Adding new menu items"},{"location":"devguide/foo/#adding-new-node-types","text":"Node types are represented by singletons of subclasses of XFormType (Nodes themselves are of type XForm , which stands for transform for historical reasons). Developing new XFormType subclasses is largely beyond the scope of this document, but you can learn a lot from looking at the pcot.xforms package and the modules therein. To create a new node type, declare a new subclass of XFormType and decorate it with the @xformtype decorator. This will make the type autoregister: the singleton will be constructed and added to the internal type registry. You will need to write the following methods in your subclass: __init__(self) to construct the type object (NOT the individual nodes). This will call the superconstructor to set the type's name, group (for the palette), and version. It will add the input and output connectors. init(self, node) will initialise any private data in the node itself (which is an XForm ). Don't confuse this with __init__ ! createTab(self, node, window) will create a new node area (i.e. tab). Often, this can be a TabData which will look at the node's out attribute, which should be a Datum. perform(self, node) will actually perform the node's action, reading inputs and setting outputs. Remember: there is only one XFormType object for each node type. All nodes are of type XForm , and they link to an XFormType object to tell them how to behave. This might seem a really odd way to do things, but it follows \"favour composition over inheritance\" and saves messiness elsewhere. Here is an example which does edge detection with OpenCV: import cv2 as cv import numpy as np from pcot.sources import SourceSet from pcot.xform import XFormType, xformtype, Datum from pcot.xforms.tabdata import TabData from pcot.imagecube import ImageCube import pcot.config # The first part of the plugin creates a new type of node. # this decorator will cause the node to auto-register. @xformtype class XFormEdgeDetect(XFormType): \"\"\"This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab.\"\"\" def __init__(self): # this node should appear in the maths group. super().__init__(\"edge\", \"maths\", \"0.0.0\") # set input and output - they are images and are unnamed. self.addInputConnector(\"\", Datum.IMG) self.addOutputConnector(\"\", Datum.IMG) def createTab(self, n, w): # there is no custom tab, we just use a data canvas. This expects \"node.out\" to be set to # either None or a Datum. return TabData(n, w) def init(self, n): # No initialisation required. pass def perform(self, node): # get the input image img = node.getInput(0, Datum.IMG) if img is not None: # find mean of all channels - construct a transform array and then use it. mat = np.array([1 / img.channels] * img.channels).reshape((1, img.channels)) grey = cv.transform(img.img, mat) # convert to 8-bit integer from 32-bit float img8 = (grey * 255).astype('uint8') # Perform edge detection out = cv.Canny(img8, 100, 200) # Convert back to 32-bit float out = (out / 255).astype('float32') # create the imagecube and set node.out for the canvas in the tab img = ImageCube(out, None, img.sources) node.out = Datum(Datum.IMG, img) else: # no image on the input, set node.out to None node.out = Datum.null # output node.out node.setOutput(0, node.out)","title":"Adding new node types"},{"location":"devguide/foo/#writing-custom-tabs","text":"As noted above, a new XFormType subclass (i.e. a new node type) can often just use TabData , which will display the Datum stored on its first output (output 0). Sometimes, however, a custom tab needs to be written. This can be a complex task, but an example is given in xformexample.py in the xforms package. All the standard XFormTypes are in this package, so you can also look at them. The basic idea is: Create a subclass of pcot.ui.tabs.Tab Write the constructor to call the superclass constructor and create the UI (or load a Designer-created UI by passing an argument to the superclass. constructor), and call self.nodeChanged() at the end to refresh the tab from the node. Override onNodeChanged() to update the tab from the node. Use the Qt signal/slot mechanism to connect the tab's controls to methods in the tab class and write code to update the node from the tab in these methods, calling self.changed() at the end of each method. The tab will have a node field which addresses the node it is viewing (but see below for a \"gotcha\" - the value of this field will change after an undo operation!)","title":"Writing custom Tabs"},{"location":"devguide/foo/#using-canvas-in-custom-tabs","text":"Creating a Canvas programmatically is straightforward, and there is an example of this in xformexample.py . If you are creating a tab in Designer, you need to add a canvas as a QWidget which you then \"promote\" to a custom control (the canvas). In the promote dialog, the class should be Canvas and the header file pcot.ui.canvas (i.e. the package name). In your onNodeChanged() method you will need to update the canvas. This involves doing a little setup, then getting the image we want to display - usually the output - and telling the canvas to display it: # do some setup self.canvas.setNode(self.node) # then display the image img = self.node.getOutput(0, Datum.IMG) self.canvas.display(img) The setup synchronises the canvas with the node, telling the canvas about the RGB mappings and that it should store data in the node for serialisation. We have to set that up each time because of how undo works, which is discussed in the next section.","title":"Using Canvas in custom tabs"},{"location":"devguide/foo/#undo-and-references-to-data-in-nodes","text":"This is a major \"gotcha.\" Whenever an undo occurs, the old node is discarded and a new node created from a previously archived version in memory. This means that the node field changes. Because of this, your tab must not store references to objects inside the node, because after an undo those references will be stale. Instead, always use self.node... to access data. It is OK to use the tab to store UI-only data which is not persisted (saved to a file or to the undo mechanism).","title":"Undo and references to data in nodes"},{"location":"devguide/latex/","text":"A quick test of LaTeX support Please ignore this page - I use it as a scratchpad for various LaTeX tests. It might seem a bit unpleasant to have this as part of the live documentation, but that absolutely guarantees that it works everywhere! LaTeX in these documents is handled with the pymdownx.arithmatex plugin, which really just hands processing off to MathJax. MathJax then renders the LaTeX using JavaScript. Lots and lots of very clever JavaScript. Here are some tests: Inline equation: y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) should work. Inline equation: $y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right)$ should work. Block equations. This has to use double-backslash: \\[ y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) \\] \\\\[ y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) \\\\] but this one doesn't: \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} Align. Note that the reference doesn't work! \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} Matrix. Note I've had to wrap in an equation. \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation} \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation} \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation}","title":"A quick test of LaTeX support"},{"location":"devguide/latex/#a-quick-test-of-latex-support","text":"Please ignore this page - I use it as a scratchpad for various LaTeX tests. It might seem a bit unpleasant to have this as part of the live documentation, but that absolutely guarantees that it works everywhere! LaTeX in these documents is handled with the pymdownx.arithmatex plugin, which really just hands processing off to MathJax. MathJax then renders the LaTeX using JavaScript. Lots and lots of very clever JavaScript. Here are some tests: Inline equation: y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) should work. Inline equation: $y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right)$ should work. Block equations. This has to use double-backslash: \\[ y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) \\] \\\\[ y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) \\\\] but this one doesn't: \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} Align. Note that the reference doesn't work! \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} Matrix. Note I've had to wrap in an equation. \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation} \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation} \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation}","title":"A quick test of LaTeX support"},{"location":"devguide/library/","text":"Using PCOT as a library As well as being a stand-alone application, PCOT can be used as a library by other Python programs. This page discusses three ways to do this, although the various elements can be easily blended in a single program: Loading a PCOT document, reading some data and passing it through the document's graph; Building a PCOT document programmatically and passing data through it; Using PCOT functions and data types without a graph. While the latter two techniques can be useful for quick ad-hoc work, on the whole we feel it is better to exchange PCOT documents for traceability and clarity. Loading and running PCOT documents A typical example might be a script to read a PCOT document and run some data through that document's graph. You could do that like this: # This example opens a graph, process some ENVI files through that graph, # and saves them back to an ENVI. It assumes the graph has an \"input 0\" node # which receives an image and a \"sink\" node which receives the processed # image. import pcot from pcot.document import Document from pcot.datum import Datum from pcot.dataformats.envi import write # initialise PCOT pcot.setup() # load the document doc = Document(\"1.pcot\") # run the graph for some ENVI files. We'll just do one here, the ENVI # document contained in the files 1.hdr and 1.dat (an ENVI document # consists of two files: header and data). for file in (\"1\",): # load the given ENVI file into input 0 rv = doc.setInputENVI(0, file+\".hdr\") if rv is not None: raise Exception(f\"{rv}\") # run the document's graph doc.run() # get the \"sink\" node outNode = doc.getNodeByName(\"sink\") # get its output img = outNode.out.get(Datum.IMG) # write to new ENVI, e.g. 1b.hdr write(file+\"b\",img) Building a PCOT document It's also possible to build a PCOT document, creating nodes within its graph. Consider the graph Figure: A simple graph. Click on image to expand. This could be built and run for a particular file with the following code: #!/usr/bin/env python import pcot import pcot.document from pcot.datum import Datum pcot.setup() doc = pcot.document.Document() result = doc.setInputENVI(0, \"/home/white/PCOT/fff.hdr\") is not None assert result is not None # create a document with just an input node in it, to bring that input into the document's graph innode = doc.graph.create(\"input 0\") # add a region of interest (ROI) node to the image roinode = doc.graph.create(\"circle\") # set the circle to be centred at (32,32) with a radius of 3 pixels roinode.roi.set(32,32,3) # connect its first input to the input node's first output # args: # input on this node to connect # node to get connection from # index on that node to connect to. roinode.connect(0,innode,0) # connect a node to the ROI node which takes the resulting image-with-ROI # and plots the spectrum of that ROI specnode = doc.graph.create(\"spectrum\") specnode.connect(0,roinode,0) # run the document doc.run() # get the output of the spectrum node, which will be a Datum, # and dereference the Datum, ensuring that the data is of the right type. # The result will be a Table object. output = specnode.getOutput(0, Datum.DATA) # print the table as CSV. print(output) Using PCOT functions and data types without a graph Often it is much simpler to just use the underlying PCOT data types without a graph. The operation described in the previous section is an example of this. We could use the dataformats.load package to load the data directly and manipulate it: #!/usr/bin/env python import pcot from pcot.datum import Datum from pcot.dataformats import load from pcot.rois import ROICircle from pcot.utils.spectrum import SpectrumSet pcot.setup() # load the ENVI file as a Datum object. Will raise an exception # if there is a problem datum = load.envi(\"/home/white/PCOT/fff.hdr\") # retrieve the image, ensuring it's an IMG datum img = datum.get(Datum.IMG) # add a region of interest (ROI) node to the image: # a circle to be centred at (32,32) with a radius of 3 pixels img.rois.append(ROICircle(32,32,3)) # construct a spectrum set from this image - this can create spectra # for multiple sources and combine them. Here we are just using a single # source - the image we are working with - and we're calling it \"in.\" ss = SpectrumSet({\"in\": img}) # Generate a table from the results and print it (as a CSV table). print(ss.table())","title":"Using PCOT as a library"},{"location":"devguide/library/#using-pcot-as-a-library","text":"As well as being a stand-alone application, PCOT can be used as a library by other Python programs. This page discusses three ways to do this, although the various elements can be easily blended in a single program: Loading a PCOT document, reading some data and passing it through the document's graph; Building a PCOT document programmatically and passing data through it; Using PCOT functions and data types without a graph. While the latter two techniques can be useful for quick ad-hoc work, on the whole we feel it is better to exchange PCOT documents for traceability and clarity.","title":"Using PCOT as a library"},{"location":"devguide/library/#loading-and-running-pcot-documents","text":"A typical example might be a script to read a PCOT document and run some data through that document's graph. You could do that like this: # This example opens a graph, process some ENVI files through that graph, # and saves them back to an ENVI. It assumes the graph has an \"input 0\" node # which receives an image and a \"sink\" node which receives the processed # image. import pcot from pcot.document import Document from pcot.datum import Datum from pcot.dataformats.envi import write # initialise PCOT pcot.setup() # load the document doc = Document(\"1.pcot\") # run the graph for some ENVI files. We'll just do one here, the ENVI # document contained in the files 1.hdr and 1.dat (an ENVI document # consists of two files: header and data). for file in (\"1\",): # load the given ENVI file into input 0 rv = doc.setInputENVI(0, file+\".hdr\") if rv is not None: raise Exception(f\"{rv}\") # run the document's graph doc.run() # get the \"sink\" node outNode = doc.getNodeByName(\"sink\") # get its output img = outNode.out.get(Datum.IMG) # write to new ENVI, e.g. 1b.hdr write(file+\"b\",img)","title":"Loading and running PCOT documents"},{"location":"devguide/library/#building-a-pcot-document","text":"It's also possible to build a PCOT document, creating nodes within its graph. Consider the graph Figure: A simple graph. Click on image to expand. This could be built and run for a particular file with the following code: #!/usr/bin/env python import pcot import pcot.document from pcot.datum import Datum pcot.setup() doc = pcot.document.Document() result = doc.setInputENVI(0, \"/home/white/PCOT/fff.hdr\") is not None assert result is not None # create a document with just an input node in it, to bring that input into the document's graph innode = doc.graph.create(\"input 0\") # add a region of interest (ROI) node to the image roinode = doc.graph.create(\"circle\") # set the circle to be centred at (32,32) with a radius of 3 pixels roinode.roi.set(32,32,3) # connect its first input to the input node's first output # args: # input on this node to connect # node to get connection from # index on that node to connect to. roinode.connect(0,innode,0) # connect a node to the ROI node which takes the resulting image-with-ROI # and plots the spectrum of that ROI specnode = doc.graph.create(\"spectrum\") specnode.connect(0,roinode,0) # run the document doc.run() # get the output of the spectrum node, which will be a Datum, # and dereference the Datum, ensuring that the data is of the right type. # The result will be a Table object. output = specnode.getOutput(0, Datum.DATA) # print the table as CSV. print(output)","title":"Building a PCOT document"},{"location":"devguide/library/#using-pcot-functions-and-data-types-without-a-graph","text":"Often it is much simpler to just use the underlying PCOT data types without a graph. The operation described in the previous section is an example of this. We could use the dataformats.load package to load the data directly and manipulate it: #!/usr/bin/env python import pcot from pcot.datum import Datum from pcot.dataformats import load from pcot.rois import ROICircle from pcot.utils.spectrum import SpectrumSet pcot.setup() # load the ENVI file as a Datum object. Will raise an exception # if there is a problem datum = load.envi(\"/home/white/PCOT/fff.hdr\") # retrieve the image, ensuring it's an IMG datum img = datum.get(Datum.IMG) # add a region of interest (ROI) node to the image: # a circle to be centred at (32,32) with a radius of 3 pixels img.rois.append(ROICircle(32,32,3)) # construct a spectrum set from this image - this can create spectra # for multiple sources and combine them. Here we are just using a single # source - the image we are working with - and we're calling it \"in.\" ss = SpectrumSet({\"in\": img}) # Generate a table from the results and print it (as a CSV table). print(ss.table())","title":"Using PCOT functions and data types without a graph"},{"location":"devguide/plugins/","text":"Writing PCOT plugins The plugin path PCOT can be extended by adding Python scripts to a directory in the plugin path. By default this is just pcotplugins in the user's home directory, but it can be changed by editing the .pcot.ini file in that directory and modifying the pluginpath value in the Locations section. This should be a semicolon-separated list of directories. You may be tempted to use quotes in the names of you plugin directories: don't. It should work fine, even if your directories have spaces in them. For example, use ~/blah/RIM Dewarping and not ~/blah/\"RIM Dewarping\" Adding new types This is covered in a separate document , as it's not often done and is a little involved. Adding new Datum functions (for use in expr and Python code) The functions used in the expr expression node can also be used in Python programs which use PCOT as a library (this is covered here ). These are called Datum functions because they both take and return Datum objects. To create a Datum function: use the @datumfunc decorator. This will register the function and wrap it in two separate wrappers: one for use in expr , the other for use in Python. write a docstring in the correct format, as illustrated below. Here's an example which declares a function to take two numbers a,b and calculate a+b*2. Note that as in all PCOT code we have to make sure the sources are handled correctly. @datumfunc def example_func(a, b): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param a: number: first number @param b: number: second number \"\"\" return a + b * Datum.k(2) This is a trivial example that relies on Datum objects having operator overloads, but note that we need to multiply b by a Datum, not a number. To do this we use Datum.k to create a scalar constant. The docstring This should consist of a number of lines describing the function followed by a number of @param lines, one for each parameter. These contain the following, separated by colons: The string `@param' A Datum type name - these can be found in the constructors of datum type objects in datumtypes.py , but the most common are number , img , roi , string . A description of the parameter Optional numeric/string arguments Optional arguments with defaults can be provided, but only if they are numeric or strings (because these are the only types which make sense for the default values). In this case the defaults will be converted to Datum objects if they are used. Here is an example of a function which multiplies an image by a constant, with the default being 2: @datumfunc def example_func(img, k=2): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param img:img:the image @param k:number:the multiplier \"\"\" # no need to construct a Datum with Datum.k(), because k is already # a Datum. return img * k Here's another example which adds two numbers or multiplies them, depending on a string - and the default is to add: @datumfunc def stringexample(a, b, op='add'): \"\"\" String argument example @param a: number: first number @param b: number: second number @param op: string: operation to perform \"\"\" if op.get(Datum.STRING) == 'add': return a + b elif op.get(Datum.STRING) == 'sub': return a - b else: raise ValueError(\"Unknown operation\") Note that you usually have to extract the actual value from the Datum objects, as we do with the op argument above. In previous examples, we take advantage of Datum's extensive operator overloading. Variadic arguments For a variable number of arguments, use the *args keyword. Here, you have to check the types by hand. For example, this function will sum numbers: @datumfunc def sumall(*args): \"\"\" Sum all arguments \"\"\" s = sum([x.get(Datum.NUMBER).n for x in args]) return Datum(Datum.NUMBER, Value(s, 0, NOUNCERTAINTY), nullSourceSet) Note the use of Value here to construct a scalar value with standard deviation (zero here) and DQ bits (indicating no uncertainty data). Also note the mandatory use of a source set - just the nullSourceSet here to indicate there is no source; this is just a test function. In a real function we would combine the input sources. For more examples of functions, look at the ExpressionEvaluator constructor in the pcot.expressions.eval module. Adding new menu items This is done by adding a function to a list of functions called when a a main window is opened. Writing code here will require some knowledge of Qt. Here is a menu option added to the File menu which will look for selected node in the document's graph, fetch its first output, and save it as an ENVI file (assuming it is an image - error checking is left as an exercise for the reader). import pcot import os from PySide2 import QtWidgets from PySide2.QtWidgets import QAction, QMessageBox from pcot.dataformats import envi def saveEnvi(w): \"\"\"Function takes a PCOT main window. It finds the first selected node, gets its output 0, and then saves an ENVI from that image.\"\"\" sel = w.doc.getSelection() if len(sel) == 0: ui.log(\"no selected node\") return node = sel[0] directory = os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')) res = QtWidgets.QFileDialog.getSaveFileName(w, \"ENVI file \", os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')), \"ENVI files (*.hdr)\") if res[0] != '': # get the output of that node (root, ext) = os.path.splitext(res[0]) img = node.getOutput(0, pcot.datum.Datum.IMG) envi.write(root, img) # the function to add the new menu item. This will take a single parameter: # the window to which the menu should be added. def addMenus(w): \"\"\"Add an item to the Edit menu\"\"\" # create the menu action (i.e. the item) act = QAction(\"save to ENVI\",parent=w) # find (or create) the Edit menu and add the action to it w.findOrAddMenu(\"Edit\").addAction(act) # link the action to the saveEnvi function, using a closure to # make sure the window argument is passed into that function. act.triggered.connect(lambda: saveEnvi(w)) # Add the addMenus functions to the list of functions called as the # main window opens. pcot.config.addMainWindowHook(addMenus) Adding new node types Node types are represented by singletons of subclasses of XFormType (Nodes themselves are of type XForm , which stands for transform for historical reasons). Developing new XFormType subclasses is largely beyond the scope of this document, but you can learn a lot from looking at the pcot.xforms package and the modules therein. To create a new node type, declare a new subclass of XFormType and decorate it with the @xformtype decorator. This will make the type autoregister: the singleton will be constructed and added to the internal type registry. You will need to write the following methods in your subclass: __init__(self) to construct the type object (NOT the individual nodes). This will call the superconstructor to set the type's name, group (for the palette), and version. It will add the input and output connectors. init(self, node) will initialise any private data in the node itself (which is an XForm ). Don't confuse this with __init__ ! createTab(self, node, window) will create a new node area (i.e. tab). Often, this can be a TabData which will look at the node's out attribute, which should be a Datum. perform(self, node) will actually perform the node's action, reading inputs and setting outputs. Remember: there is only one XFormType object for each node type. All nodes are of type XForm , and they link to an XFormType object to tell them how to behave. This might seem a really odd way to do things, but it follows \"favour composition over inheritance\" and saves messiness elsewhere. Here is an example which does edge detection with OpenCV: import cv2 as cv import numpy as np from pcot.sources import SourceSet from pcot.xform import XFormType, xformtype, Datum from pcot.xforms.tabdata import TabData from pcot.imagecube import ImageCube import pcot.config # The first part of the plugin creates a new type of node. # this decorator will cause the node to auto-register. @xformtype class XFormEdgeDetect(XFormType): \"\"\"This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab.\"\"\" def __init__(self): # this node should appear in the maths group. super().__init__(\"edge\", \"maths\", \"0.0.0\") # set input and output - they are images and are unnamed. self.addInputConnector(\"\", Datum.IMG) self.addOutputConnector(\"\", Datum.IMG) def createTab(self, n, w): # there is no custom tab, we just use a data canvas. This expects \"node.out\" to be set to # either None or a Datum. return TabData(n, w) def init(self, n): # No initialisation required. pass def perform(self, node): # get the input image img = node.getInput(0, Datum.IMG) if img is not None: # find mean of all channels - construct a transform array and then use it. mat = np.array([1 / img.channels] * img.channels).reshape((1, img.channels)) grey = cv.transform(img.img, mat) # convert to 8-bit integer from 32-bit float img8 = (grey * 255).astype('uint8') # Perform edge detection out = cv.Canny(img8, 100, 200) # Convert back to 32-bit float out = (out / 255).astype('float32') # create the imagecube and set node.out for the canvas in the tab img = ImageCube(out, None, img.sources) node.out = Datum(Datum.IMG, img) else: # no image on the input, set node.out to None node.out = Datum.null # output node.out node.setOutput(0, node.out) Writing custom Tabs As noted above, a new XFormType subclass (i.e. a new node type) can often just use TabData , which will display the Datum stored on its first output (output 0). Sometimes, however, a custom tab needs to be written. This can be a complex task, but an example is given in xformexample.py in the xforms package. All the standard XFormTypes are in this package, so you can also look at them. The basic idea is: Create a subclass of pcot.ui.tabs.Tab Write the constructor to call the superclass constructor and create the UI (or load a Designer-created UI by passing an argument to the superclass. constructor), and call self.nodeChanged() at the end to refresh the tab from the node. Override onNodeChanged() to update the tab from the node. Use the Qt signal/slot mechanism to connect the tab's controls to methods in the tab class and write code to update the node from the tab in these methods, calling self.changed() at the end of each method. The tab will have a node field which addresses the node it is viewing (but see below for a \"gotcha\" - the value of this field will change after an undo operation!) Using Canvas in custom tabs Creating a Canvas programmatically is straightforward, and there is an example of this in xformexample.py . If you are creating a tab in Designer, you need to add a canvas as a QWidget which you then \"promote\" to a custom control (the canvas). In the promote dialog, the class should be Canvas and the header file pcot.ui.canvas (i.e. the package name). In your onNodeChanged() method you will need to update the canvas. This involves doing a little setup, then getting the image we want to display - usually the output - and telling the canvas to display it: # do some setup self.canvas.setNode(self.node) # then display the image img = self.node.getOutput(0, Datum.IMG) self.canvas.display(img) The setup synchronises the canvas with the node, telling the canvas about the RGB mappings and that it should store data in the node for serialisation. We have to set that up each time because of how undo works, which is discussed in the next section. Undo and references to data in nodes This is a major \"gotcha.\" Whenever an undo occurs, the old node is discarded and a new node created from a previously archived version in memory. This means that the node field changes. Because of this, your tab must not store references to objects inside the node, because after an undo those references will be stale. Instead, always use self.node... to access data. It is OK to use the tab to store UI-only data which is not persisted (saved to a file or to the undo mechanism).","title":"Writing PCOT plugins"},{"location":"devguide/plugins/#writing-pcot-plugins","text":"","title":"Writing PCOT plugins"},{"location":"devguide/plugins/#the-plugin-path","text":"PCOT can be extended by adding Python scripts to a directory in the plugin path. By default this is just pcotplugins in the user's home directory, but it can be changed by editing the .pcot.ini file in that directory and modifying the pluginpath value in the Locations section. This should be a semicolon-separated list of directories. You may be tempted to use quotes in the names of you plugin directories: don't. It should work fine, even if your directories have spaces in them. For example, use ~/blah/RIM Dewarping and not ~/blah/\"RIM Dewarping\"","title":"The plugin path"},{"location":"devguide/plugins/#adding-new-types","text":"This is covered in a separate document , as it's not often done and is a little involved.","title":"Adding new types"},{"location":"devguide/plugins/#adding-new-datum-functions-for-use-in-expr-and-python-code","text":"The functions used in the expr expression node can also be used in Python programs which use PCOT as a library (this is covered here ). These are called Datum functions because they both take and return Datum objects. To create a Datum function: use the @datumfunc decorator. This will register the function and wrap it in two separate wrappers: one for use in expr , the other for use in Python. write a docstring in the correct format, as illustrated below. Here's an example which declares a function to take two numbers a,b and calculate a+b*2. Note that as in all PCOT code we have to make sure the sources are handled correctly. @datumfunc def example_func(a, b): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param a: number: first number @param b: number: second number \"\"\" return a + b * Datum.k(2) This is a trivial example that relies on Datum objects having operator overloads, but note that we need to multiply b by a Datum, not a number. To do this we use Datum.k to create a scalar constant.","title":"Adding new Datum functions (for use in expr and Python code)"},{"location":"devguide/plugins/#the-docstring","text":"This should consist of a number of lines describing the function followed by a number of @param lines, one for each parameter. These contain the following, separated by colons: The string `@param' A Datum type name - these can be found in the constructors of datum type objects in datumtypes.py , but the most common are number , img , roi , string . A description of the parameter","title":"The docstring"},{"location":"devguide/plugins/#optional-numericstring-arguments","text":"Optional arguments with defaults can be provided, but only if they are numeric or strings (because these are the only types which make sense for the default values). In this case the defaults will be converted to Datum objects if they are used. Here is an example of a function which multiplies an image by a constant, with the default being 2: @datumfunc def example_func(img, k=2): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param img:img:the image @param k:number:the multiplier \"\"\" # no need to construct a Datum with Datum.k(), because k is already # a Datum. return img * k Here's another example which adds two numbers or multiplies them, depending on a string - and the default is to add: @datumfunc def stringexample(a, b, op='add'): \"\"\" String argument example @param a: number: first number @param b: number: second number @param op: string: operation to perform \"\"\" if op.get(Datum.STRING) == 'add': return a + b elif op.get(Datum.STRING) == 'sub': return a - b else: raise ValueError(\"Unknown operation\") Note that you usually have to extract the actual value from the Datum objects, as we do with the op argument above. In previous examples, we take advantage of Datum's extensive operator overloading.","title":"Optional numeric/string arguments"},{"location":"devguide/plugins/#variadic-arguments","text":"For a variable number of arguments, use the *args keyword. Here, you have to check the types by hand. For example, this function will sum numbers: @datumfunc def sumall(*args): \"\"\" Sum all arguments \"\"\" s = sum([x.get(Datum.NUMBER).n for x in args]) return Datum(Datum.NUMBER, Value(s, 0, NOUNCERTAINTY), nullSourceSet) Note the use of Value here to construct a scalar value with standard deviation (zero here) and DQ bits (indicating no uncertainty data). Also note the mandatory use of a source set - just the nullSourceSet here to indicate there is no source; this is just a test function. In a real function we would combine the input sources. For more examples of functions, look at the ExpressionEvaluator constructor in the pcot.expressions.eval module.","title":"Variadic arguments"},{"location":"devguide/plugins/#adding-new-menu-items","text":"This is done by adding a function to a list of functions called when a a main window is opened. Writing code here will require some knowledge of Qt. Here is a menu option added to the File menu which will look for selected node in the document's graph, fetch its first output, and save it as an ENVI file (assuming it is an image - error checking is left as an exercise for the reader). import pcot import os from PySide2 import QtWidgets from PySide2.QtWidgets import QAction, QMessageBox from pcot.dataformats import envi def saveEnvi(w): \"\"\"Function takes a PCOT main window. It finds the first selected node, gets its output 0, and then saves an ENVI from that image.\"\"\" sel = w.doc.getSelection() if len(sel) == 0: ui.log(\"no selected node\") return node = sel[0] directory = os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')) res = QtWidgets.QFileDialog.getSaveFileName(w, \"ENVI file \", os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')), \"ENVI files (*.hdr)\") if res[0] != '': # get the output of that node (root, ext) = os.path.splitext(res[0]) img = node.getOutput(0, pcot.datum.Datum.IMG) envi.write(root, img) # the function to add the new menu item. This will take a single parameter: # the window to which the menu should be added. def addMenus(w): \"\"\"Add an item to the Edit menu\"\"\" # create the menu action (i.e. the item) act = QAction(\"save to ENVI\",parent=w) # find (or create) the Edit menu and add the action to it w.findOrAddMenu(\"Edit\").addAction(act) # link the action to the saveEnvi function, using a closure to # make sure the window argument is passed into that function. act.triggered.connect(lambda: saveEnvi(w)) # Add the addMenus functions to the list of functions called as the # main window opens. pcot.config.addMainWindowHook(addMenus)","title":"Adding new menu items"},{"location":"devguide/plugins/#adding-new-node-types","text":"Node types are represented by singletons of subclasses of XFormType (Nodes themselves are of type XForm , which stands for transform for historical reasons). Developing new XFormType subclasses is largely beyond the scope of this document, but you can learn a lot from looking at the pcot.xforms package and the modules therein. To create a new node type, declare a new subclass of XFormType and decorate it with the @xformtype decorator. This will make the type autoregister: the singleton will be constructed and added to the internal type registry. You will need to write the following methods in your subclass: __init__(self) to construct the type object (NOT the individual nodes). This will call the superconstructor to set the type's name, group (for the palette), and version. It will add the input and output connectors. init(self, node) will initialise any private data in the node itself (which is an XForm ). Don't confuse this with __init__ ! createTab(self, node, window) will create a new node area (i.e. tab). Often, this can be a TabData which will look at the node's out attribute, which should be a Datum. perform(self, node) will actually perform the node's action, reading inputs and setting outputs. Remember: there is only one XFormType object for each node type. All nodes are of type XForm , and they link to an XFormType object to tell them how to behave. This might seem a really odd way to do things, but it follows \"favour composition over inheritance\" and saves messiness elsewhere. Here is an example which does edge detection with OpenCV: import cv2 as cv import numpy as np from pcot.sources import SourceSet from pcot.xform import XFormType, xformtype, Datum from pcot.xforms.tabdata import TabData from pcot.imagecube import ImageCube import pcot.config # The first part of the plugin creates a new type of node. # this decorator will cause the node to auto-register. @xformtype class XFormEdgeDetect(XFormType): \"\"\"This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab.\"\"\" def __init__(self): # this node should appear in the maths group. super().__init__(\"edge\", \"maths\", \"0.0.0\") # set input and output - they are images and are unnamed. self.addInputConnector(\"\", Datum.IMG) self.addOutputConnector(\"\", Datum.IMG) def createTab(self, n, w): # there is no custom tab, we just use a data canvas. This expects \"node.out\" to be set to # either None or a Datum. return TabData(n, w) def init(self, n): # No initialisation required. pass def perform(self, node): # get the input image img = node.getInput(0, Datum.IMG) if img is not None: # find mean of all channels - construct a transform array and then use it. mat = np.array([1 / img.channels] * img.channels).reshape((1, img.channels)) grey = cv.transform(img.img, mat) # convert to 8-bit integer from 32-bit float img8 = (grey * 255).astype('uint8') # Perform edge detection out = cv.Canny(img8, 100, 200) # Convert back to 32-bit float out = (out / 255).astype('float32') # create the imagecube and set node.out for the canvas in the tab img = ImageCube(out, None, img.sources) node.out = Datum(Datum.IMG, img) else: # no image on the input, set node.out to None node.out = Datum.null # output node.out node.setOutput(0, node.out)","title":"Adding new node types"},{"location":"devguide/plugins/#writing-custom-tabs","text":"As noted above, a new XFormType subclass (i.e. a new node type) can often just use TabData , which will display the Datum stored on its first output (output 0). Sometimes, however, a custom tab needs to be written. This can be a complex task, but an example is given in xformexample.py in the xforms package. All the standard XFormTypes are in this package, so you can also look at them. The basic idea is: Create a subclass of pcot.ui.tabs.Tab Write the constructor to call the superclass constructor and create the UI (or load a Designer-created UI by passing an argument to the superclass. constructor), and call self.nodeChanged() at the end to refresh the tab from the node. Override onNodeChanged() to update the tab from the node. Use the Qt signal/slot mechanism to connect the tab's controls to methods in the tab class and write code to update the node from the tab in these methods, calling self.changed() at the end of each method. The tab will have a node field which addresses the node it is viewing (but see below for a \"gotcha\" - the value of this field will change after an undo operation!)","title":"Writing custom Tabs"},{"location":"devguide/plugins/#using-canvas-in-custom-tabs","text":"Creating a Canvas programmatically is straightforward, and there is an example of this in xformexample.py . If you are creating a tab in Designer, you need to add a canvas as a QWidget which you then \"promote\" to a custom control (the canvas). In the promote dialog, the class should be Canvas and the header file pcot.ui.canvas (i.e. the package name). In your onNodeChanged() method you will need to update the canvas. This involves doing a little setup, then getting the image we want to display - usually the output - and telling the canvas to display it: # do some setup self.canvas.setNode(self.node) # then display the image img = self.node.getOutput(0, Datum.IMG) self.canvas.display(img) The setup synchronises the canvas with the node, telling the canvas about the RGB mappings and that it should store data in the node for serialisation. We have to set that up each time because of how undo works, which is discussed in the next section.","title":"Using Canvas in custom tabs"},{"location":"devguide/plugins/#undo-and-references-to-data-in-nodes","text":"This is a major \"gotcha.\" Whenever an undo occurs, the old node is discarded and a new node created from a previously archived version in memory. This means that the node field changes. Because of this, your tab must not store references to objects inside the node, because after an undo those references will be stale. Instead, always use self.node... to access data. It is OK to use the tab to store UI-only data which is not persisted (saved to a file or to the undo mechanism).","title":"Undo and references to data in nodes"},{"location":"devguide/types/","text":"Notes on types Built-in types of data (i.e. of Datum objects) are kept in datum.py . A type specifies: its name whether it is an image subtype (it's tricky to write a method for this, since ImgType is defined after Type) whether it is an \"internal type\" used in the expression evaluator and not for connections (e.g. IdentType, FuncType and NoneType) optional serialisation and deserialisation methods taking and returning Datum, which convert to and from JSON-serialisable values - i.e. primitive types, tuples, lists and dicts; no objects. The Datum class has a type list, which contains singletons of all the type objects. To register a type: create a type object append to the Datum types list if required, register a connector brush with connbrushes.register. Deal with binary and unary operators in expr expressions if required (see below). An example This code is taken from the example1.py plugin file. This (in part) declares a function which takes two numbers, and produces an object containing the dividend and remainder of those numbers. These are held in a custom object: # Our data will be an object of class TestObject. class TestObject: def __init__(self, div, rem): self.div = div self.rem = rem def __str__(self): return f\"({self.div}, {self.rem})\" Now we need to provide the type singleton: # now the type singleton, which controls how Datum objects which hold TestObjects # serialise and deserialise themselves (turn themselves into JSON-serialisable # data and back). # I'm naming the class with an underscore - the type object will be without this. class _TestObjectType(Type): def __init__(self): # just call the superconstructor telling it the name of the type # and in this case, that the stringification (the result of __str__ on the # value) is short enough to fit into an expr node's graph box. super().__init__('testtuple', outputStringShort=True) # now we have to write code which converts Datums of this type into # stuff which can be converted to JSON and back again. Converting # into JSON-serialisable is termed \"serialisation\" and reconstructing # the original Datum object and all its data is \"deserialisation\". def serialise(self, d): # how to serialise data of this type: serialise() methods must return # a tuple of typename and contents. # The contents must be JSON-serialisable, and must contain both the # data to be saved and the serialised source information. # First convert TestObject to something we can serialise serialisedObject = d.val.div, d.val.rem # and create the serialised datum of the name and contents return self.name, (serialisedObject, d.getSources().serialise()) def deserialise(self, d, document): # given a serialised tuple generated by serialise(), produce a Datum # of this type. serialisedObject, serialisedSources = d # first generate the contents # deserialise the serialised sources data sources = SourceSet.deserialise(serialisedSources, document) # then pass to the datum constructor along with the type singleton. return Datum(self, serialisedObject, sources) We register the type singleton, but keep a reference to the object so we can use it in our own code when we create Datum objects. We also provide a connector brush, so that connections of this type are rendered differently in the graph: # create the singleton and register it, but keep hold of the variable so # we can use it to create new Datum objects. TestObjectType = _TestObjectType() Datum.registerType(TestObjectType) # add a brush for the connections in the graph pcot.connbrushes.register(TestObjectType, QColor(\"darkMagenta\")) See example1.py for how this new type is used. Operators Work in progress - read the Value documentation to see how this works in detail, particularly with numeric and image data (i.e. data which can be expressed as (mean,sd,DQ) triples). Previously operators were entirely hardwired in utils.ops. This stopped us creating new types. We could define something like binop(self,other) in the type classes, but this wouldn't allow us to add new types as the RHS for operations which have built-in types as the LHS. The Simplest Thing That Can Possibly Work is a dictionary of operation functions keyed (in the case of binops) by a tuple of types. So this is how operators work now, relying on two registration processes. The first is the registration of the operator lexeme (e.g. \"*\" or \"+\") and precedence, and an associated function to call. This happens as part of Parser. The function calls a binop() function in the ops module, passing in the operator ID. The second is the registration of operator ID (e.g. Operator.ADD) and types in the ops module, with an associated function to call. This is often a wrapper function around a lambda: the wrapper knows to unpack (say) image and number data, and the lambda says they should be processed with addition. Adding a new type with operator semantics Create a subclass of datum.Type add serialisation methods if required call Datum.registerType() with the type If required, add a new connector brush with connbrushes.register() To use the type, use the Type object with the Datum constructor and Datum.get() method. Adding operator semantics call ops.registerBinop and ops.registerUnop to register functions to perform the required operations. The function should take Datum objects and return a Datum.","title":"Notes on types"},{"location":"devguide/types/#notes-on-types","text":"Built-in types of data (i.e. of Datum objects) are kept in datum.py . A type specifies: its name whether it is an image subtype (it's tricky to write a method for this, since ImgType is defined after Type) whether it is an \"internal type\" used in the expression evaluator and not for connections (e.g. IdentType, FuncType and NoneType) optional serialisation and deserialisation methods taking and returning Datum, which convert to and from JSON-serialisable values - i.e. primitive types, tuples, lists and dicts; no objects. The Datum class has a type list, which contains singletons of all the type objects. To register a type: create a type object append to the Datum types list if required, register a connector brush with connbrushes.register. Deal with binary and unary operators in expr expressions if required (see below).","title":"Notes on types"},{"location":"devguide/types/#an-example","text":"This code is taken from the example1.py plugin file. This (in part) declares a function which takes two numbers, and produces an object containing the dividend and remainder of those numbers. These are held in a custom object: # Our data will be an object of class TestObject. class TestObject: def __init__(self, div, rem): self.div = div self.rem = rem def __str__(self): return f\"({self.div}, {self.rem})\" Now we need to provide the type singleton: # now the type singleton, which controls how Datum objects which hold TestObjects # serialise and deserialise themselves (turn themselves into JSON-serialisable # data and back). # I'm naming the class with an underscore - the type object will be without this. class _TestObjectType(Type): def __init__(self): # just call the superconstructor telling it the name of the type # and in this case, that the stringification (the result of __str__ on the # value) is short enough to fit into an expr node's graph box. super().__init__('testtuple', outputStringShort=True) # now we have to write code which converts Datums of this type into # stuff which can be converted to JSON and back again. Converting # into JSON-serialisable is termed \"serialisation\" and reconstructing # the original Datum object and all its data is \"deserialisation\". def serialise(self, d): # how to serialise data of this type: serialise() methods must return # a tuple of typename and contents. # The contents must be JSON-serialisable, and must contain both the # data to be saved and the serialised source information. # First convert TestObject to something we can serialise serialisedObject = d.val.div, d.val.rem # and create the serialised datum of the name and contents return self.name, (serialisedObject, d.getSources().serialise()) def deserialise(self, d, document): # given a serialised tuple generated by serialise(), produce a Datum # of this type. serialisedObject, serialisedSources = d # first generate the contents # deserialise the serialised sources data sources = SourceSet.deserialise(serialisedSources, document) # then pass to the datum constructor along with the type singleton. return Datum(self, serialisedObject, sources) We register the type singleton, but keep a reference to the object so we can use it in our own code when we create Datum objects. We also provide a connector brush, so that connections of this type are rendered differently in the graph: # create the singleton and register it, but keep hold of the variable so # we can use it to create new Datum objects. TestObjectType = _TestObjectType() Datum.registerType(TestObjectType) # add a brush for the connections in the graph pcot.connbrushes.register(TestObjectType, QColor(\"darkMagenta\")) See example1.py for how this new type is used.","title":"An example"},{"location":"devguide/types/#operators","text":"Work in progress - read the Value documentation to see how this works in detail, particularly with numeric and image data (i.e. data which can be expressed as (mean,sd,DQ) triples). Previously operators were entirely hardwired in utils.ops. This stopped us creating new types. We could define something like binop(self,other) in the type classes, but this wouldn't allow us to add new types as the RHS for operations which have built-in types as the LHS. The Simplest Thing That Can Possibly Work is a dictionary of operation functions keyed (in the case of binops) by a tuple of types. So this is how operators work now, relying on two registration processes. The first is the registration of the operator lexeme (e.g. \"*\" or \"+\") and precedence, and an associated function to call. This happens as part of Parser. The function calls a binop() function in the ops module, passing in the operator ID. The second is the registration of operator ID (e.g. Operator.ADD) and types in the ops module, with an associated function to call. This is often a wrapper function around a lambda: the wrapper knows to unpack (say) image and number data, and the lambda says they should be processed with addition.","title":"Operators"},{"location":"devguide/types/#adding-a-new-type-with-operator-semantics","text":"Create a subclass of datum.Type add serialisation methods if required call Datum.registerType() with the type If required, add a new connector brush with connbrushes.register() To use the type, use the Type object with the Datum constructor and Datum.get() method. Adding operator semantics call ops.registerBinop and ops.registerUnop to register functions to perform the required operations. The function should take Datum objects and return a Datum.","title":"Adding a new type with operator semantics"},{"location":"devguide/values/","text":"How Values work Value is PCOT's fundamental type for working with uncertain numerical data. Values are triplets of: a 32-bit floating point nominal value (i.e. a mean), a 32-bit floating point uncertainty value (as standard deviation) around that mean, and a set of 16 data quality (DQ) bits. As a user of PCOT you may never encounter Values, but they are used internally whenever any operations involving uncertainty are done. Here are the typical situations where that occurs: Binary and unary operations on Value objects This is the \"base case\". Values have dunder methods for operations. These are usually quite simple, although some (such as exponentiation) are nasty. They: perform the operation to get the new nominal value. This is usually done with a lambda function, relying on Numpy's broadcasting to do the \"right thing\" with what it's given. call a function to calculate the new uncertainty value call a function to combine the two DQs pass the three results into Value's constructor to get a new value Examples - the multiplication operator: def __mul__(self, other): return Value(self.n * other.n, mul_unc(self.n, self.u, other.n, other.u), combineDQs(self, other)) The AND operator: def __and__(self, other): \"\"\"The & operator actually finds the minimum (Zadeh fuzzy op)\"\"\" n = np.where(self.n > other.n, other.n, self.n) u = np.where(self.n > other.n, other.u, self.u) d = np.where(self.n > other.n, other.dq, self.dq) return Value(n, u, d) Binary and unary operations on Datum objects This is for binops - unary operations are much the same, but a lot simpler (although imageUnop currently takes the underlying numpy array). Datum dunder function runs - this is usually very simple. For example, for addition it's just return ops.binop(ops.Operator.ADD, self, other) . Calls pcot.expressions.ops.binop with the appopriate ops.Operator code ops.binop runs converting any raw numbers to Datum.NUMBER data with zero uncertainty. Each possible triple (operator, leftdatum, rightdatum) of operator and Datum types will have a \"binop semantics Datum wrapper\" method registered for it by ops.initOps. For example, (multiplication, Datum.IMG, Datum.NUMBER) will call ops.imageNumberBinop with the lambda lambda x,y: x*y where the latter will take and return Value. ops.binop calls the binop semantics method for the two Datum types For pairings of numbers and/or images, The binop semantics Datum wrapper converts any image data into a Value after performing subimage extraction, then calls the provided lambda. This calls the appropriate dunder method on the two Values. In fact, we could replace lambda x,y: x*y with Value.__mul__ etc., but it's much clearer this way. see above for details of this. In imageNumberBinop (or equivalent) the returned array Value will be stitched back into the left-hand image using ImageCube.modifyWithSub , and that new image will be returned wrapped in a Datum. If the semantic wrapper was numberBinop, so that a number is returned, we just wrap that in a Datum. For other types - not Datum.NUMBER or Datum.IMG - other semantics methods will have been written. For example, the ROI types have binary operators which construct ROIBinop objects using dunder methods: def regROIBinopSemantics(op, fn): \"\"\"Used to register binops for ROIs, which support a subset of ops.\"\"\" registerBinopSemantics(op, Datum.ROI, Datum.ROI, lambda dx, dy: ROIBinop(dx, dy, fn)) regROIBinopSemantics(Operator.ADD, lambda x, y: x + y) regROIBinopSemantics(Operator.SUB, lambda x, y: x - y) regROIBinopSemantics(Operator.MUL, lambda x, y: x * y) regROIBinopSemantics(Operator.DIV, lambda x, y: x / y) regROIBinopSemantics(Operator.POW, lambda x, y: x ** y) Binary and unary operations in an expr node pcot.expressions.parse.InstOp runs, popping two Datum objects and calling a callback function stored in binopRegistry. This callback will have been registered by Parser.registerBinop, and is simply a lambda calling pcot.expressions.ops.binop with an Operator code for the operator, e.g. p.registerBinop('*', 20, lambda a, b: binop(Operator.MUL, a, b)) (20 is the precedence) The callback will call ops.binop, which is step 3 in the preceding section. Again, unary operations are very similar but rather simpler. functions of Value objects Value generally has a method for each supported function. For example, the tan method will perform the trigonometric tan with uncertainty, returning a new Value. This is rarely called directly - instead, we generally work with Datum objects. datumfuncs: functions of Datum objects These are registered with the @datumfunc operator (see the plugins documentation ) which can be found in pcot.expressions.register. This registers two separate wrappers: the expression wrapper, used when the function is called from an expr node; and the Python function wrapper, called when the function is called from inside Python. They can take any number of arguments, but they must be either Datum objects or numeric. Datumfuncs called from Python The @datumfunc decorator will have wrapped the function in the Python decorator, so that subsequent calls from Python will go through the wrapper. This wrapper will set default values for missing arguments (which must be string or numeric) convert numeric and string arguments to Datum objects call the original function and return the result (which must be Datum) Datumfuncs called from expr nodes The @datumfunc decorator also wraps the function in another wrapper - the exprwrapper - and registers the wrapped function with the expression parser. The wrapper itself is simpler than the Python wrapper because it can make use of facilities in the parser to convert values and check for errors. When data reaches the exprwrapper, we already know that the arguments are all Datum objects. Datumfuncs of single numeric/scalar arguments Functions of a single numeric or image Datum are sometimes written using an \"inner wrapper\", which will turn imagecubes and numbers into Value objects in a similar way to how the semantic binop wrappers work for operations. It will also wrap the resulting Value in a Datum. An example is pcot.expression.register.funcWrapper . Here is the datumfunc sin in full: @datumfunc def sin(a): \"\"\" Calculate sine of an angle in radians @param a:img,number:the angle (or image in which each pixel is a single angle) \"\"\" return funcWrapper(lambda xx: xx.sin(), a) We don't need worry about whether the argument is an image or a scalar, because the funcWrapper will deal with it. However, funcWrapper can only deal with images and scalars. The stats wrapper Another datumfunc inner wrapper which can be used is statsWrapper. This takes a variable number of image and numeric arguments which it aggregates into a single 1D array, on which it then runs its function. It's typically for taking the mean or sum of images and/or values I don't like how this works at the moment; the results are often meaningless or prone to misinterpretation. Finding the mean of a multiband image should result in a vector of values, ideally.","title":"How Values work"},{"location":"devguide/values/#how-values-work","text":"Value is PCOT's fundamental type for working with uncertain numerical data. Values are triplets of: a 32-bit floating point nominal value (i.e. a mean), a 32-bit floating point uncertainty value (as standard deviation) around that mean, and a set of 16 data quality (DQ) bits. As a user of PCOT you may never encounter Values, but they are used internally whenever any operations involving uncertainty are done. Here are the typical situations where that occurs:","title":"How Values work"},{"location":"devguide/values/#binary-and-unary-operations-on-value-objects","text":"This is the \"base case\". Values have dunder methods for operations. These are usually quite simple, although some (such as exponentiation) are nasty. They: perform the operation to get the new nominal value. This is usually done with a lambda function, relying on Numpy's broadcasting to do the \"right thing\" with what it's given. call a function to calculate the new uncertainty value call a function to combine the two DQs pass the three results into Value's constructor to get a new value Examples - the multiplication operator: def __mul__(self, other): return Value(self.n * other.n, mul_unc(self.n, self.u, other.n, other.u), combineDQs(self, other)) The AND operator: def __and__(self, other): \"\"\"The & operator actually finds the minimum (Zadeh fuzzy op)\"\"\" n = np.where(self.n > other.n, other.n, self.n) u = np.where(self.n > other.n, other.u, self.u) d = np.where(self.n > other.n, other.dq, self.dq) return Value(n, u, d)","title":"Binary and unary operations on Value objects"},{"location":"devguide/values/#binary-and-unary-operations-on-datum-objects","text":"This is for binops - unary operations are much the same, but a lot simpler (although imageUnop currently takes the underlying numpy array). Datum dunder function runs - this is usually very simple. For example, for addition it's just return ops.binop(ops.Operator.ADD, self, other) . Calls pcot.expressions.ops.binop with the appopriate ops.Operator code ops.binop runs converting any raw numbers to Datum.NUMBER data with zero uncertainty. Each possible triple (operator, leftdatum, rightdatum) of operator and Datum types will have a \"binop semantics Datum wrapper\" method registered for it by ops.initOps. For example, (multiplication, Datum.IMG, Datum.NUMBER) will call ops.imageNumberBinop with the lambda lambda x,y: x*y where the latter will take and return Value. ops.binop calls the binop semantics method for the two Datum types For pairings of numbers and/or images, The binop semantics Datum wrapper converts any image data into a Value after performing subimage extraction, then calls the provided lambda. This calls the appropriate dunder method on the two Values. In fact, we could replace lambda x,y: x*y with Value.__mul__ etc., but it's much clearer this way. see above for details of this. In imageNumberBinop (or equivalent) the returned array Value will be stitched back into the left-hand image using ImageCube.modifyWithSub , and that new image will be returned wrapped in a Datum. If the semantic wrapper was numberBinop, so that a number is returned, we just wrap that in a Datum. For other types - not Datum.NUMBER or Datum.IMG - other semantics methods will have been written. For example, the ROI types have binary operators which construct ROIBinop objects using dunder methods: def regROIBinopSemantics(op, fn): \"\"\"Used to register binops for ROIs, which support a subset of ops.\"\"\" registerBinopSemantics(op, Datum.ROI, Datum.ROI, lambda dx, dy: ROIBinop(dx, dy, fn)) regROIBinopSemantics(Operator.ADD, lambda x, y: x + y) regROIBinopSemantics(Operator.SUB, lambda x, y: x - y) regROIBinopSemantics(Operator.MUL, lambda x, y: x * y) regROIBinopSemantics(Operator.DIV, lambda x, y: x / y) regROIBinopSemantics(Operator.POW, lambda x, y: x ** y)","title":"Binary and unary operations on Datum objects"},{"location":"devguide/values/#binary-and-unary-operations-in-an-expr-node","text":"pcot.expressions.parse.InstOp runs, popping two Datum objects and calling a callback function stored in binopRegistry. This callback will have been registered by Parser.registerBinop, and is simply a lambda calling pcot.expressions.ops.binop with an Operator code for the operator, e.g. p.registerBinop('*', 20, lambda a, b: binop(Operator.MUL, a, b)) (20 is the precedence) The callback will call ops.binop, which is step 3 in the preceding section. Again, unary operations are very similar but rather simpler.","title":"Binary and unary operations in an expr node"},{"location":"devguide/values/#functions-of-value-objects","text":"Value generally has a method for each supported function. For example, the tan method will perform the trigonometric tan with uncertainty, returning a new Value. This is rarely called directly - instead, we generally work with Datum objects.","title":"functions of Value objects"},{"location":"devguide/values/#datumfuncs-functions-of-datum-objects","text":"These are registered with the @datumfunc operator (see the plugins documentation ) which can be found in pcot.expressions.register. This registers two separate wrappers: the expression wrapper, used when the function is called from an expr node; and the Python function wrapper, called when the function is called from inside Python. They can take any number of arguments, but they must be either Datum objects or numeric.","title":"datumfuncs: functions of Datum objects"},{"location":"devguide/values/#datumfuncs-called-from-python","text":"The @datumfunc decorator will have wrapped the function in the Python decorator, so that subsequent calls from Python will go through the wrapper. This wrapper will set default values for missing arguments (which must be string or numeric) convert numeric and string arguments to Datum objects call the original function and return the result (which must be Datum)","title":"Datumfuncs called from Python"},{"location":"devguide/values/#datumfuncs-called-from-expr-nodes","text":"The @datumfunc decorator also wraps the function in another wrapper - the exprwrapper - and registers the wrapped function with the expression parser. The wrapper itself is simpler than the Python wrapper because it can make use of facilities in the parser to convert values and check for errors. When data reaches the exprwrapper, we already know that the arguments are all Datum objects.","title":"Datumfuncs called from expr nodes"},{"location":"devguide/values/#datumfuncs-of-single-numericscalar-arguments","text":"Functions of a single numeric or image Datum are sometimes written using an \"inner wrapper\", which will turn imagecubes and numbers into Value objects in a similar way to how the semantic binop wrappers work for operations. It will also wrap the resulting Value in a Datum. An example is pcot.expression.register.funcWrapper . Here is the datumfunc sin in full: @datumfunc def sin(a): \"\"\" Calculate sine of an angle in radians @param a:img,number:the angle (or image in which each pixel is a single angle) \"\"\" return funcWrapper(lambda xx: xx.sin(), a) We don't need worry about whether the argument is an image or a scalar, because the funcWrapper will deal with it. However, funcWrapper can only deal with images and scalars.","title":"Datumfuncs of single numeric/scalar arguments"},{"location":"devguide/values/#the-stats-wrapper","text":"Another datumfunc inner wrapper which can be used is statsWrapper. This takes a variable number of image and numeric arguments which it aggregates into a single 1D array, on which it then runs its function. It's typically for taking the mean or sum of images and/or values I don't like how this works at the moment; the results are often meaningless or prone to misinterpretation. Finding the mean of a multiband image should result in a vector of values, ideally.","title":"The stats wrapper"},{"location":"gettingstarted/","text":"Overview This provides a basic introduction to PCOT, telling you how to install it, providing important information on the core concepts behind PCOT, and giving a basic tutorial. Once you have tinkered with PCOT a little the User Guide will provide reference information. How to install and run PCOT Introduction to core PCOT concepts A first tutorial Video introduction There is a brief video introduction to PCOT.","title":"Overview"},{"location":"gettingstarted/#overview","text":"This provides a basic introduction to PCOT, telling you how to install it, providing important information on the core concepts behind PCOT, and giving a basic tutorial. Once you have tinkered with PCOT a little the User Guide will provide reference information. How to install and run PCOT Introduction to core PCOT concepts A first tutorial","title":"Overview"},{"location":"gettingstarted/#video-introduction","text":"There is a brief video introduction to PCOT.","title":"Video introduction"},{"location":"gettingstarted/concepts/","text":"Concepts How PCOT works (and why) PCOT was originally designed to help scientists and engineers analyse PanCam data and produce useful secondary data. It acts downstream from the Rover Operations Control Centre (ROCC) on images which have already been processed to some extent, and is a successor to ExoSpec 1 . As such, its primary purpose is to generate relative reflectance images and spectral parameter maps, although it will also be able to produce spectra from regions of interest. Indeed, it should be flexible enough to perform a wide range of unforeseen calculations. PCOT can also handle many other kinds of data. It is particularly suited to processing multispectral images with uncertainty and error data, and can currently read PDS4 and ENVI formats, alongside more common RGB formats which can be collated into multispectral images. Of paramount importance is the verifiability and reproducibility of data generated from PCOT. To this end, a PCOT document is a data product which can be shared between users, which also fully describes how the data was generated from the initial import to the final output. Users are encouraged to exchange PCOT documents in addition to, or instead of, the generated images or data. The Graph To achieve this, a PCOT document manipulates data in a graph - a network of nodes, each of which takes some data, works on it, perhaps displays some data, and perhaps outputs derived data. Technically speaking, this is a \"directed acyclic graph\": each connection has a direction, going from the output of one node to the input of another, and there can't be any loops. As an example, consider that we might want to overlay some kind of spectral parameter map, converted to a colour gradient, over an RGB image (note: I'm not a geologist, I'm a software engineer, so perhaps this is a very artificial example). One way to do it might be this: Figure: An example graph. Click on image to expand. We can see the graph in the panel on the right, showing each node as a box with connections to other nodes (ignore the green numbers, they just show how many times each node has run - it's a debugging aid!) Here's what each node in the graph is doing: The input 0 node reads input number 0 into the graph. The inputs are set up separately from the graph, and can be multispectral images or other data (e.g. housekeeping) from outside PCOT. The rect node lets the user draw a rectangle on the image to define a region of interest. Images can have many regions of interest and several different kinds are available. The node with 4 inputs a,b,c,d is an expr node, which calculates the result of a mathematical expression performed on each pixel. The node is showing the expression it is running: a$671 / a$438 . This will read the bands whose wavelengths are 671nm and 438nm in the node's a input, and find their ratio for every pixel. The result will be a single-band image. Expr nodes can perform much more complex calculations than this. The gradient node will convert a single-band image into an RGB image with a user-defined gradient and inset it into the RGB representation of another image - here we are insetting into the input image, using the RGB representation used by that node. Here is another example, showing a spectral plot: The input node again brings a multispectral image into the graph. The multidot node adds a number of small, circular regions of interest. Each has a different name and colour, in this case set automatically to just numbers and random colours. Creating the regions is as easy as clicking on the image. The spectrum node plots a spectrogram of the regions present in the image for all the wavelengths in that image. Figure: Spectrogram example. Click on image to expand. Here I have \"undocked\" the spectrum node's view to be a separate window for easy viewing. The spectrum can also be saved as a PDF or converted into CSV data. I'm also showing the entire app, including the menu bar and four input buttons. The Document A PCOT document is a file which can be shared among users. It consists of The inputs - data loaded from sources external to PCOT. These are kept separate from the graph, because you might want to use a different graph on the same inputs, or the load the same inputs into a different graph. There are currently up to four inputs, but this can easily be changed. The graph - a set of nodes and connections between them which define operations to be performed on inputs, as shown above. The settings - these are global to the entire application. Macros - these are sets of nodes which can be used multiple times and appear as single nodes in the graph, although each one has its own \"private\" graph. Currently very experimental (and largely undocumented). Important The data being sent out of the inputs into the graph is saved and loaded with the document, so the original source data does not need to be stored - you can send the document to someone and it will still work even if they don't have the sources. Quantities All numerical quantities in PCOT - whether scalar values or pixels in images - consist of three values: nominal value, uncertainty and data quality (DQ) bits. Uncertainty and DQ bitss can be viewed as overlays in the canvas (PCOT's image viewer component). Uncertainty The uncertainty is expressed as standard deviation, and is propagated through most operations. Be careful here, however - all values are assumed to be independent. This can lead to grossly incorrect results . For example, we could set up a graph consisting of a node with the value 0\\pm1 0\\pm1 , feed it into a mathematical expression ( expr ) node as variable *a), and set the node's expression to a-a a-a : Figure: Incorrect uncertainty calculation. Click on image to expand. The result, 0\\pm\\sqrt{2} 0\\pm\\sqrt{2} , is a consequence of the subtraction assuming that all quantities are independent even when they are clearly the same quantity. Data quality bits Each pixel in an image has a set of bits describing its quality in the source data, which are propagated through all operations. Additionally, other DQ bits may be added as the data moves through the graph - for example, division by zero may occur, or an operation whose result is undefined for those values. While DQ bits are flexible, the following are probably stable: Bit Meaning NODATA no data present (typically for a particular pixel) NOUNCERTAINTY Pixel has no uncertainty (again typically for pixels) SAT Data is saturated DIVZERO A division by zero has occurred UNDEF Result of operation is undefined COMPLEX Data is real part of complex result ERROR Unspecified error There may also be user-defined or \"test\" bits for use in development. Move on to a First Tutorial Allender, Elyse J., Roger B. Stabbins, Matthew D. Gunn, Claire R. Cousins, and Andrew J. Coates. \"The ExoMars spectral tool (ExoSpec): An image analysis tool for ExoMars 2020 PanCam imagery.\" In Image and Signal Processing for Remote Sensing XXIV , vol. 10789, pp. 163-181. SPIE, 2018. link to PDF \u21a9","title":"PCOT concepts"},{"location":"gettingstarted/concepts/#concepts","text":"How PCOT works (and why) PCOT was originally designed to help scientists and engineers analyse PanCam data and produce useful secondary data. It acts downstream from the Rover Operations Control Centre (ROCC) on images which have already been processed to some extent, and is a successor to ExoSpec 1 . As such, its primary purpose is to generate relative reflectance images and spectral parameter maps, although it will also be able to produce spectra from regions of interest. Indeed, it should be flexible enough to perform a wide range of unforeseen calculations. PCOT can also handle many other kinds of data. It is particularly suited to processing multispectral images with uncertainty and error data, and can currently read PDS4 and ENVI formats, alongside more common RGB formats which can be collated into multispectral images. Of paramount importance is the verifiability and reproducibility of data generated from PCOT. To this end, a PCOT document is a data product which can be shared between users, which also fully describes how the data was generated from the initial import to the final output. Users are encouraged to exchange PCOT documents in addition to, or instead of, the generated images or data.","title":"Concepts"},{"location":"gettingstarted/concepts/#the-graph","text":"To achieve this, a PCOT document manipulates data in a graph - a network of nodes, each of which takes some data, works on it, perhaps displays some data, and perhaps outputs derived data. Technically speaking, this is a \"directed acyclic graph\": each connection has a direction, going from the output of one node to the input of another, and there can't be any loops. As an example, consider that we might want to overlay some kind of spectral parameter map, converted to a colour gradient, over an RGB image (note: I'm not a geologist, I'm a software engineer, so perhaps this is a very artificial example). One way to do it might be this: Figure: An example graph. Click on image to expand. We can see the graph in the panel on the right, showing each node as a box with connections to other nodes (ignore the green numbers, they just show how many times each node has run - it's a debugging aid!) Here's what each node in the graph is doing: The input 0 node reads input number 0 into the graph. The inputs are set up separately from the graph, and can be multispectral images or other data (e.g. housekeeping) from outside PCOT. The rect node lets the user draw a rectangle on the image to define a region of interest. Images can have many regions of interest and several different kinds are available. The node with 4 inputs a,b,c,d is an expr node, which calculates the result of a mathematical expression performed on each pixel. The node is showing the expression it is running: a$671 / a$438 . This will read the bands whose wavelengths are 671nm and 438nm in the node's a input, and find their ratio for every pixel. The result will be a single-band image. Expr nodes can perform much more complex calculations than this. The gradient node will convert a single-band image into an RGB image with a user-defined gradient and inset it into the RGB representation of another image - here we are insetting into the input image, using the RGB representation used by that node. Here is another example, showing a spectral plot: The input node again brings a multispectral image into the graph. The multidot node adds a number of small, circular regions of interest. Each has a different name and colour, in this case set automatically to just numbers and random colours. Creating the regions is as easy as clicking on the image. The spectrum node plots a spectrogram of the regions present in the image for all the wavelengths in that image. Figure: Spectrogram example. Click on image to expand. Here I have \"undocked\" the spectrum node's view to be a separate window for easy viewing. The spectrum can also be saved as a PDF or converted into CSV data. I'm also showing the entire app, including the menu bar and four input buttons.","title":"The Graph"},{"location":"gettingstarted/concepts/#the-document","text":"A PCOT document is a file which can be shared among users. It consists of The inputs - data loaded from sources external to PCOT. These are kept separate from the graph, because you might want to use a different graph on the same inputs, or the load the same inputs into a different graph. There are currently up to four inputs, but this can easily be changed. The graph - a set of nodes and connections between them which define operations to be performed on inputs, as shown above. The settings - these are global to the entire application. Macros - these are sets of nodes which can be used multiple times and appear as single nodes in the graph, although each one has its own \"private\" graph. Currently very experimental (and largely undocumented). Important The data being sent out of the inputs into the graph is saved and loaded with the document, so the original source data does not need to be stored - you can send the document to someone and it will still work even if they don't have the sources.","title":"The Document"},{"location":"gettingstarted/concepts/#quantities","text":"All numerical quantities in PCOT - whether scalar values or pixels in images - consist of three values: nominal value, uncertainty and data quality (DQ) bits. Uncertainty and DQ bitss can be viewed as overlays in the canvas (PCOT's image viewer component).","title":"Quantities"},{"location":"gettingstarted/concepts/#uncertainty","text":"The uncertainty is expressed as standard deviation, and is propagated through most operations. Be careful here, however - all values are assumed to be independent. This can lead to grossly incorrect results . For example, we could set up a graph consisting of a node with the value 0\\pm1 0\\pm1 , feed it into a mathematical expression ( expr ) node as variable *a), and set the node's expression to a-a a-a : Figure: Incorrect uncertainty calculation. Click on image to expand. The result, 0\\pm\\sqrt{2} 0\\pm\\sqrt{2} , is a consequence of the subtraction assuming that all quantities are independent even when they are clearly the same quantity.","title":"Uncertainty"},{"location":"gettingstarted/concepts/#data-quality-bits","text":"Each pixel in an image has a set of bits describing its quality in the source data, which are propagated through all operations. Additionally, other DQ bits may be added as the data moves through the graph - for example, division by zero may occur, or an operation whose result is undefined for those values. While DQ bits are flexible, the following are probably stable: Bit Meaning NODATA no data present (typically for a particular pixel) NOUNCERTAINTY Pixel has no uncertainty (again typically for pixels) SAT Data is saturated DIVZERO A division by zero has occurred UNDEF Result of operation is undefined COMPLEX Data is real part of complex result ERROR Unspecified error There may also be user-defined or \"test\" bits for use in development. Move on to a First Tutorial Allender, Elyse J., Roger B. Stabbins, Matthew D. Gunn, Claire R. Cousins, and Andrew J. Coates. \"The ExoMars spectral tool (ExoSpec): An image analysis tool for ExoMars 2020 PanCam imagery.\" In Image and Signal Processing for Remote Sensing XXIV , vol. 10789, pp. 163-181. SPIE, 2018. link to PDF \u21a9","title":"Data quality bits"},{"location":"gettingstarted/help/","text":"Getting help Nodes Inside the PCOT app there are three ways to get help on a node: Double-clicking on the little blue box in the top right corner of that node in the graph, Using the right-click context menu on a node, Using the right-click context menu on a button in the palette. The node help texts are also available in the automatically generated documentation . If this isn't enough, don't hesitate to contact the Aberystwyth team. Expressions Doing the above on an expr node will tell you what operations, functions and properties are available. This text is also available in the automatically generated documentation for this node . You can also get help on properties and functions by right clicking in the log box at the bottom and selecting the appropriate option. Inside the expression box in the expr node, you can right-click on most things and ask for help.","title":"Getting help"},{"location":"gettingstarted/help/#getting-help","text":"","title":"Getting help"},{"location":"gettingstarted/help/#nodes","text":"Inside the PCOT app there are three ways to get help on a node: Double-clicking on the little blue box in the top right corner of that node in the graph, Using the right-click context menu on a node, Using the right-click context menu on a button in the palette. The node help texts are also available in the automatically generated documentation . If this isn't enough, don't hesitate to contact the Aberystwyth team.","title":"Nodes"},{"location":"gettingstarted/help/#expressions","text":"Doing the above on an expr node will tell you what operations, functions and properties are available. This text is also available in the automatically generated documentation for this node . You can also get help on properties and functions by right clicking in the log box at the bottom and selecting the appropriate option. Inside the expression box in the expr node, you can right-click on most things and ask for help.","title":"Expressions"},{"location":"gettingstarted/inputs/","text":"Loading different image formats The examples in the tutorial use ENVI images, but other image formats are available: plain RGB \"multiband\" images (multiple monochrome PNG images, one per band) PDS4 products (work in progress) Loading an ENVI image To reiterate, this is how to load an image from an ENVI file: Click an input button to open an input, and select ENVI. Click on \"Select Directory\" and choose a directory with ENVI files. Double click on .hdr files in the main file view. This will load the file into the input. Note that ENVI is a \"two-file\" format - the .dat files contain the data, while .hdr files of the same name in the same directory contain information about the data. We only show the .hdr file in the file view, but the corresponding .dat file must be present too. ENVI files are a useful multispectral format, and we can provide scripts to convert raw image sets into ENVI if required. We currently only support images which are 32-bit floating point in the BSQ (band-sequential) format. Loading an RGB image To load RGB, open an input and click the RGB button. A dialog will appear which is very similar to the ENVI file dialog above , but showing ENVI header files instead of image files. Double-click on such a file to load its associated data, which is assumed to be in the same directory with the same name. RGB images don't have filters, so there is no wavelength information - instead, the channels are named R, G and B and these names can be used in expr nodes (e.g. a$R ). Loading a \"multiband\" image Sometimes data is provided as a set of monochrome PNG files, although this is clearly far from ideal. In this case we need to tell PCOT how to work out which filter is being used for each file. Again, we open the dialog by clicking on an input button and clicking the appropriate method - \"Multifile\" in this case. This will open the ENVI dialog, which is rather more complex than the RGB or ENVI dialogs: Figure: An open Multiband input. Click on image to expand. The procedure here is roughly this: Make sure the file names contain the the lens (left or right) as L or R, and the filter position number. Work out a regular expression which can find these in the file name. Hopefully you can just use the default, which assumes that the filename contains a string like LWAC01 for left lens, position 01; or RWAC10 for right lens, position 10. If your filenames don't have this format and it's too difficult to rename them, you'll have to write an RE yourself or find a handy IT person to help. Get the files into a single directory and open the input dialog as shown above. Determine which camera configuration produced the images (PANCAM or AUPE are supported but more can be added) and set the Camera option accordingly. Different setups use different filter sets, and will translate filter positions into different filter wavelengths and names. Click the \"Get Directory\" button. In the new dialog, select a directory containing the image files and and click \"Choose.\" A lot of files will appear in the Files box. Double-click images to preview them as a single channel. If they are dark, select an appropriate multiplier and double-click again. Click in image checkboxes to select them; selected images will be combined into a single multispectral image. Close the dialog when you have the selected images you want. It might be a good idea to create an input node to examine the resulting multispectral image. There is more detail on the regular expression syntax here . PDS4 products This is very much work in progress - please contact the developers if you need this functionality soon.","title":"Loading other image formats"},{"location":"gettingstarted/inputs/#loading-different-image-formats","text":"The examples in the tutorial use ENVI images, but other image formats are available: plain RGB \"multiband\" images (multiple monochrome PNG images, one per band) PDS4 products (work in progress)","title":"Loading different image formats"},{"location":"gettingstarted/inputs/#loading-an-envi-image","text":"To reiterate, this is how to load an image from an ENVI file: Click an input button to open an input, and select ENVI. Click on \"Select Directory\" and choose a directory with ENVI files. Double click on .hdr files in the main file view. This will load the file into the input. Note that ENVI is a \"two-file\" format - the .dat files contain the data, while .hdr files of the same name in the same directory contain information about the data. We only show the .hdr file in the file view, but the corresponding .dat file must be present too. ENVI files are a useful multispectral format, and we can provide scripts to convert raw image sets into ENVI if required. We currently only support images which are 32-bit floating point in the BSQ (band-sequential) format.","title":"Loading an ENVI image"},{"location":"gettingstarted/inputs/#loading-an-rgb-image","text":"To load RGB, open an input and click the RGB button. A dialog will appear which is very similar to the ENVI file dialog above , but showing ENVI header files instead of image files. Double-click on such a file to load its associated data, which is assumed to be in the same directory with the same name. RGB images don't have filters, so there is no wavelength information - instead, the channels are named R, G and B and these names can be used in expr nodes (e.g. a$R ).","title":"Loading an RGB image"},{"location":"gettingstarted/inputs/#loading-a-multiband-image","text":"Sometimes data is provided as a set of monochrome PNG files, although this is clearly far from ideal. In this case we need to tell PCOT how to work out which filter is being used for each file. Again, we open the dialog by clicking on an input button and clicking the appropriate method - \"Multifile\" in this case. This will open the ENVI dialog, which is rather more complex than the RGB or ENVI dialogs: Figure: An open Multiband input. Click on image to expand. The procedure here is roughly this: Make sure the file names contain the the lens (left or right) as L or R, and the filter position number. Work out a regular expression which can find these in the file name. Hopefully you can just use the default, which assumes that the filename contains a string like LWAC01 for left lens, position 01; or RWAC10 for right lens, position 10. If your filenames don't have this format and it's too difficult to rename them, you'll have to write an RE yourself or find a handy IT person to help. Get the files into a single directory and open the input dialog as shown above. Determine which camera configuration produced the images (PANCAM or AUPE are supported but more can be added) and set the Camera option accordingly. Different setups use different filter sets, and will translate filter positions into different filter wavelengths and names. Click the \"Get Directory\" button. In the new dialog, select a directory containing the image files and and click \"Choose.\" A lot of files will appear in the Files box. Double-click images to preview them as a single channel. If they are dark, select an appropriate multiplier and double-click again. Click in image checkboxes to select them; selected images will be combined into a single multispectral image. Close the dialog when you have the selected images you want. It might be a good idea to create an input node to examine the resulting multispectral image. There is more detail on the regular expression syntax here .","title":"Loading a \"multiband\" image"},{"location":"gettingstarted/inputs/#pds4-products","text":"This is very much work in progress - please contact the developers if you need this functionality soon.","title":"PDS4 products"},{"location":"gettingstarted/installrun/","text":"Installing and running PCOT PCOT is a Python program (and library) with a number of dependencies, notably numpy and PySide2 (the official Python interface to Qt). We find the best way to manage these is to use Anaconda and Poetry . Installation has been tested on Windows 10, MacOS and Ubuntu 20.04. Install Anaconda The first thing you will need to do is install Anaconda, which can be done from here: Windows: https://docs.anaconda.com/anaconda/install/windows/ Linux: https://docs.anaconda.com/anaconda/install/linux/ MacOS: https://docs.anaconda.com/anaconda/install/mac-os/ (untested) Obtain the software This can be done by either downloading the archive from Github and extracting it into a new directory, or cloning the repository. In both cases, the top level directory should be called PCOT (this isn't really mandatory but makes the instructions below simpler). The best way to download is this: Open an Anaconda shell window (see below) If you have an SSH key set up for GitHub, type this command into the shell ( changing the repository address if it is different ): git clone git@github.com:AU-ExoMars/PCOT.git Otherwise type this: git clone https://github.com/AU-ExoMars/PCOT.git You should now have a PCOT directory which will contain this file (as README.md) and quite a few others. Opening Anaconda's shell on different OSs Windows: Open the Anaconda Powershell Prompt application, which will have been installed when you installed Anaconda. This can be found in the Start menu under Anaconda3, or in the applications list in Anaconda Navigator (which is also in the Start menu in the same place). Linux and MacOS : just open a Bash shell Installing on Ubuntu / MacOS Assuming you have successfully installed Anaconda and cloned or downloaded PCOT as above: Open a bash shell cd to the PCOT directory (which contains this file). Run the command conda create -n pcot python=3.8 poetry . This will create an environment called pcot which uses Python 3.8 and the Poetry dependency and packaging manager. It may take some time. Activate the environment with conda activate pcot . Now run poetry install . This will set up all the packages PCOT is dependent on and install PCOT. You should now be able to run pcot to start the application. Installing on Windows Assuming you have successfully installed Anaconda and cloned or downloaded PCOT as above: Open the Anaconda PowerShell Prompt application from the Start Menu. cd to the PCOT directory (which contains this file). Run the command conda create -n pcot python=3.8 poetry . This will create an environment called pcot which uses Python 3.8 and the Poetry dependency and packaging manager. It may take some time. Activate the environment with conda activate pcot . Now run poetry install . This will set up all the packages PCOT is dependent on and install PCOT. You should now be able to run pcot to start the application. Running PCOT Once you have installed PCOT as above, you can run it by opening an Anaconda shell and entering the following commands: conda activate pcot pcot Running PCOT inside Pycharm These instructions may be useful if you want to run PCOT inside a debugger - for example, if you are testing a custom node. First set up the Conda environment and interpreter: Open PyCharm and open the PCOT directory as an existing project. Open File/Settings.. (Ctrl+Alt+S) Select Project:PCOT / Python Interpreter If the Python Interpreter is not already Python 3.8 with something like anaconda3/envs/pcot/bin/python Select the cogwheel to the right of the Python Interpreter dropdown and then select Add . Select Conda Environment . Select Existing Environment . Select the environment: it should be something like anaconda3/envs/pcot/bin/python . Select OK . Now set up the run configuration: Select Edit Configurations... (or it might be Add Configuration... ) from the configurations drop down in the menu bar Add a new configuration (the + symbol) and select Python Set Script Path to PCOT/src/pcot/__main__.py Make sure the interpreter is something like Project Default (Python 3.8 (pcot)) , i.e. the Python interpreter of the pcot environment. You should now be able to run and debug PCOT. Environment variables It's a good idea, but not mandatory, to set the environment variable PCOT_USER to a string of the form name . For example, in Linux I have added the following to my .bashrc file: export PCOT_USER=\"Jim Finnis \" This data is added to all saved PCOT graphs. If the environment variable is not set, the username returned by Python's getpass module is used (e.g. 'jcf12'). In case of problems There are a few things which can stop PCOT running - see issues .","title":"Installing and running"},{"location":"gettingstarted/installrun/#installing-and-running-pcot","text":"PCOT is a Python program (and library) with a number of dependencies, notably numpy and PySide2 (the official Python interface to Qt). We find the best way to manage these is to use Anaconda and Poetry . Installation has been tested on Windows 10, MacOS and Ubuntu 20.04.","title":"Installing and running PCOT"},{"location":"gettingstarted/installrun/#install-anaconda","text":"The first thing you will need to do is install Anaconda, which can be done from here: Windows: https://docs.anaconda.com/anaconda/install/windows/ Linux: https://docs.anaconda.com/anaconda/install/linux/ MacOS: https://docs.anaconda.com/anaconda/install/mac-os/ (untested)","title":"Install Anaconda"},{"location":"gettingstarted/installrun/#obtain-the-software","text":"This can be done by either downloading the archive from Github and extracting it into a new directory, or cloning the repository. In both cases, the top level directory should be called PCOT (this isn't really mandatory but makes the instructions below simpler). The best way to download is this: Open an Anaconda shell window (see below) If you have an SSH key set up for GitHub, type this command into the shell ( changing the repository address if it is different ): git clone git@github.com:AU-ExoMars/PCOT.git Otherwise type this: git clone https://github.com/AU-ExoMars/PCOT.git You should now have a PCOT directory which will contain this file (as README.md) and quite a few others.","title":"Obtain the software"},{"location":"gettingstarted/installrun/#opening-anacondas-shell-on-different-oss","text":"Windows: Open the Anaconda Powershell Prompt application, which will have been installed when you installed Anaconda. This can be found in the Start menu under Anaconda3, or in the applications list in Anaconda Navigator (which is also in the Start menu in the same place). Linux and MacOS : just open a Bash shell","title":"Opening Anaconda's shell on different OSs"},{"location":"gettingstarted/installrun/#installing-on-ubuntu-macos","text":"Assuming you have successfully installed Anaconda and cloned or downloaded PCOT as above: Open a bash shell cd to the PCOT directory (which contains this file). Run the command conda create -n pcot python=3.8 poetry . This will create an environment called pcot which uses Python 3.8 and the Poetry dependency and packaging manager. It may take some time. Activate the environment with conda activate pcot . Now run poetry install . This will set up all the packages PCOT is dependent on and install PCOT. You should now be able to run pcot to start the application.","title":"Installing on Ubuntu / MacOS"},{"location":"gettingstarted/installrun/#installing-on-windows","text":"Assuming you have successfully installed Anaconda and cloned or downloaded PCOT as above: Open the Anaconda PowerShell Prompt application from the Start Menu. cd to the PCOT directory (which contains this file). Run the command conda create -n pcot python=3.8 poetry . This will create an environment called pcot which uses Python 3.8 and the Poetry dependency and packaging manager. It may take some time. Activate the environment with conda activate pcot . Now run poetry install . This will set up all the packages PCOT is dependent on and install PCOT. You should now be able to run pcot to start the application.","title":"Installing on Windows"},{"location":"gettingstarted/installrun/#running-pcot","text":"Once you have installed PCOT as above, you can run it by opening an Anaconda shell and entering the following commands: conda activate pcot pcot","title":"Running PCOT"},{"location":"gettingstarted/installrun/#running-pcot-inside-pycharm","text":"These instructions may be useful if you want to run PCOT inside a debugger - for example, if you are testing a custom node. First set up the Conda environment and interpreter: Open PyCharm and open the PCOT directory as an existing project. Open File/Settings.. (Ctrl+Alt+S) Select Project:PCOT / Python Interpreter If the Python Interpreter is not already Python 3.8 with something like anaconda3/envs/pcot/bin/python Select the cogwheel to the right of the Python Interpreter dropdown and then select Add . Select Conda Environment . Select Existing Environment . Select the environment: it should be something like anaconda3/envs/pcot/bin/python . Select OK . Now set up the run configuration: Select Edit Configurations... (or it might be Add Configuration... ) from the configurations drop down in the menu bar Add a new configuration (the + symbol) and select Python Set Script Path to PCOT/src/pcot/__main__.py Make sure the interpreter is something like Project Default (Python 3.8 (pcot)) , i.e. the Python interpreter of the pcot environment. You should now be able to run and debug PCOT.","title":"Running PCOT inside Pycharm"},{"location":"gettingstarted/installrun/#environment-variables","text":"It's a good idea, but not mandatory, to set the environment variable PCOT_USER to a string of the form name . For example, in Linux I have added the following to my .bashrc file: export PCOT_USER=\"Jim Finnis \" This data is added to all saved PCOT graphs. If the environment variable is not set, the username returned by Python's getpass module is used (e.g. 'jcf12').","title":"Environment variables"},{"location":"gettingstarted/installrun/#in-case-of-problems","text":"There are a few things which can stop PCOT running - see issues .","title":"In case of problems"},{"location":"gettingstarted/issues/","text":"Common runtime issues Can't start Qt on Linux This sometimes happens: qt.qpa.plugin: Could not load the Qt platform plugin \"xcb\" in \"\" even though it was found. This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem. Available platform plugins are: eglfs, linuxfb, minimal, minimalegl, offscreen, vnc, wayland-egl, wayland, wayland-xcomposite-egl, wayland-xcomposite-glx, webgl, xcb. Try this: export QT_DEBUG_PLUGINS=1 pcot to run the program again, and look at the output. You might see errors like this (I've removed some stuff): QFactoryLoader::QFactoryLoader() checking directory path \"[...]envs/pcot/bin/platforms\" ... Cannot load library [...]/plugins/platforms/libqxcb.so: (libxcb-xinerama.so.0: cannot open shared object file: No such file or directory) QLibraryPrivate::loadPlugin failed on \"...[stuff removed].. (libxcb-xinerama.so.0: cannot open shared object file: No such file or directory)\" If that's the case, install the missing package: sudo apt install libxcb-xinerama0 That might help. Otherwise, send a message to us with the output from the QT_DEBUG_PLUGINS run and we will investigate. Conda fails on Windows I have once seen an error involving OpenSSH not being correctly installed on Windows when the conda create... command was run. This happened toward the end of the installation. To fix it, I just ran the command again - it installed OpenSSH correctly and I was able to proceed.","title":"Known issues"},{"location":"gettingstarted/issues/#common-runtime-issues","text":"","title":"Common runtime issues"},{"location":"gettingstarted/issues/#cant-start-qt-on-linux","text":"This sometimes happens: qt.qpa.plugin: Could not load the Qt platform plugin \"xcb\" in \"\" even though it was found. This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem. Available platform plugins are: eglfs, linuxfb, minimal, minimalegl, offscreen, vnc, wayland-egl, wayland, wayland-xcomposite-egl, wayland-xcomposite-glx, webgl, xcb. Try this: export QT_DEBUG_PLUGINS=1 pcot to run the program again, and look at the output. You might see errors like this (I've removed some stuff): QFactoryLoader::QFactoryLoader() checking directory path \"[...]envs/pcot/bin/platforms\" ... Cannot load library [...]/plugins/platforms/libqxcb.so: (libxcb-xinerama.so.0: cannot open shared object file: No such file or directory) QLibraryPrivate::loadPlugin failed on \"...[stuff removed].. (libxcb-xinerama.so.0: cannot open shared object file: No such file or directory)\" If that's the case, install the missing package: sudo apt install libxcb-xinerama0 That might help. Otherwise, send a message to us with the output from the QT_DEBUG_PLUGINS run and we will investigate.","title":"Can't start Qt on Linux"},{"location":"gettingstarted/issues/#conda-fails-on-windows","text":"I have once seen an error involving OpenSSH not being correctly installed on Windows when the conda create... command was run. This happened toward the end of the installation. To fix it, I just ran the command again - it installed OpenSSH correctly and I was able to proceed.","title":"Conda fails on Windows"},{"location":"gettingstarted/tutold/","text":"A first tutorial This page is a gentle introduction to PCOT's user interface elements, and will walk you through loading image data and performing some simple manipulations. There is a video guide - it may be a little out of date, because development is progressing quickly but the basics will be the same (this also applies to this document). Be aware that this is very much an early version and there are no doubt a lot of serious problems! Preparation PCOT is primarily designed to work with multispectral images. For this tutorial we have prepared an image and packaged it with the rest of the system, but you will need to uncompress it: it's an ENVI image and these can be rather large. It can be found in the exampleData directory as obj4-sand-subset.zip . Uncompress it somewhere (perhaps into the same directory) producing two files: 1.hdr (the header file) and 1.dat (the data itself). This is a 256x256 image with taken using AUPE (Aberystwyth University Pancam Emulator), with 11 bands of data. It is a subset of a larger image used in testing, and is shown below as an RGB image: Figure: The example image (640nm, 550nm and 440nm bands). Click on image to expand. Now you have this image you can start PCOT. Introduction to the user interface First, a quick look at the UI with a document open. The image below shows the PCOT interface with text in red describing each part. Figure: The PCOT user interface. Click on image to expand. The window is divided into several areas: At the top, the input buttons each open one of PCOT's input windows. These describe how information is read into PCOT. Below this and to the left is the node area , which will be empty on startup. Double-clicking on a node in the graph will open a detailed view for that node. You can have several views open in this area looking at different nodes, and you can undock by double-clicking on the tab at the top of the view. To the right of the node area is the graph . This shows the nodes in the document and their connections. A new document always has an input 0 node, which brings input 0 into the graph. To the right of the graph is the palette . Clicking on a button in the palette will add a node of the given type to the graph. At the bottom is the log area , the status bar , and a set of global controls . Working with graph nodes Selecting in the graph When you open the program the first thing you will see is a graph containing a single input node, with no nodes open. You can select a node in the graph by clicking on it, or by dragging a box which intersects the node. A selected node will be tinted blue. Opening a node for editing Double-clicking on a node will open that node for editing. If you double-click on the input node, it will turn a dark cyan colour and a view of the node will appear on the left, along with a tab to select that view when multiple node views are open: Figure: An open but unused input node. Click on image to expand. The node is cyan because the current view's node is tinted green, and the node is already tinted blue because it is selected in the graph. There are two selection models: Multiple nodes can be selected in the graph; these are tinted blue. A single node's view can be open and currently being edited, this node is tinted green in the graph. Nodes which are both being edited and are selected are tinted cyan. The view for this particular node - input 0 - shows what external input is currently being read into PCOT on the input numbered 0. In this case it shows \"None\" because there is currently no input. Creating a new node This can be done in two ways: Clicking on a node type in the palette will create a new instance of that type in the graph, hopefully somewhere sensible. Right-clicking a node type from the palette and dragging onto the graph will create a node where you wish. \"Undocking\" a node's view Sometimes it is useful to see several node views at the same time. Double-clicking on the tab, where the name of the node type appears, will make the view open in a separate window. This can be done for several views, and the windows can be rearranged as you wish. Closing the window will \"redock\" the view so it appears in the node area as before. Constant and comment nodes These two nodes are special - the boxes in the graph have text fields which can be edited. For constant nodes, the value in the box will be the numeric output of the node. This node has no view, and double-clicking has no effect. For comment nodes, the value in the box is a text comment that can help other users navigate the graph. Once edited, the box can be resized by clicking and dragging its bottom-right corner. The text will flow to fit the box. Double-clicking on a comment node opens a view which provides an alternative way of editing the text, as well as changing the font size and colours. It's also the only way of getting blank lines into the text, since hitting the \"enter\" key will stop editing when you are editing the node text directly in its box. Figure: Comment and constant nodes. Click on image to expand. Loading an image While PCOT can support several types of multispectral data file, we will load an RGB image into PCOT for now. Clicking on the Input 0 button at the top of the main window will open the first of the four input's editor window. This will show a number of buttons, one for each input method. Click on RGB, and the window will show that input's RGB method settings and select the RGB method as being active (see below). Using the directory tree widget, double-click any image file (PNG or RGB). The right-hand side is a common component in PCOT known as a \"canvas\", which will show the image selected. The canvas lets you modify how the RGB channels are mapped using the three combo boxes. Here, they will just hold \"R\", \"G\" and \"B\" as the source band names because this is an RGB image source. Figure: An open RGB input. Click on image to expand. At the bottom of the image are three source indicators : these show what bands within which inputs were used to generate the canvas image. They should show something like [0:R][0:G][0:B] meaning that the red channel was generated from the band labelled \"R\" in input 0, and so on. Canvases Most nodes use a canvas to display some bands of an image as RGB. This will take up a large proportion of their view - in some cases all of it - with no other controls. It is worth discussing in some detail. A canvas is shown in the previous section as the right-hand side of an input widget. Another is shown below as the entire control area for an actual input node, which brings one of the four inputs into the graph. Figure: An open *input 0* node. Click on image to expand. You can pan the canvas using the scroll bars at the edge, and zoom with the mouse wheel. The button at bottom right will reset to show the entire image. To the left of the canvas image itself are three combo boxes which determine the image mapping : how the bands within the (probably) multispectral image map onto RGB channels for viewing. Each band in the combo box shows the input number, a colon, and typically the name, position or wavelength of the band. Exactly what is shown depends on the image being loaded and the Caption global control . Below this are some assorted controls: Show ROIs will mark any regions of interest the image has - these are added using nodes like rect (for a rectangle) and are carried forward through subsequent nodes (where appropriate), delimiting the area upon which calculations are performed. They also control the regions used for calculating spectra. Normally an ROI is only shown in the node which adds it. Show spectrum opens a side pane, and dragging the cursor across the image will plot the spectrum of the pixel under the cursor in this pane. If no filter wavelengths are available, a list of the values for each band is shown. Save RGB saves the RGB-mapped image as a PNG. Guess RGB tries to guess appropriate channels for the RGB canvas image. The canvas is going to get a lot more complex in the next release, because it needs to show image quality and uncertainty data for each band. This information will be moved into its own page Manipulating an image Let's perform a simple manipulation on an RGB image. It's not what PCOT is for, but it will demonstrate some of the principles without requiring actual multispectral data. In this example, we'll generate a \"red/green slope\" image (which is pretty meaningless). Start PCOT and load an image into input 0 as before, by clicking on the Input 0 button, selecting RGB and double-clicking on an image file. Double-click on the input 0 node in the graph - instances of this node bring input 0 into the graph. Click on expr in the palette (on the right) to create an expression evaluation node. Drag a connection from the output of input 0 to the a input of expr Double-click on the expr node to open its view for editing We now have an input feeding into an expression evaluator, which we can edit. First, let's just see one band. Click in the expr view's expression box: this is the box which says \"Enter expression here...\". Enter the string a$R This says \"select band R from input a \" - \"R\" is the name given to the red channel in RGB images (in multispectral images we typically use wavelengths on the right-hand side of the $ operator, such as a$640 ). Press \"Run\" in the node's view. You should now see a monochrome image in the node's canvas: the result of the calculation, containing only the red channel. Now change the expression to a$R - a$G and press \"Run\". This will set each pixel to the result of subtracting the green value for that pixel from the red value, giving us our \"red/green slope.\" You will probably see quite a mess, because the canvas expects data between 0 and 1 and some of the pixels will probably be negative. We need to normalise the image to that range. Change the expression to norm(a$R - a$G) and press \"Run\" again to see the final result. Note that the source indicators in the bottom left of the image are now displaying something like [0:G&0:R][0:G&0:R][0:G&0:R] This indicates that all three RGB channels shown in the canvas are getting their data from both the R and G bands of input 0. The end result should look something like this: Figure: A \"red-green slope\" calculated from an RGB image. Click on image to expand. Getting help Nodes Inside the PCOT app there are three ways to get help on a node: Double-clicking on the little blue box in the top right corner of that node in the graph, Using the right-click context menu on a node, Using the right-click context menu on a button in the palette. The node help texts are also available in the automatically generated documentation . If this isn't enough, don't hesitate to contact the Aberystwyth team. Expressions Doing the above on an expr node will tell you what operations, functions and properties are available. This text is also available in the automatically generated documentation for this node . You can also get help on properties and functions by right clicking in the log box at the bottom and selecting the appropriate option. Inside the expression box in the expr node, you can right-click on most things and ask for help. Loading different image formats The examples above use RGB images, which aren't much use for real work. Other image formats are available: ENVI images \"multiband\" images (multiple monochrome PNG images, one per band) PDS4 products (work in progress) Loading an ENVI image ENVI images consists of a header (.hdr) file and the actual data (.dat) file. Currently PCOT can only load ENVI files which are 32-bit floating point, BSQ (band sequential) interleaved. To load ENVI, open an input and click the ENVI button. A dialog will appear which is very similar to the RGB file dialog above , but showing ENVI header files instead of image files. Double-click on such a file to load its associated data, which is assumed to be in the same directory with the same name. Filter name and wavelength information will be taken from the file. Loading a \"multiband\" image Sometimes data is provided as a set of monochrome PNG files, although this is clearly far from ideal. In this case we need to tell PCOT how to work out which filter is being used for each file. Again, we open the dialog by clicking on an input button and clicking the appropriate method - \"Multifile\" in this case. This will open the ENVI dialog, which is rather more complex than the RGB or ENVI dialogs: Figure: An open ENVI input. Click on image to expand. The procedure here is roughly this: Make sure the file names contain the the lens (left or right) as L or R, and the filter position number. Work out a regular expression which can find these in the file name. Hopefully you can just use the default, which assumes that the filename contains a string like LWAC01 for left lens, position 01; or RWAC10 for right lens, position 10. If your filenames don't have this format and it's too difficult to rename them, you'll have to write an RE yourself or find a handy IT person to help. Get the files into a single directory and open the input dialog as shown above. Determine which camera configuration - PANCAM or AUPE - produced the images and set the Camera option accordingly. These two setups use different filter sets, and will translate filter positions into different filter wavelengths and names. Click the \"Get Directory\" button. In the new dialog, select a directory containing the ENVI .hdr files and their accompanying .dat files, and click \"Select Folder\". A lot of files will appear in the Files box. Double-click images to preview them as a single channel. If they are dark, select an appropriate multiplier and double-click again. Click in image checkboxes to select them; selected images will be combined into a single multispectral mage. Close the dialog when you have the selected images you want. It might be a good idea to create an input node to examine the resulting multispectral image. PDS4 products This is very much work in progress - please contact the developers if you need this functionality soon.","title":"A first tutorial"},{"location":"gettingstarted/tutold/#a-first-tutorial","text":"This page is a gentle introduction to PCOT's user interface elements, and will walk you through loading image data and performing some simple manipulations. There is a video guide - it may be a little out of date, because development is progressing quickly but the basics will be the same (this also applies to this document). Be aware that this is very much an early version and there are no doubt a lot of serious problems!","title":"A first tutorial"},{"location":"gettingstarted/tutold/#preparation","text":"PCOT is primarily designed to work with multispectral images. For this tutorial we have prepared an image and packaged it with the rest of the system, but you will need to uncompress it: it's an ENVI image and these can be rather large. It can be found in the exampleData directory as obj4-sand-subset.zip . Uncompress it somewhere (perhaps into the same directory) producing two files: 1.hdr (the header file) and 1.dat (the data itself). This is a 256x256 image with taken using AUPE (Aberystwyth University Pancam Emulator), with 11 bands of data. It is a subset of a larger image used in testing, and is shown below as an RGB image: Figure: The example image (640nm, 550nm and 440nm bands). Click on image to expand. Now you have this image you can start PCOT.","title":"Preparation"},{"location":"gettingstarted/tutold/#introduction-to-the-user-interface","text":"First, a quick look at the UI with a document open. The image below shows the PCOT interface with text in red describing each part. Figure: The PCOT user interface. Click on image to expand. The window is divided into several areas: At the top, the input buttons each open one of PCOT's input windows. These describe how information is read into PCOT. Below this and to the left is the node area , which will be empty on startup. Double-clicking on a node in the graph will open a detailed view for that node. You can have several views open in this area looking at different nodes, and you can undock by double-clicking on the tab at the top of the view. To the right of the node area is the graph . This shows the nodes in the document and their connections. A new document always has an input 0 node, which brings input 0 into the graph. To the right of the graph is the palette . Clicking on a button in the palette will add a node of the given type to the graph. At the bottom is the log area , the status bar , and a set of global controls .","title":"Introduction to the user interface"},{"location":"gettingstarted/tutold/#working-with-graph-nodes","text":"","title":"Working with graph nodes"},{"location":"gettingstarted/tutold/#selecting-in-the-graph","text":"When you open the program the first thing you will see is a graph containing a single input node, with no nodes open. You can select a node in the graph by clicking on it, or by dragging a box which intersects the node. A selected node will be tinted blue.","title":"Selecting in the graph"},{"location":"gettingstarted/tutold/#opening-a-node-for-editing","text":"Double-clicking on a node will open that node for editing. If you double-click on the input node, it will turn a dark cyan colour and a view of the node will appear on the left, along with a tab to select that view when multiple node views are open: Figure: An open but unused input node. Click on image to expand. The node is cyan because the current view's node is tinted green, and the node is already tinted blue because it is selected in the graph. There are two selection models: Multiple nodes can be selected in the graph; these are tinted blue. A single node's view can be open and currently being edited, this node is tinted green in the graph. Nodes which are both being edited and are selected are tinted cyan. The view for this particular node - input 0 - shows what external input is currently being read into PCOT on the input numbered 0. In this case it shows \"None\" because there is currently no input.","title":"Opening a node for editing"},{"location":"gettingstarted/tutold/#creating-a-new-node","text":"This can be done in two ways: Clicking on a node type in the palette will create a new instance of that type in the graph, hopefully somewhere sensible. Right-clicking a node type from the palette and dragging onto the graph will create a node where you wish.","title":"Creating a new node"},{"location":"gettingstarted/tutold/#undocking-a-nodes-view","text":"Sometimes it is useful to see several node views at the same time. Double-clicking on the tab, where the name of the node type appears, will make the view open in a separate window. This can be done for several views, and the windows can be rearranged as you wish. Closing the window will \"redock\" the view so it appears in the node area as before.","title":"\"Undocking\" a node's view"},{"location":"gettingstarted/tutold/#constant-and-comment-nodes","text":"These two nodes are special - the boxes in the graph have text fields which can be edited. For constant nodes, the value in the box will be the numeric output of the node. This node has no view, and double-clicking has no effect. For comment nodes, the value in the box is a text comment that can help other users navigate the graph. Once edited, the box can be resized by clicking and dragging its bottom-right corner. The text will flow to fit the box. Double-clicking on a comment node opens a view which provides an alternative way of editing the text, as well as changing the font size and colours. It's also the only way of getting blank lines into the text, since hitting the \"enter\" key will stop editing when you are editing the node text directly in its box. Figure: Comment and constant nodes. Click on image to expand.","title":"Constant and comment nodes"},{"location":"gettingstarted/tutold/#loading-an-image","text":"While PCOT can support several types of multispectral data file, we will load an RGB image into PCOT for now. Clicking on the Input 0 button at the top of the main window will open the first of the four input's editor window. This will show a number of buttons, one for each input method. Click on RGB, and the window will show that input's RGB method settings and select the RGB method as being active (see below). Using the directory tree widget, double-click any image file (PNG or RGB). The right-hand side is a common component in PCOT known as a \"canvas\", which will show the image selected. The canvas lets you modify how the RGB channels are mapped using the three combo boxes. Here, they will just hold \"R\", \"G\" and \"B\" as the source band names because this is an RGB image source. Figure: An open RGB input. Click on image to expand. At the bottom of the image are three source indicators : these show what bands within which inputs were used to generate the canvas image. They should show something like [0:R][0:G][0:B] meaning that the red channel was generated from the band labelled \"R\" in input 0, and so on.","title":"Loading an image"},{"location":"gettingstarted/tutold/#canvases","text":"Most nodes use a canvas to display some bands of an image as RGB. This will take up a large proportion of their view - in some cases all of it - with no other controls. It is worth discussing in some detail. A canvas is shown in the previous section as the right-hand side of an input widget. Another is shown below as the entire control area for an actual input node, which brings one of the four inputs into the graph. Figure: An open *input 0* node. Click on image to expand. You can pan the canvas using the scroll bars at the edge, and zoom with the mouse wheel. The button at bottom right will reset to show the entire image. To the left of the canvas image itself are three combo boxes which determine the image mapping : how the bands within the (probably) multispectral image map onto RGB channels for viewing. Each band in the combo box shows the input number, a colon, and typically the name, position or wavelength of the band. Exactly what is shown depends on the image being loaded and the Caption global control . Below this are some assorted controls: Show ROIs will mark any regions of interest the image has - these are added using nodes like rect (for a rectangle) and are carried forward through subsequent nodes (where appropriate), delimiting the area upon which calculations are performed. They also control the regions used for calculating spectra. Normally an ROI is only shown in the node which adds it. Show spectrum opens a side pane, and dragging the cursor across the image will plot the spectrum of the pixel under the cursor in this pane. If no filter wavelengths are available, a list of the values for each band is shown. Save RGB saves the RGB-mapped image as a PNG. Guess RGB tries to guess appropriate channels for the RGB canvas image. The canvas is going to get a lot more complex in the next release, because it needs to show image quality and uncertainty data for each band. This information will be moved into its own page","title":"Canvases"},{"location":"gettingstarted/tutold/#manipulating-an-image","text":"Let's perform a simple manipulation on an RGB image. It's not what PCOT is for, but it will demonstrate some of the principles without requiring actual multispectral data. In this example, we'll generate a \"red/green slope\" image (which is pretty meaningless). Start PCOT and load an image into input 0 as before, by clicking on the Input 0 button, selecting RGB and double-clicking on an image file. Double-click on the input 0 node in the graph - instances of this node bring input 0 into the graph. Click on expr in the palette (on the right) to create an expression evaluation node. Drag a connection from the output of input 0 to the a input of expr Double-click on the expr node to open its view for editing We now have an input feeding into an expression evaluator, which we can edit. First, let's just see one band. Click in the expr view's expression box: this is the box which says \"Enter expression here...\". Enter the string a$R This says \"select band R from input a \" - \"R\" is the name given to the red channel in RGB images (in multispectral images we typically use wavelengths on the right-hand side of the $ operator, such as a$640 ). Press \"Run\" in the node's view. You should now see a monochrome image in the node's canvas: the result of the calculation, containing only the red channel. Now change the expression to a$R - a$G and press \"Run\". This will set each pixel to the result of subtracting the green value for that pixel from the red value, giving us our \"red/green slope.\" You will probably see quite a mess, because the canvas expects data between 0 and 1 and some of the pixels will probably be negative. We need to normalise the image to that range. Change the expression to norm(a$R - a$G) and press \"Run\" again to see the final result. Note that the source indicators in the bottom left of the image are now displaying something like [0:G&0:R][0:G&0:R][0:G&0:R] This indicates that all three RGB channels shown in the canvas are getting their data from both the R and G bands of input 0. The end result should look something like this: Figure: A \"red-green slope\" calculated from an RGB image. Click on image to expand.","title":"Manipulating an image"},{"location":"gettingstarted/tutold/#getting-help","text":"","title":"Getting help"},{"location":"gettingstarted/tutold/#nodes","text":"Inside the PCOT app there are three ways to get help on a node: Double-clicking on the little blue box in the top right corner of that node in the graph, Using the right-click context menu on a node, Using the right-click context menu on a button in the palette. The node help texts are also available in the automatically generated documentation . If this isn't enough, don't hesitate to contact the Aberystwyth team.","title":"Nodes"},{"location":"gettingstarted/tutold/#expressions","text":"Doing the above on an expr node will tell you what operations, functions and properties are available. This text is also available in the automatically generated documentation for this node . You can also get help on properties and functions by right clicking in the log box at the bottom and selecting the appropriate option. Inside the expression box in the expr node, you can right-click on most things and ask for help.","title":"Expressions"},{"location":"gettingstarted/tutold/#loading-different-image-formats","text":"The examples above use RGB images, which aren't much use for real work. Other image formats are available: ENVI images \"multiband\" images (multiple monochrome PNG images, one per band) PDS4 products (work in progress)","title":"Loading different image formats"},{"location":"gettingstarted/tutold/#loading-an-envi-image","text":"ENVI images consists of a header (.hdr) file and the actual data (.dat) file. Currently PCOT can only load ENVI files which are 32-bit floating point, BSQ (band sequential) interleaved. To load ENVI, open an input and click the ENVI button. A dialog will appear which is very similar to the RGB file dialog above , but showing ENVI header files instead of image files. Double-click on such a file to load its associated data, which is assumed to be in the same directory with the same name. Filter name and wavelength information will be taken from the file.","title":"Loading an ENVI image"},{"location":"gettingstarted/tutold/#loading-a-multiband-image","text":"Sometimes data is provided as a set of monochrome PNG files, although this is clearly far from ideal. In this case we need to tell PCOT how to work out which filter is being used for each file. Again, we open the dialog by clicking on an input button and clicking the appropriate method - \"Multifile\" in this case. This will open the ENVI dialog, which is rather more complex than the RGB or ENVI dialogs: Figure: An open ENVI input. Click on image to expand. The procedure here is roughly this: Make sure the file names contain the the lens (left or right) as L or R, and the filter position number. Work out a regular expression which can find these in the file name. Hopefully you can just use the default, which assumes that the filename contains a string like LWAC01 for left lens, position 01; or RWAC10 for right lens, position 10. If your filenames don't have this format and it's too difficult to rename them, you'll have to write an RE yourself or find a handy IT person to help. Get the files into a single directory and open the input dialog as shown above. Determine which camera configuration - PANCAM or AUPE - produced the images and set the Camera option accordingly. These two setups use different filter sets, and will translate filter positions into different filter wavelengths and names. Click the \"Get Directory\" button. In the new dialog, select a directory containing the ENVI .hdr files and their accompanying .dat files, and click \"Select Folder\". A lot of files will appear in the Files box. Double-click images to preview them as a single channel. If they are dark, select an appropriate multiplier and double-click again. Click in image checkboxes to select them; selected images will be combined into a single multispectral mage. Close the dialog when you have the selected images you want. It might be a good idea to create an input node to examine the resulting multispectral image.","title":"Loading a \"multiband\" image"},{"location":"gettingstarted/tutold/#pds4-products","text":"This is very much work in progress - please contact the developers if you need this functionality soon.","title":"PDS4 products"},{"location":"gettingstarted/tutorial/","text":"A first tutorial This page is a gentle introduction to PCOT's user interface elements, and will walk you through loading image data and performing some simple manipulations. There is also a video guide - it may be a little out of date, because development is progressing quickly but the basics will be the same. In this document, text written with a highlight like this is an instruction to follow. Be aware that this is an early version and there are no doubt a lot of serious problems! Also, the software is changing very quickly, so it may not look exactly as it does in these pages. Preparation PCOT is primarily designed to work with multispectral images. For this tutorial we have prepared an image and packaged it with the rest of the system, but you will need to uncompress it: it's an ENVI image and these can be rather large. It can be found in the exampleData directory as obj4-sand-subset.zip . Uncompress this image somewhere (perhaps into the same directory) producing two files: 1.hdr (the header file) and 1.dat (the data itself). This is a 256x256 image with taken using AUPE (Aberystwyth University Pancam Emulator), with 11 bands of data. It is a subset of a larger image used in testing, and is shown below as an RGB image: Figure: The example image (640nm, 550nm and 440nm bands). Click on image to expand. Now you have this image you can start PCOT . Introduction to the user interface The image below shows the PCOT interface when the program has just been started, with text in red describing each part. Figure: The PCOT user interface. Click on image to expand. The window is divided into several areas: At the top, the input buttons each open one of PCOT's input windows. These describe how information is read into PCOT. Below this and to the left is the node area , which will be empty on startup. Double-clicking on a node in the graph (see below) will open a detailed view for that node. You can have several views open in this area looking at different nodes, and you can undock by double-clicking on the tab at the top of the view. To the right of the node area is the graph . This shows the nodes in the document and their connections. A new document always has an input 0 node, which brings input 0 into the graph. To the right of the graph is the palette , which consists of a number of sections which can be expanded or hidden. Clicking on a button in the palette will add a node of the given type to the graph. At the bottom is the log area , the status bar , and a set of global controls . Working with graph nodes Each node is shown as a box with input connections on the top and output connections on the bottom. To illustrate this, consider the following graph you've seen before: Figure: R671_438 inset into source image RGB representation. Click on image to expand. Node boxes Looking at a pair of nodes in the graph: Figure: A pair of nodes. Click on image to expand. The inputs and outputs are coloured by type: blue is perhaps the most common and indicates an image. Red means any kind of data can be connected here, and generally only appears on inputs. Inputs and outputs can also have names (but don't always). Each node has a help box - double-clicking on this pops up help for the node. Each node has a name - usually this is the node's type, but in the case of the expr node it is the expression being calculated (see below). Nodes can have a \"display text\" in blue. These show extra data, such as the output of a calculation in an expr node or the annotation given to a region of interest. Currently nodes without a display text also show the number of times they have been calculated as a debugging aid. Selecting and opening graph nodes You can select a node in the graph by clicking on it, or by dragging a box which intersects the node. A selected node will be tinted blue. You can open a node for viewing or editing in the node area by double-clicking. If you double-click on the input 0 node, it will turn a dark cyan colour and a view of the node will appear on the left, along with a tab to select that view when multiple node views are open. You can close an open node by clicking on the cross in the node's tab. If you open the input 0 node in a new document you will see this: Figure: PCOT with an open but unused input node. Click on image to expand. The node is cyan because the current view's node is tinted green, and the node is already tinted blue because it is selected in the graph. There are two selection models: Multiple nodes can be selected in the graph; these are tinted blue. A single node's view can be open and currently being edited, this node is tinted green in the graph. Nodes which are both being edited and are selected are tinted cyan. The view for this particular node - input 0 - shows what external input is currently being read into PCOT on the input numbered 0. In this case it shows \"None\" because there is currently no input. Creating a new node This can be done in two ways: Clicking on a node type in the palette will create a new instance of that type in the graph, hopefully somewhere sensible. Right-clicking a node type from the palette and dragging onto the graph will create a node where you wish. \"Undocking\" a node's view Sometimes it is useful to see several node views at the same time. Double-clicking on the tab, where the name of the node type appears, will make the view open in a separate window. This can be done for several views, and the windows can be rearranged as you wish. Closing the window will \"redock\" the view so it appears in the node area as before. Constant and comment nodes These two nodes are special - the boxes in the graph have text fields which can be edited. For constant nodes, the value in the box will be the numeric output of the node. This node has no view, and double-clicking has no effect. For comment nodes, the value in the box is a text comment that can help other users navigate the graph. Once edited, the box can be resized by clicking and dragging its bottom-right corner. The text will flow to fit the box. Double-clicking on a comment node opens a view which provides an alternative way of editing the text, as well as changing the font size and colours. It's also the only way of getting blank lines into the text, since hitting the \"enter\" key will stop editing when you are editing the node text directly in its box. Figure: Comment and constant nodes. Click on image to expand. Loading an image The purpose of the input 0 node is to bring the an input into the graph for manipulation. As noted elsewhere , the graph is separate from the inputs. This makes it easier to run different graphs on the same inputs, or the same graph on different inputs. Clicking on the \"Input 0\" button at the top of the window will open a window to let us change the input, which looks like this: Figure: An input window, with the null input selected. Click on image to expand. Each input supports a number of input methods , only one of which can be active at a time. By default, the method is null , meaning that no data is loaded into that input. PCOT can support several types of multispectral data file: we will load a small ENVI image we uncompressed earlier. Click on ENVI , and the window will show that input's ENVI method settings and select the ENVI method as being active (see below). In the ENVI input window, Click on Select Directory to open a dialog Choose the directory where you stored the 1.hdr and 1.dat files you extracted earlier Double-click on the 1.hdr file You should see something like this: Figure: An open ENVI input. Click on image to expand. The Canvas The right-hand side is a common component in PCOT known as a canvas which will show the image selected. The canvas lets you modify how the RGB channels are mapped using the three combo boxes. Each box gives the band number, the input number and the wavelength. For example, the red channel is coming from band 2, which is from input 0, and has a wavelength of 640nm. If the RGB method is used to load an ordinary RGB image (e.g. from a PNG), the wavelength will not be shown. At the bottom of the image itself are three source indicators : these show what bands within which inputs were used to generate the canvas image. They should show something like [0:640][0:540][0:480] indicating that the red, green and blue channels come from the 640nm, 540nm and 480nm bands in input 0's image. The source indicator may get quite complex - it shows all inputs that have had an effect on each channel of the image. The indicators may be different because the source display is currently under development. The canvas is quite complicated because it does a lot of things, such as displaying spectra for each pixel, normalisation settings, and displaying data quality bits. More information can be found here , but for now: Click the spectrum button to open a spectrum view, and move the cursor over the image to show the spectrum at different pixels. You may need to resize the window, or drag the \"double bar\" which separates the image from the spectrum. Manipulating an image: obtaining a spectral parameter map Let's perform a simple manipulation on an image. Here we will generate a spectral parameter map from two of the image bands. This parameter is R671_438 , the ratio between the 671nm and 438nm bands, which indicates the presence of ferric minerals 1 . Start PCOT and load an image into input 0 as before , by clicking on the Input 0 button, selecting RGB and double-clicking on an image file. Double-click on the input 0 node in the graph - instances of this node bring input 0 into the graph. Open the maths section in the palette by clicking on it. Click on expr in the maths section to create an expression evaluation node. Drag a connection from the output of input 0 to the a input of expr . Double-click on the expr node to open its view for editing - this will show an empty output because our mathematical expression is empty. We now have an input feeding into an expression evaluator, which we can edit. First, let's just see one band. Click in the expr view's expression box : this is the box which says \"Enter expression here...\". Enter the string a$671 This says \"select the band with centre wavelength 671nm from input a \". Press \"Run\" in the node's view . You should now see a monochrome image in the node's canvas: the result of the calculation, containing only that single band. Now change the expression to a$671/a$438 and press \"Run\" to see the result, which should be this: Figure: R671_438 parameter from an multispectral image. Click on image to expand. Note that the source indicators in the bottom left of the image are now displaying [0:438&0:671] [0:438&0:671] [0:438&0:671] This indicates that all three RGB channels shown in the canvas are getting their data from the 438 and 671 bands of input 0. Disconnecting nodes and node error states Disconnect the input node by dragging the arrowhead from the a box and releasing it in empty space. This will cause an error: Figure: A node in an error state. Click on image to expand. The EXPR is the kind of error - EXPR is an error in expression parsing/execution. More details can be seen in the log window, which in this case reads Error in expression: variable's input is not connected This is because there is no longer an image feeding into the expr node's input for variable a . Now reconnect the input node to the expr node to fix the error. Adding a region of interest It's a little hard to see what's going on, so we will add a region of interest. This will make the expr node treat the operation differently - only the area inside the rectangle will be norm(a$671/a$438) . Everywhere else in the image, the output will come from the left-hand side of the expression (the 671nm channel). The rules for ROIs are explained more fully on this page . Open the regions section of the palette and add a rect node between the input and expr nodes (the latter of which will be labelled with its expression). Nothing will change, because the rectangle has not been set. Edit the rect node and draw a rectangle by clicking and dragging on the canvas. Set the scale to 5 pixels and type \"671_438\" in the Annotation box , something like this: Figure: R671_438 with a rectangle. Click on image to expand. Now the expr node shows this - only the rectangle has the 671/438 parameter, while the rest of the image shows the 671nm band. To see this, click on the expr tab : Figure: R671_438 with a rectangle. Click on image to expand. The image is dark, because the values of the parameter shown in the rectangle are much greater than those of the 671nm band. It can be hard to adjust the rectangle when you can't see what's happening in the final image - try undocking the rect node and dragging it to a different part of the screen by double-clicking on the tab, so you can edit the rectangle while the expr output is visible. Dock it again by closing the undocked window . To make things more visible still, open the data section and add a gradient node after the expr node and view it , clicking making sure Show ROIs is enabled on its canvas (ROIs are typically retained on derived images): Figure: R671_438 with a rectangle and gradient. Click on image to expand. There's not much to see because of the nature of the image, unfortunately! Note that there is a \"legend\" at top left, and this can be edited and placed by clicking on the Legend button : Figure: R671_438 with a better gradient legend. Click on image to expand. The colour palette for the gradient, incidentally, is the \"viridis\" palette which is helpful for people with certain kinds of colour blindness 2 . Other presets are available by clicking on the Load Preset button, or you can edit the gradient by hand: double-click to add or remove a point, drag to move a point. It is possible to \"inset\" the gradient into the RGB representation used in the input node by passing that to the insetinto input of the gradient: Figure: R671_438 inset into source image RGB representation. Click on image to expand. If you switch to the Legend part of the gradient node and select Left margin for the Legend item , when you export the image with the canvas' Export Image button the legend will be in the margin: Figure: Exported R671_438 (svg). Click on image to expand. Allender, E. J., C. R. Cousins, M. D. Gunn, and C. M. Caudill. \"Multiscale and multispectral characterization of mineralogy with the ExoMars 2022 rover remote sensing payload.\" Earth and Space Science 7, no. 4 (2020): e2019EA000692. \u21a9 Simon Garnier, Noam Ross, Robert Rudis, Ant\u00f4nio P. Camargo, Marco Sciaini, and C\u00e9dric Scherer (2021). viridis(Lite) - Colorblind-Friendly Color Maps for R. viridis package version 0.6.2. \u21a9","title":"Tutorial"},{"location":"gettingstarted/tutorial/#a-first-tutorial","text":"This page is a gentle introduction to PCOT's user interface elements, and will walk you through loading image data and performing some simple manipulations. There is also a video guide - it may be a little out of date, because development is progressing quickly but the basics will be the same. In this document, text written with a highlight like this is an instruction to follow. Be aware that this is an early version and there are no doubt a lot of serious problems! Also, the software is changing very quickly, so it may not look exactly as it does in these pages.","title":"A first tutorial"},{"location":"gettingstarted/tutorial/#preparation","text":"PCOT is primarily designed to work with multispectral images. For this tutorial we have prepared an image and packaged it with the rest of the system, but you will need to uncompress it: it's an ENVI image and these can be rather large. It can be found in the exampleData directory as obj4-sand-subset.zip . Uncompress this image somewhere (perhaps into the same directory) producing two files: 1.hdr (the header file) and 1.dat (the data itself). This is a 256x256 image with taken using AUPE (Aberystwyth University Pancam Emulator), with 11 bands of data. It is a subset of a larger image used in testing, and is shown below as an RGB image: Figure: The example image (640nm, 550nm and 440nm bands). Click on image to expand. Now you have this image you can start PCOT .","title":"Preparation"},{"location":"gettingstarted/tutorial/#introduction-to-the-user-interface","text":"The image below shows the PCOT interface when the program has just been started, with text in red describing each part. Figure: The PCOT user interface. Click on image to expand. The window is divided into several areas: At the top, the input buttons each open one of PCOT's input windows. These describe how information is read into PCOT. Below this and to the left is the node area , which will be empty on startup. Double-clicking on a node in the graph (see below) will open a detailed view for that node. You can have several views open in this area looking at different nodes, and you can undock by double-clicking on the tab at the top of the view. To the right of the node area is the graph . This shows the nodes in the document and their connections. A new document always has an input 0 node, which brings input 0 into the graph. To the right of the graph is the palette , which consists of a number of sections which can be expanded or hidden. Clicking on a button in the palette will add a node of the given type to the graph. At the bottom is the log area , the status bar , and a set of global controls .","title":"Introduction to the user interface"},{"location":"gettingstarted/tutorial/#working-with-graph-nodes","text":"Each node is shown as a box with input connections on the top and output connections on the bottom. To illustrate this, consider the following graph you've seen before: Figure: R671_438 inset into source image RGB representation. Click on image to expand.","title":"Working with graph nodes"},{"location":"gettingstarted/tutorial/#node-boxes","text":"Looking at a pair of nodes in the graph: Figure: A pair of nodes. Click on image to expand. The inputs and outputs are coloured by type: blue is perhaps the most common and indicates an image. Red means any kind of data can be connected here, and generally only appears on inputs. Inputs and outputs can also have names (but don't always). Each node has a help box - double-clicking on this pops up help for the node. Each node has a name - usually this is the node's type, but in the case of the expr node it is the expression being calculated (see below). Nodes can have a \"display text\" in blue. These show extra data, such as the output of a calculation in an expr node or the annotation given to a region of interest. Currently nodes without a display text also show the number of times they have been calculated as a debugging aid.","title":"Node boxes"},{"location":"gettingstarted/tutorial/#selecting-and-opening-graph-nodes","text":"You can select a node in the graph by clicking on it, or by dragging a box which intersects the node. A selected node will be tinted blue. You can open a node for viewing or editing in the node area by double-clicking. If you double-click on the input 0 node, it will turn a dark cyan colour and a view of the node will appear on the left, along with a tab to select that view when multiple node views are open. You can close an open node by clicking on the cross in the node's tab. If you open the input 0 node in a new document you will see this: Figure: PCOT with an open but unused input node. Click on image to expand. The node is cyan because the current view's node is tinted green, and the node is already tinted blue because it is selected in the graph. There are two selection models: Multiple nodes can be selected in the graph; these are tinted blue. A single node's view can be open and currently being edited, this node is tinted green in the graph. Nodes which are both being edited and are selected are tinted cyan. The view for this particular node - input 0 - shows what external input is currently being read into PCOT on the input numbered 0. In this case it shows \"None\" because there is currently no input.","title":"Selecting and opening graph nodes"},{"location":"gettingstarted/tutorial/#creating-a-new-node","text":"This can be done in two ways: Clicking on a node type in the palette will create a new instance of that type in the graph, hopefully somewhere sensible. Right-clicking a node type from the palette and dragging onto the graph will create a node where you wish.","title":"Creating a new node"},{"location":"gettingstarted/tutorial/#undocking-a-nodes-view","text":"Sometimes it is useful to see several node views at the same time. Double-clicking on the tab, where the name of the node type appears, will make the view open in a separate window. This can be done for several views, and the windows can be rearranged as you wish. Closing the window will \"redock\" the view so it appears in the node area as before.","title":"\"Undocking\" a node's view"},{"location":"gettingstarted/tutorial/#constant-and-comment-nodes","text":"These two nodes are special - the boxes in the graph have text fields which can be edited. For constant nodes, the value in the box will be the numeric output of the node. This node has no view, and double-clicking has no effect. For comment nodes, the value in the box is a text comment that can help other users navigate the graph. Once edited, the box can be resized by clicking and dragging its bottom-right corner. The text will flow to fit the box. Double-clicking on a comment node opens a view which provides an alternative way of editing the text, as well as changing the font size and colours. It's also the only way of getting blank lines into the text, since hitting the \"enter\" key will stop editing when you are editing the node text directly in its box. Figure: Comment and constant nodes. Click on image to expand.","title":"Constant and comment nodes"},{"location":"gettingstarted/tutorial/#loading-an-image","text":"The purpose of the input 0 node is to bring the an input into the graph for manipulation. As noted elsewhere , the graph is separate from the inputs. This makes it easier to run different graphs on the same inputs, or the same graph on different inputs. Clicking on the \"Input 0\" button at the top of the window will open a window to let us change the input, which looks like this: Figure: An input window, with the null input selected. Click on image to expand. Each input supports a number of input methods , only one of which can be active at a time. By default, the method is null , meaning that no data is loaded into that input. PCOT can support several types of multispectral data file: we will load a small ENVI image we uncompressed earlier. Click on ENVI , and the window will show that input's ENVI method settings and select the ENVI method as being active (see below). In the ENVI input window, Click on Select Directory to open a dialog Choose the directory where you stored the 1.hdr and 1.dat files you extracted earlier Double-click on the 1.hdr file You should see something like this: Figure: An open ENVI input. Click on image to expand.","title":"Loading an image"},{"location":"gettingstarted/tutorial/#the-canvas","text":"The right-hand side is a common component in PCOT known as a canvas which will show the image selected. The canvas lets you modify how the RGB channels are mapped using the three combo boxes. Each box gives the band number, the input number and the wavelength. For example, the red channel is coming from band 2, which is from input 0, and has a wavelength of 640nm. If the RGB method is used to load an ordinary RGB image (e.g. from a PNG), the wavelength will not be shown. At the bottom of the image itself are three source indicators : these show what bands within which inputs were used to generate the canvas image. They should show something like [0:640][0:540][0:480] indicating that the red, green and blue channels come from the 640nm, 540nm and 480nm bands in input 0's image. The source indicator may get quite complex - it shows all inputs that have had an effect on each channel of the image. The indicators may be different because the source display is currently under development. The canvas is quite complicated because it does a lot of things, such as displaying spectra for each pixel, normalisation settings, and displaying data quality bits. More information can be found here , but for now: Click the spectrum button to open a spectrum view, and move the cursor over the image to show the spectrum at different pixels. You may need to resize the window, or drag the \"double bar\" which separates the image from the spectrum.","title":"The Canvas"},{"location":"gettingstarted/tutorial/#manipulating-an-image-obtaining-a-spectral-parameter-map","text":"Let's perform a simple manipulation on an image. Here we will generate a spectral parameter map from two of the image bands. This parameter is R671_438 , the ratio between the 671nm and 438nm bands, which indicates the presence of ferric minerals 1 . Start PCOT and load an image into input 0 as before , by clicking on the Input 0 button, selecting RGB and double-clicking on an image file. Double-click on the input 0 node in the graph - instances of this node bring input 0 into the graph. Open the maths section in the palette by clicking on it. Click on expr in the maths section to create an expression evaluation node. Drag a connection from the output of input 0 to the a input of expr . Double-click on the expr node to open its view for editing - this will show an empty output because our mathematical expression is empty. We now have an input feeding into an expression evaluator, which we can edit. First, let's just see one band. Click in the expr view's expression box : this is the box which says \"Enter expression here...\". Enter the string a$671 This says \"select the band with centre wavelength 671nm from input a \". Press \"Run\" in the node's view . You should now see a monochrome image in the node's canvas: the result of the calculation, containing only that single band. Now change the expression to a$671/a$438 and press \"Run\" to see the result, which should be this: Figure: R671_438 parameter from an multispectral image. Click on image to expand. Note that the source indicators in the bottom left of the image are now displaying [0:438&0:671] [0:438&0:671] [0:438&0:671] This indicates that all three RGB channels shown in the canvas are getting their data from the 438 and 671 bands of input 0.","title":"Manipulating an image: obtaining a spectral parameter map"},{"location":"gettingstarted/tutorial/#disconnecting-nodes-and-node-error-states","text":"Disconnect the input node by dragging the arrowhead from the a box and releasing it in empty space. This will cause an error: Figure: A node in an error state. Click on image to expand. The EXPR is the kind of error - EXPR is an error in expression parsing/execution. More details can be seen in the log window, which in this case reads Error in expression: variable's input is not connected This is because there is no longer an image feeding into the expr node's input for variable a . Now reconnect the input node to the expr node to fix the error.","title":"Disconnecting nodes and node error states"},{"location":"gettingstarted/tutorial/#adding-a-region-of-interest","text":"It's a little hard to see what's going on, so we will add a region of interest. This will make the expr node treat the operation differently - only the area inside the rectangle will be norm(a$671/a$438) . Everywhere else in the image, the output will come from the left-hand side of the expression (the 671nm channel). The rules for ROIs are explained more fully on this page . Open the regions section of the palette and add a rect node between the input and expr nodes (the latter of which will be labelled with its expression). Nothing will change, because the rectangle has not been set. Edit the rect node and draw a rectangle by clicking and dragging on the canvas. Set the scale to 5 pixels and type \"671_438\" in the Annotation box , something like this: Figure: R671_438 with a rectangle. Click on image to expand. Now the expr node shows this - only the rectangle has the 671/438 parameter, while the rest of the image shows the 671nm band. To see this, click on the expr tab : Figure: R671_438 with a rectangle. Click on image to expand. The image is dark, because the values of the parameter shown in the rectangle are much greater than those of the 671nm band. It can be hard to adjust the rectangle when you can't see what's happening in the final image - try undocking the rect node and dragging it to a different part of the screen by double-clicking on the tab, so you can edit the rectangle while the expr output is visible. Dock it again by closing the undocked window . To make things more visible still, open the data section and add a gradient node after the expr node and view it , clicking making sure Show ROIs is enabled on its canvas (ROIs are typically retained on derived images): Figure: R671_438 with a rectangle and gradient. Click on image to expand. There's not much to see because of the nature of the image, unfortunately! Note that there is a \"legend\" at top left, and this can be edited and placed by clicking on the Legend button : Figure: R671_438 with a better gradient legend. Click on image to expand. The colour palette for the gradient, incidentally, is the \"viridis\" palette which is helpful for people with certain kinds of colour blindness 2 . Other presets are available by clicking on the Load Preset button, or you can edit the gradient by hand: double-click to add or remove a point, drag to move a point. It is possible to \"inset\" the gradient into the RGB representation used in the input node by passing that to the insetinto input of the gradient: Figure: R671_438 inset into source image RGB representation. Click on image to expand. If you switch to the Legend part of the gradient node and select Left margin for the Legend item , when you export the image with the canvas' Export Image button the legend will be in the margin: Figure: Exported R671_438 (svg). Click on image to expand. Allender, E. J., C. R. Cousins, M. D. Gunn, and C. M. Caudill. \"Multiscale and multispectral characterization of mineralogy with the ExoMars 2022 rover remote sensing payload.\" Earth and Space Science 7, no. 4 (2020): e2019EA000692. \u21a9 Simon Garnier, Noam Ross, Robert Rudis, Ant\u00f4nio P. Camargo, Marco Sciaini, and C\u00e9dric Scherer (2021). viridis(Lite) - Colorblind-Friendly Color Maps for R. viridis package version 0.6.2. \u21a9","title":"Adding a region of interest"},{"location":"gettingstarted/tutorial2/","text":"Tutorial part 2 Mathematical operations Performing operations on parts of an image Calculating a spectrum","title":"Tutorial part 2"},{"location":"gettingstarted/tutorial2/#tutorial-part-2","text":"","title":"Tutorial part 2"},{"location":"gettingstarted/tutorial2/#mathematical-operations","text":"","title":"Mathematical operations"},{"location":"gettingstarted/tutorial2/#performing-operations-on-parts-of-an-image","text":"","title":"Performing operations on parts of an image"},{"location":"gettingstarted/tutorial2/#calculating-a-spectrum","text":"","title":"Calculating a spectrum"},{"location":"userguide/","text":"Overview These pages are a reference guide to the PCOT application. If you want to read about using PCOT as a Python library, documentation is available in the Developer's Guide . At the moment, this consists of in-depth pages on particular aspects of PCOT. For introductory information see the Getting Started section, which will tell you how to install and run PCOT and give you a tutorial covering the basics. Contents Operating principles Global controls on the PCOT UI. The canvas and its optional views Special topics Using the multifile input to read multispectral data from BMP, PNG etc. Using the expr node to perform mathematical operations Autodocs Automatically generated documentation for nodes, expression functions and expression properties.","title":"Overview"},{"location":"userguide/#overview","text":"These pages are a reference guide to the PCOT application. If you want to read about using PCOT as a Python library, documentation is available in the Developer's Guide . At the moment, this consists of in-depth pages on particular aspects of PCOT. For introductory information see the Getting Started section, which will tell you how to install and run PCOT and give you a tutorial covering the basics.","title":"Overview"},{"location":"userguide/#contents","text":"Operating principles Global controls on the PCOT UI. The canvas and its optional views","title":"Contents"},{"location":"userguide/#special-topics","text":"Using the multifile input to read multispectral data from BMP, PNG etc. Using the expr node to perform mathematical operations","title":"Special topics"},{"location":"userguide/#autodocs","text":"Automatically generated documentation for nodes, expression functions and expression properties.","title":"Autodocs"},{"location":"userguide/canvas/","text":"The Canvas and its optional views Most nodes and inputs use a canvas to display some bands of an image as RGB. This will take up a large proportion of their view - in some cases all of it. It is worth discussing in some detail. Another is shown below as the entire control area for an actual input node, which brings one of the four inputs into the graph. Figure: An open *input 0* node. Click on image to expand. You can pan the canvas using the scroll bars at the edge, and zoom with the mouse wheel. The button at bottom right will reset to show the entire image. To the left of the canvas image itself is the canvas control area. This has quite a lot in it, so is scrollable with collapsible sections, rather like the palette. At the top - and not collapsible - are three combo boxes which determine the image mapping : how the bands within the (probably) multispectral image map onto RGB channels for viewing. Each band in the combo box shows the input number, a colon, and typically the name, position or wavelength of the band. Exactly what is shown depends on the image being loaded and the Caption global control . The Guess RGB button tries to guess appropriate channels for the RGB canvas image. Data section This is the first of the collapsible sections. Show ROIs will mark any regions of interest the image has - these are added using nodes like rect (for a rectangle) and are carried forward through subsequent nodes (where appropriate), delimiting the area upon which calculations are performed. They also control the regions used for calculating spectra. Normally an ROI is only shown in the node which adds it. Show spectrum opens a side pane, and dragging the cursor across the image will plot the spectrum of the pixel under the cursor in this pane. If no filter wavelengths are available, a list of the values for each band is shown. The show spectrum pane looks like this: The screenshot isn't showing the cursor, unfortunately, but the spectrum is for the pixel under the cursor. Figure: The \"show spectrum\" pane on an input node's canvas. Click on image to expand. When a spectrum view is opened the image pane can be tiny - to fix this you can resize the PCOT window (or undocked node window), or drag the separator between the image and the spectrum (the two vertical bars). Each dot is shown with an approximation of its wavelength colour (using Dan Bruton's algorithm ) or black if the wavelength is not visible. Export image saves the RGB-mapped image as a PDF, SVG or PNG. If a gradient has been plotted using the gradient node, it may add a legend to the image. Cursor coordinates are shown next, followed by number of pixels and ROIs in the image and the image dimensions (width x height x bands). The hide DQ disables the data quality overlays and hides their sections - data quality can be slow to draw. Normalisation section Nominally, the canvas channels are 0-1 with 0 being zero intensity and 1 being full intensity (so RGB 1,1,1 is white). Values outside the 0-1 range are clamped. However, this can be changed. The norm box selects the normalisation range: to all bands means that the normalisation range of each band for each pixel is the minimum and maximum of all the bands as a whole. to RGB means that the normalisation range is taken from the channels mapped to RGB in the canvas. independent means that each band is normalised independently, to its own range. none means no normalisation is done. If to cropped area is on, the range used for normalisation is taken from the portion of the image visible in the canvas. Data quality layers Each image has a set of data quality bits and an uncertainty value associated with each pixel of each band. Viewing this can be challenging. We make three \"layers\" of DQ data available: each works the same way: SRC specifies which band we are viewing the DQ for. If max is specified, the maximum uncertainty across all bands is used. If sum , then the sum of the uncertainties is used. If a DQ bit is being shown, the intersection of those bits across all bands is used for both max and sum . DATA specifies what data is being shown: NONE specifies that the DQ layer is inactive. BIT:name options specify a particular DQ bit. These are subject to change, but are likely to include nodata , nounc (no uncertainty data), sat (saturated). UNC specifies that the uncertainty data should be shown. UNC>THRESH specifies that the layer should be full intensity for pixels where the uncertainty is above a threshold (see below). UNCTHRESH specifies that the layer should be full intensity for pixels where the uncertainty is above a threshold (see below). UNCL|R)WAC(?P[0-9][0-9]).* This means: .* matches any number of any character, so there could be anything at the start of the filename L|R means \"either L or R \" - but we have specified a \"named match\" with brackets and the ?P<..> notation: (?PL|R) . This means \"either L or R and store the result under the name lens \". WAC means we must then have the sequence of letters WAC (?P[0-9][0-9]) means we must now match two digits ( [0-9] ) and store them as n The final .* means that there can now be any number of any character again - so there could be anything at the end of the filename. The idea is that a filename like /home/jim/files/DogBiscuitLWAC02Fish.jpg will be matched, and will result in L being stored as lens and 02 being stored as n . Named matches and how they are used Only one of the following should be true (e.g. you can't use name and n together): lens and n : if these are found, they are joined together to form a filter position which is looked up in the filter set (by the position column). The idea is that lens indicates either the left or right camera and n identifies a filter. name : if this is found, it is used to match a filter using the name column cwl : if this is found, it is used to match a filter using the cwl (wavelength) column If you need assistance, or this isn't flexible enough, contact us.","title":"Reading images from multiple files"},{"location":"userguide/multifile/#the-multifile-input-method","text":"Documentation for the other methods is forthcoming. The regex aspect of multifile is complicated, so documenting it was a priority. The multifile input method allows multiple monochrome files of \"standard\" types like PNG and BMP to be imported as multispectral images. Each file is flattened to greyscale in the process if it is RGB by finding the mean of the three channels for each pixel. For this to work, PCOT needs to be able to find out which filter was used for each image. This is done by storing a set of filters in a CSV file and using parts of the image files' names to work out which filter is used.","title":"The Multifile input method"},{"location":"userguide/multifile/#filter-csv-files","text":"Two files are available by default. These are PANCAM (for the actual ExoMars camera) and AUPE (Aberystwyth PANCAM Emulator). These can be found in the src/pcot/assets/ directory. Here's an example of part of such a file: cwl,fwhm,transmission,position,name 570,12,0.989,L01,G04 530,15,0.957,L02,G03 610,10,0.956,L03,G05 It should be fairly obvious. If you need to add a new filter set, edit the .pcot.ini file in your home directory and a [filters] block if one does not exist. To this block, add a line like this: [filters] myfilterset=filename For example [filters] JCFCAM=c:/users/jim/jimfilters.csv This new set, called JCFCAM, will be loaded when PCOT is started.","title":"Filter CSV files"},{"location":"userguide/multifile/#setting-a-file-pattern","text":"The file pattern is a regular expression used to work out which filter is used for which image. If you have some experience with regular expressions (or access to someone with this experience), it will help immensely . Regular expressions describe patterns which texts might match. For example, the regex c[a-z]t will match any three-letter string starting with c and ending with t . (Beginner's guide)[ https://www.regular-expressions.info/index.html ] (Useful 'playground' to try out expressions)[ https://regex101.com/ ] We can also use a regex to extract parts of the string. The default pattern looks something like this: .*(?PL|R)WAC(?P[0-9][0-9]).* This means: .* matches any number of any character, so there could be anything at the start of the filename L|R means \"either L or R \" - but we have specified a \"named match\" with brackets and the ?P<..> notation: (?PL|R) . This means \"either L or R and store the result under the name lens \". WAC means we must then have the sequence of letters WAC (?P[0-9][0-9]) means we must now match two digits ( [0-9] ) and store them as n The final .* means that there can now be any number of any character again - so there could be anything at the end of the filename. The idea is that a filename like /home/jim/files/DogBiscuitLWAC02Fish.jpg will be matched, and will result in L being stored as lens and 02 being stored as n .","title":"Setting a file pattern"},{"location":"userguide/multifile/#named-matches-and-how-they-are-used","text":"Only one of the following should be true (e.g. you can't use name and n together): lens and n : if these are found, they are joined together to form a filter position which is looked up in the filter set (by the position column). The idea is that lens indicates either the left or right camera and n identifies a filter. name : if this is found, it is used to match a filter using the name column cwl : if this is found, it is used to match a filter using the cwl (wavelength) column If you need assistance, or this isn't flexible enough, contact us.","title":"Named matches and how they are used"},{"location":"userguide/principles/","text":"Operating principles PCOT nodes need to follow a set of rules to ensure that the information they process is handled consistently. This page describes these rules, most of which apply to image data. Source handling rules Each datum handled by PCOT has a \"source set\" describing where that datum ultimately comes from. Sources vary: in images, each band in an input image carries a source datum describing where it comes from. For a PDS4 source it could be a LIDVID, or it could simply be a filename (although ideally it should have some archive indexing data). Because data can be combined in various ways, each datum could have multiple sources. Image data in PCOT have a separate source set for each band. The rules for sources are simple: Every datum has a source set. This may be a null source if the datum is generated internally (consider the output from constant nodes). In the case of an image, this source set is actually a separate source set for each band, but may still be considered as a single source set for some operations (since the union of the band sets is available). If a datum, or band in a multi-band image, is constructed from data from more than once source set, the resulting datum or band consists of the union of the sets. As an example, consider the rather contrived example below. Here, each datum is marked as a black circle below which is a description of the the sources - there's more explanation of this below. image inputs are in yellow scalar inputs are in blue nodes are white rectangles expr nodes (which calculate mathematical expressions) are marked as such with the expression being the main text Figure: An example graph.. Click on image to expand. We have three inputs into the graph: Input 0 has an image with three bands centered on 640nm, 540nm and 440nm. Input 1 has a different image with four bands. Input 2 has a numeric value of 0.2 (perhaps a housekeeping datum of some kind). These data are then combined in various ways: Input 1 is converted to a single greyscale band and multiplied by input 2 (the scalar 0.2) The result of this operation is then added to the 540nm band of input 0. What do the sources look like for each datum? Datum A is an image, and as such it has one source set per band. Each source set consists of a single source, with details of input and filter wavelength. So here, the sources for A could be written as [ {0:640}, {0:540}, {0:440} ] That is, a list of three sets, each of which contains a single source which I've written in the form input:wavelength . Datum B is the extracted 540nm band of input A, so its sources are: [ {0:540} ] Datum C is another multiband image, this time from input 1: [ {1:780}, {1:640}, {1:540}, {1:440} ] Datum D is the greyscale conversion of Datum C above. This creates a single-band image, but that band combines all the bands of datum C. This results in a single source set containing all the bands: [ {1:780, 1:640, 1:540, 1:440} ] Datum E is the only non-image source. I will denote it here as just \"2\" indicating that it is input 2. It is worth noting here that sources may contain a lot of extra data describing exactly where they come from. For example, this input may come from PDS4, in which case at least the LIDVID will be included. But for now this is simply {2} Note that this is not shown as a list because it is not a multiband source. Datum F multiplies each band in datum D by the scalar E. This means that the source for E must be added to the source set for each band. There is only one band at this point because of the greyscale conversion: [ {1:780, 1:640, 1:540, 1:440, 2} ] Finally, we add the single-band images B and F together, resulting in another single-band image. This addition is done bandwise. There is only one band, so the result is: [ {1:780, 1:640, 1:540, 1:440, 2, 0:540} ] ROI rules Images may contain regions of interest. If this is the case, then any operation should only be performed on the region of interest if possible. However, the rest of the image should be passed through unchanged to provide context - it is always possible to use a croproi node before the operation if cropping is required. This rule has the following practical outcomes, in which images are denoted by capitals A, B, \\dots A, B, \\dots and scalars are denoted by lowercase x, y. \\dots x, y. \\dots So: In binary operations on an image and a scalar such as x+A x+A or A+x A+x , the operation is only performed on the region covered by the union of all ROIs on A A . Other parts of A A are left unchanged in the output, which carries the same ROIs. In binary operations on two images A+B A+B etc., there should be only one set of ROIs in operation. This means that either only one image has an ROI, or the sets of ROIs for both images are identical. Outside the ROI, the image pixels are taken from the left-hand side of the operator as shown in the figure below. Figure: Two images A and B, B has an ROI. In the result, the area covered by the ROI is A+B, the rest of the image is passed through from A (which has no ROI). Click on image to expand. Originally the rule was that the intersection of the unions of the ROIs would be processed, while the rest of the output would be from the left-hand side (in the case of image data). That was too complicated, and broke the \"principle of least astonishment.\" If the user has put different ROIs on both sides of their image they have probably made a mistake. However, it's quite possible for the same ROIs to be on both sides of an expression if they were derived from the same image. Quality data All values in PCOT - scalar and image - can have an uncertainty value and data quality (DQ) bits. In imagecubes, this applies to every pixel of every band. The uncertainty values are expressed as standard deviations. Operations need to combine these data in a sensible way. This is currently implemented using the Value class. Consists of: image uncertainty map (float per band) image quality/info bits (uint16 per band) Uncertainty Uncertainty values are propagated through calculations in the expr node and elsewhere according to these rules: \\begin{align} \\sigma(a+b), \\quad \\sigma(a-b) &= \\sqrt{\\sigma^2 a + \\sigma^2 b}\\\\ \\sigma(ab) &= |ab| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}\\quad \\text{when all values positive}\\\\ \\sigma(a/b) &= \\left|\\frac{a}{b}\\right| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\frac{\\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}}{b^2}\\quad \\text{when all values positive}\\\\ \\sigma(a^b) &= \\sqrt{ (a^{b-1} b \\sigma a)^2 + (a^b \\log a \\sigma b)^2} \\\\ &= \\sqrt{a^{2b-2} ((a \\sigma b \\log a)^2 + (b \\sigma a)^2)}\\\\ \\sigma (a \\& b) &= \\min (\\sigma a, \\sigma b)\\\\ \\sigma (a | b) &= \\max (\\sigma a, \\sigma b) \\end{align} \\begin{align} \\sigma(a+b), \\quad \\sigma(a-b) &= \\sqrt{\\sigma^2 a + \\sigma^2 b}\\\\ \\sigma(ab) &= |ab| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}\\quad \\text{when all values positive}\\\\ \\sigma(a/b) &= \\left|\\frac{a}{b}\\right| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\frac{\\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}}{b^2}\\quad \\text{when all values positive}\\\\ \\sigma(a^b) &= \\sqrt{ (a^{b-1} b \\sigma a)^2 + (a^b \\log a \\sigma b)^2} \\\\ &= \\sqrt{a^{2b-2} ((a \\sigma b \\log a)^2 + (b \\sigma a)^2)}\\\\ \\sigma (a \\& b) &= \\min (\\sigma a, \\sigma b)\\\\ \\sigma (a | b) &= \\max (\\sigma a, \\sigma b) \\end{align} Remember that generally these rules only apply if the bands are independent. In reality there is always a covariance between them, but we have to ignore that. Not all images contain uncertainty data - it may be that the input image doesn't have this data, or that a function has been performed on the image which does not permit uncertainty data to be carried through (consider a decorrelation stretch, for example). This can be viewed by selecting the nounc (no uncertainty) bit for viewing in the canvas . DQ bits Each scalar, or band value for each pixel, has an associated set of bits which indicate error states, etc. Bits are currently: Bit name Meaning Effect on calculations (see notes below) NODATA There is no data here BAD SAT Pixel is saturated high in this band BAD DIVZERO The data is the result of a division by zero BAD UNDEF The data is the result of an undefined operation BAD COMPLEX The data is a complex number likely to be undefined (or just the real part) BAD ERROR There is an unspecified error in this data BAD NOUNCERTAINTY There is no uncertainty data All bits are propagated into the data generated from the data they are attached to. BAD means that the data in these pixels should not be considered. While calculations will still be done on BAD data, the BAD bits will be propagated. In the case of nodes which generate a scalar from images, such as finding the mean or SD of a set of pixels (or similar operations in the spectrum node) the pixels marked BAD should be ignored. It is possible to set bits based on per-pixel conditions with the dqmod node. For example, convert all uncertainties greater than a given value into errors. It is possible to convert DQ bits into regions of interest using the roidq node, with the region being made up of pixels for which certain bits are absent or present. This can be done looking at all bands or just one. In general, when multiple image bands are combined (either from the same image or from different images) these are OR-ed together. This typically happens in a band-wise fashion because images are combined band-wise. Thus, when two images a a and b b are added, and the bits for channel i i of image a a are B_i(a) B_i(a) , \\[ B_i(a+b) = B_i(a) \\vee B_i(b)\\quad \\text{for all channels } i \\] However, some operations have a more complex flow of information. For example, a decorrelation stretch results in information from all bands being used in each band. In cases like this, the resulting bands are all ORed toether: \\[ B_i(\\text{decorr}(a)) = \\bigvee_i B_i(a) \\] UNIMPLEMENTED BIT OPERATIONS Nodes which perform a convolution operation or similar should propagate the error pixel to all affected pixels, leading to a blob of pixels in the output. I realise This isn't ideal ; another possibility could be to just zero the mask? But then we lose the error data. At the moment I don't believe we have any \"non-local\" behaviour where pixels affect regions of pixels in the output, so the point could be moot. Filter aberration UNIMPLEMENTED The filter wavelengths are only accurate for pixels in the centre of the image, due to the difference in the light path through the filter at different angles of incidence. Therefore: There will be a system in place to calculate the actual filter wavelength for a given pixel and use this in spectral plots (using the centre of the ROI for the spectrum node) A function should be available to generate the filter aberration value in expr - this would allow an \"image\" to be made of the aberration value which could be used in calculations It should be possible to set the ERROR bit for excessive aberration values Canvas information UNIMPLEMENTED The following should be visible in the canvas as optional overlays: Filter aberration as a heat map (default OFF)","title":"Operating principles"},{"location":"userguide/principles/#operating-principles","text":"PCOT nodes need to follow a set of rules to ensure that the information they process is handled consistently. This page describes these rules, most of which apply to image data.","title":"Operating principles"},{"location":"userguide/principles/#source-handling-rules","text":"Each datum handled by PCOT has a \"source set\" describing where that datum ultimately comes from. Sources vary: in images, each band in an input image carries a source datum describing where it comes from. For a PDS4 source it could be a LIDVID, or it could simply be a filename (although ideally it should have some archive indexing data). Because data can be combined in various ways, each datum could have multiple sources. Image data in PCOT have a separate source set for each band. The rules for sources are simple: Every datum has a source set. This may be a null source if the datum is generated internally (consider the output from constant nodes). In the case of an image, this source set is actually a separate source set for each band, but may still be considered as a single source set for some operations (since the union of the band sets is available). If a datum, or band in a multi-band image, is constructed from data from more than once source set, the resulting datum or band consists of the union of the sets. As an example, consider the rather contrived example below. Here, each datum is marked as a black circle below which is a description of the the sources - there's more explanation of this below. image inputs are in yellow scalar inputs are in blue nodes are white rectangles expr nodes (which calculate mathematical expressions) are marked as such with the expression being the main text Figure: An example graph.. Click on image to expand. We have three inputs into the graph: Input 0 has an image with three bands centered on 640nm, 540nm and 440nm. Input 1 has a different image with four bands. Input 2 has a numeric value of 0.2 (perhaps a housekeeping datum of some kind). These data are then combined in various ways: Input 1 is converted to a single greyscale band and multiplied by input 2 (the scalar 0.2) The result of this operation is then added to the 540nm band of input 0. What do the sources look like for each datum? Datum A is an image, and as such it has one source set per band. Each source set consists of a single source, with details of input and filter wavelength. So here, the sources for A could be written as [ {0:640}, {0:540}, {0:440} ] That is, a list of three sets, each of which contains a single source which I've written in the form input:wavelength . Datum B is the extracted 540nm band of input A, so its sources are: [ {0:540} ] Datum C is another multiband image, this time from input 1: [ {1:780}, {1:640}, {1:540}, {1:440} ] Datum D is the greyscale conversion of Datum C above. This creates a single-band image, but that band combines all the bands of datum C. This results in a single source set containing all the bands: [ {1:780, 1:640, 1:540, 1:440} ] Datum E is the only non-image source. I will denote it here as just \"2\" indicating that it is input 2. It is worth noting here that sources may contain a lot of extra data describing exactly where they come from. For example, this input may come from PDS4, in which case at least the LIDVID will be included. But for now this is simply {2} Note that this is not shown as a list because it is not a multiband source. Datum F multiplies each band in datum D by the scalar E. This means that the source for E must be added to the source set for each band. There is only one band at this point because of the greyscale conversion: [ {1:780, 1:640, 1:540, 1:440, 2} ] Finally, we add the single-band images B and F together, resulting in another single-band image. This addition is done bandwise. There is only one band, so the result is: [ {1:780, 1:640, 1:540, 1:440, 2, 0:540} ]","title":"Source handling rules"},{"location":"userguide/principles/#roi-rules","text":"Images may contain regions of interest. If this is the case, then any operation should only be performed on the region of interest if possible. However, the rest of the image should be passed through unchanged to provide context - it is always possible to use a croproi node before the operation if cropping is required. This rule has the following practical outcomes, in which images are denoted by capitals A, B, \\dots A, B, \\dots and scalars are denoted by lowercase x, y. \\dots x, y. \\dots So: In binary operations on an image and a scalar such as x+A x+A or A+x A+x , the operation is only performed on the region covered by the union of all ROIs on A A . Other parts of A A are left unchanged in the output, which carries the same ROIs. In binary operations on two images A+B A+B etc., there should be only one set of ROIs in operation. This means that either only one image has an ROI, or the sets of ROIs for both images are identical. Outside the ROI, the image pixels are taken from the left-hand side of the operator as shown in the figure below. Figure: Two images A and B, B has an ROI. In the result, the area covered by the ROI is A+B, the rest of the image is passed through from A (which has no ROI). Click on image to expand. Originally the rule was that the intersection of the unions of the ROIs would be processed, while the rest of the output would be from the left-hand side (in the case of image data). That was too complicated, and broke the \"principle of least astonishment.\" If the user has put different ROIs on both sides of their image they have probably made a mistake. However, it's quite possible for the same ROIs to be on both sides of an expression if they were derived from the same image.","title":"ROI rules"},{"location":"userguide/principles/#quality-data","text":"All values in PCOT - scalar and image - can have an uncertainty value and data quality (DQ) bits. In imagecubes, this applies to every pixel of every band. The uncertainty values are expressed as standard deviations. Operations need to combine these data in a sensible way. This is currently implemented using the Value class. Consists of: image uncertainty map (float per band) image quality/info bits (uint16 per band)","title":"Quality data"},{"location":"userguide/principles/#uncertainty","text":"Uncertainty values are propagated through calculations in the expr node and elsewhere according to these rules: \\begin{align} \\sigma(a+b), \\quad \\sigma(a-b) &= \\sqrt{\\sigma^2 a + \\sigma^2 b}\\\\ \\sigma(ab) &= |ab| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}\\quad \\text{when all values positive}\\\\ \\sigma(a/b) &= \\left|\\frac{a}{b}\\right| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\frac{\\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}}{b^2}\\quad \\text{when all values positive}\\\\ \\sigma(a^b) &= \\sqrt{ (a^{b-1} b \\sigma a)^2 + (a^b \\log a \\sigma b)^2} \\\\ &= \\sqrt{a^{2b-2} ((a \\sigma b \\log a)^2 + (b \\sigma a)^2)}\\\\ \\sigma (a \\& b) &= \\min (\\sigma a, \\sigma b)\\\\ \\sigma (a | b) &= \\max (\\sigma a, \\sigma b) \\end{align} \\begin{align} \\sigma(a+b), \\quad \\sigma(a-b) &= \\sqrt{\\sigma^2 a + \\sigma^2 b}\\\\ \\sigma(ab) &= |ab| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}\\quad \\text{when all values positive}\\\\ \\sigma(a/b) &= \\left|\\frac{a}{b}\\right| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\frac{\\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}}{b^2}\\quad \\text{when all values positive}\\\\ \\sigma(a^b) &= \\sqrt{ (a^{b-1} b \\sigma a)^2 + (a^b \\log a \\sigma b)^2} \\\\ &= \\sqrt{a^{2b-2} ((a \\sigma b \\log a)^2 + (b \\sigma a)^2)}\\\\ \\sigma (a \\& b) &= \\min (\\sigma a, \\sigma b)\\\\ \\sigma (a | b) &= \\max (\\sigma a, \\sigma b) \\end{align} Remember that generally these rules only apply if the bands are independent. In reality there is always a covariance between them, but we have to ignore that. Not all images contain uncertainty data - it may be that the input image doesn't have this data, or that a function has been performed on the image which does not permit uncertainty data to be carried through (consider a decorrelation stretch, for example). This can be viewed by selecting the nounc (no uncertainty) bit for viewing in the canvas .","title":"Uncertainty"},{"location":"userguide/principles/#dq-bits","text":"Each scalar, or band value for each pixel, has an associated set of bits which indicate error states, etc. Bits are currently: Bit name Meaning Effect on calculations (see notes below) NODATA There is no data here BAD SAT Pixel is saturated high in this band BAD DIVZERO The data is the result of a division by zero BAD UNDEF The data is the result of an undefined operation BAD COMPLEX The data is a complex number likely to be undefined (or just the real part) BAD ERROR There is an unspecified error in this data BAD NOUNCERTAINTY There is no uncertainty data All bits are propagated into the data generated from the data they are attached to. BAD means that the data in these pixels should not be considered. While calculations will still be done on BAD data, the BAD bits will be propagated. In the case of nodes which generate a scalar from images, such as finding the mean or SD of a set of pixels (or similar operations in the spectrum node) the pixels marked BAD should be ignored. It is possible to set bits based on per-pixel conditions with the dqmod node. For example, convert all uncertainties greater than a given value into errors. It is possible to convert DQ bits into regions of interest using the roidq node, with the region being made up of pixels for which certain bits are absent or present. This can be done looking at all bands or just one. In general, when multiple image bands are combined (either from the same image or from different images) these are OR-ed together. This typically happens in a band-wise fashion because images are combined band-wise. Thus, when two images a a and b b are added, and the bits for channel i i of image a a are B_i(a) B_i(a) , \\[ B_i(a+b) = B_i(a) \\vee B_i(b)\\quad \\text{for all channels } i \\] However, some operations have a more complex flow of information. For example, a decorrelation stretch results in information from all bands being used in each band. In cases like this, the resulting bands are all ORed toether: \\[ B_i(\\text{decorr}(a)) = \\bigvee_i B_i(a) \\]","title":"DQ bits"},{"location":"userguide/principles/#unimplemented-bit-operations","text":"Nodes which perform a convolution operation or similar should propagate the error pixel to all affected pixels, leading to a blob of pixels in the output. I realise This isn't ideal ; another possibility could be to just zero the mask? But then we lose the error data. At the moment I don't believe we have any \"non-local\" behaviour where pixels affect regions of pixels in the output, so the point could be moot.","title":"UNIMPLEMENTED BIT OPERATIONS"},{"location":"userguide/principles/#filter-aberration-unimplemented","text":"The filter wavelengths are only accurate for pixels in the centre of the image, due to the difference in the light path through the filter at different angles of incidence. Therefore: There will be a system in place to calculate the actual filter wavelength for a given pixel and use this in spectral plots (using the centre of the ROI for the spectrum node) A function should be available to generate the filter aberration value in expr - this would allow an \"image\" to be made of the aberration value which could be used in calculations It should be possible to set the ERROR bit for excessive aberration values","title":"Filter aberration UNIMPLEMENTED"},{"location":"userguide/principles/#canvas-information-unimplemented","text":"The following should be visible in the canvas as optional overlays: Filter aberration as a heat map (default OFF)","title":"Canvas information UNIMPLEMENTED"},{"location":"userguide/uncs/","text":"Uncertainties \\begin{align} \\sigma(a+b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(a-b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(ab) &= \\sqrt{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}\\\\ \\sigma(a/b) &= \\sqrt{\\frac{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}{b^4}}\\\\ \\sigma(a^b) &= \\sqrt{a^{2b-2} (a^2 \\sigma_b^2 (\\ln{a})^2 + b^2 \\sigma_a^2)} \\end{align} \\begin{align} \\sigma(a+b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(a-b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(ab) &= \\sqrt{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}\\\\ \\sigma(a/b) &= \\sqrt{\\frac{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}{b^4}}\\\\ \\sigma(a^b) &= \\sqrt{a^{2b-2} (a^2 \\sigma_b^2 (\\ln{a})^2 + b^2 \\sigma_a^2)} \\end{align}","title":"Uncertainties"},{"location":"userguide/uncs/#uncertainties","text":"\\begin{align} \\sigma(a+b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(a-b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(ab) &= \\sqrt{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}\\\\ \\sigma(a/b) &= \\sqrt{\\frac{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}{b^4}}\\\\ \\sigma(a^b) &= \\sqrt{a^{2b-2} (a^2 \\sigma_b^2 (\\ln{a})^2 + b^2 \\sigma_a^2)} \\end{align} \\begin{align} \\sigma(a+b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(a-b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(ab) &= \\sqrt{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}\\\\ \\sigma(a/b) &= \\sqrt{\\frac{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}{b^4}}\\\\ \\sigma(a^b) &= \\sqrt{a^{2b-2} (a^2 \\sigma_b^2 (\\ln{a})^2 + b^2 \\sigma_a^2)} \\end{align}","title":"Uncertainties"}]}
\ No newline at end of file
+{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"PCOT - the PanCam Operations Toolkit PCOT is a Python program and library which allows users to manipulate multispectral images and other data from the ExoMars Rosalind Franklin rover. This is an alpha version with serious limitations. There is no calibration code of any kind (although the preliminaries are in place) PDS4 import capabilities are poor - we only support spec-rad products from the ExoMars PANCAM instrument - but we also support ENVI provided the images are 32-bit float BSQ RGB PNGs multispectral images made of multiple monochrome PNGs multispectral images made of monochrome raw files in various formats and adding new PDS4 formats should be relatively straightforward There are probably a lot of useful operations missing There are certainly a lot of bugs. Getting Started User Guide Dev Roadmap Automatically generated node and function docs Reporting bugs There are known issues which can stop PCOT running - see issues . If your problem isn't described there please create a new issue on Github or contact the Aberystwyth team. Release history Can be found here","title":"PCOT - the PanCam Operations Toolkit"},{"location":"#pcot-the-pancam-operations-toolkit","text":"PCOT is a Python program and library which allows users to manipulate multispectral images and other data from the ExoMars Rosalind Franklin rover. This is an alpha version with serious limitations. There is no calibration code of any kind (although the preliminaries are in place) PDS4 import capabilities are poor - we only support spec-rad products from the ExoMars PANCAM instrument - but we also support ENVI provided the images are 32-bit float BSQ RGB PNGs multispectral images made of multiple monochrome PNGs multispectral images made of monochrome raw files in various formats and adding new PDS4 formats should be relatively straightforward There are probably a lot of useful operations missing There are certainly a lot of bugs. Getting Started User Guide Dev Roadmap Automatically generated node and function docs","title":"PCOT - the PanCam Operations Toolkit"},{"location":"#reporting-bugs","text":"There are known issues which can stop PCOT running - see issues . If your problem isn't described there please create a new issue on Github or contact the Aberystwyth team.","title":"Reporting bugs"},{"location":"#release-history","text":"Can be found here","title":"Release history"},{"location":"releases/","text":"Releases Production releases None Beta releases None Alpha releases 0.7.0-alpha 2024-05-03 EAST PENTIRE Very many more unit tests Bug fixes Complete rewrite of spectrum system, using the SpectrumSet object Multidot now does painted regions and floodfill Joseph's PCT detector outputs image with ROIs Dump removed and sink enhanced TabData shows sources Inputs decoupled from Sources - Sources now use composition, not inheritance Comment box for nodes removed (it was never used) Direct multifile loading Direct PDS4 loading - required refactoring of entire PDS4 layer Direct ENVI loading Raw file loading from mono images supporting lots of formats Loader presets for multifile Operator overloading on Datum objects The \"datumfunc\" system replacing hand-registration of functions flip and rotate functions (datumfuncs) String datum objects and strings usable in expr Docs on library usage Changes to nodes so that slow nodes can be disabled and very slow nodes start disabled. This functionality existed before, but was \"ad-hoc\" Document.changed() is now Document.run() and forces disabled nodes to run Most nodes now store data in their outputs rather than a \"node.out\" which is then written to an output Changes to multidot - doc improvements, UX and bug fixes 0.6.1-alpha 2023-10-04 DYNAS COVE (minor release) Multifile input can accept BMP files Better multifile documentation Filter specifications are no longer hardwired and are loaded from CSVs PANCAM and AUPE filters are default filter sets loaded in Others can be specified in a config file (and can override PANCAM and AUPE) Filter set no longer required by PDS4 input 0.6.0-alpha 2023-09-11 DRIFT STONES uncertainty and error bit propagation in expr and all nodes Testing quality and propagation rules (see Principles ) Test graphs for nodes and other high-level functionality Test nodes for those graphs Tabular output on spectrum and histogram nodes Gen node for test patterns Refactoring of Datum Utility nodes - e.g. roidq for generating an ROI from DQ bits Output enhancements Gradient node can export to PDF Annotations (e.g. text labels) are now drawn on the painter at high res, and have been refactored hugely Annotations use thickness zero by default (the Qt \"cosmetic\" thickness) PCT detector node ROI negation and refactoring of operators roiexpr node for composing ROIs using expressions Crude band depth node (needs work) A lot of bug fixes and regression fixes 0.5.0-alpha 2023-03-08 CARLENNO ROUND Data quality and bit viewing on canvas Palette and canvas interface with collapsable sections Annotations (ROIs, legends) are now drawn onto the canvas rather than the image Export to PDF, SVG and PNG with those hi-res annotations gradient is much simpler, can overlay onto the image and can draw a legend 0.4.0-alpha 2022-11-30 CAER BRAN Annotation system entirely rewritten PDF/PNG/SVG exporter Gradient legend annotation Doc updates 0.3.0-alpha 2022-10-27 BEACON HUT Open source! PDS4 importer with proctools Ad-hoc Spectrum viewer in canvas Significant rewrite of expression execution code, permitting custom types to have operations defined on them Direct input method for library use Improved default RGB mapping in canvas Testing Basics testing Testing of the operating principles (see Principles ) Source rules ROI rules rect node can now be edited numerically circle node can add circular ROIs, which can be edited numerically. 0.2.0-alpha 2022-04-21 ANJARDEN SPRING \"pixel scanning\" on canvases, shows spectrum of pixel when active custom cursor, pixel under cursor highlighted at high zooms text toggle button (currently unused) fixes to example plugin added macos.spec for pyinstaller archive system shows progress when loading each archive element Issue 1 fix (multiple tab closes when main window reinitialised) dynamic type determination for expr output can connect incompatible node outputs to inputs; indicated as red arrows infinite recursion in ROI nodes fix splash screen for Windows/Linux pyinstaller startup (not yet supported on MacOS pyinstaller) custom Datum and connection brush types now easy expr resizing regression fix multiple input buttons after load/resize fix status bar repaints on ui.msg, so it's updated in load and perform context menu on editable text caused a crash (bug in Qt). Workaround. comment boxes 0.1.0-alpha 2022-03-02 ALSIA WELL Initial alpha release outside Aberystwyth","title":"Releases"},{"location":"releases/#releases","text":"","title":"Releases"},{"location":"releases/#production-releases","text":"None","title":"Production releases"},{"location":"releases/#beta-releases","text":"None","title":"Beta releases"},{"location":"releases/#alpha-releases","text":"","title":"Alpha releases"},{"location":"releases/#070-alpha-2024-05-03-east-pentire","text":"Very many more unit tests Bug fixes Complete rewrite of spectrum system, using the SpectrumSet object Multidot now does painted regions and floodfill Joseph's PCT detector outputs image with ROIs Dump removed and sink enhanced TabData shows sources Inputs decoupled from Sources - Sources now use composition, not inheritance Comment box for nodes removed (it was never used) Direct multifile loading Direct PDS4 loading - required refactoring of entire PDS4 layer Direct ENVI loading Raw file loading from mono images supporting lots of formats Loader presets for multifile Operator overloading on Datum objects The \"datumfunc\" system replacing hand-registration of functions flip and rotate functions (datumfuncs) String datum objects and strings usable in expr Docs on library usage Changes to nodes so that slow nodes can be disabled and very slow nodes start disabled. This functionality existed before, but was \"ad-hoc\" Document.changed() is now Document.run() and forces disabled nodes to run Most nodes now store data in their outputs rather than a \"node.out\" which is then written to an output Changes to multidot - doc improvements, UX and bug fixes","title":"0.7.0-alpha 2024-05-03 EAST PENTIRE"},{"location":"releases/#061-alpha-2023-10-04-dynas-cove-minor-release","text":"Multifile input can accept BMP files Better multifile documentation Filter specifications are no longer hardwired and are loaded from CSVs PANCAM and AUPE filters are default filter sets loaded in Others can be specified in a config file (and can override PANCAM and AUPE) Filter set no longer required by PDS4 input","title":"0.6.1-alpha 2023-10-04 DYNAS COVE (minor release)"},{"location":"releases/#060-alpha-2023-09-11-drift-stones","text":"uncertainty and error bit propagation in expr and all nodes Testing quality and propagation rules (see Principles ) Test graphs for nodes and other high-level functionality Test nodes for those graphs Tabular output on spectrum and histogram nodes Gen node for test patterns Refactoring of Datum Utility nodes - e.g. roidq for generating an ROI from DQ bits Output enhancements Gradient node can export to PDF Annotations (e.g. text labels) are now drawn on the painter at high res, and have been refactored hugely Annotations use thickness zero by default (the Qt \"cosmetic\" thickness) PCT detector node ROI negation and refactoring of operators roiexpr node for composing ROIs using expressions Crude band depth node (needs work) A lot of bug fixes and regression fixes","title":"0.6.0-alpha 2023-09-11 DRIFT STONES"},{"location":"releases/#050-alpha-2023-03-08-carlenno-round","text":"Data quality and bit viewing on canvas Palette and canvas interface with collapsable sections Annotations (ROIs, legends) are now drawn onto the canvas rather than the image Export to PDF, SVG and PNG with those hi-res annotations gradient is much simpler, can overlay onto the image and can draw a legend","title":"0.5.0-alpha 2023-03-08 CARLENNO ROUND"},{"location":"releases/#040-alpha-2022-11-30-caer-bran","text":"Annotation system entirely rewritten PDF/PNG/SVG exporter Gradient legend annotation Doc updates","title":"0.4.0-alpha 2022-11-30 CAER BRAN"},{"location":"releases/#030-alpha-2022-10-27-beacon-hut","text":"Open source! PDS4 importer with proctools Ad-hoc Spectrum viewer in canvas Significant rewrite of expression execution code, permitting custom types to have operations defined on them Direct input method for library use Improved default RGB mapping in canvas Testing Basics testing Testing of the operating principles (see Principles ) Source rules ROI rules rect node can now be edited numerically circle node can add circular ROIs, which can be edited numerically.","title":"0.3.0-alpha 2022-10-27 BEACON HUT"},{"location":"releases/#020-alpha-2022-04-21-anjarden-spring","text":"\"pixel scanning\" on canvases, shows spectrum of pixel when active custom cursor, pixel under cursor highlighted at high zooms text toggle button (currently unused) fixes to example plugin added macos.spec for pyinstaller archive system shows progress when loading each archive element Issue 1 fix (multiple tab closes when main window reinitialised) dynamic type determination for expr output can connect incompatible node outputs to inputs; indicated as red arrows infinite recursion in ROI nodes fix splash screen for Windows/Linux pyinstaller startup (not yet supported on MacOS pyinstaller) custom Datum and connection brush types now easy expr resizing regression fix multiple input buttons after load/resize fix status bar repaints on ui.msg, so it's updated in load and perform context menu on editable text caused a crash (bug in Qt). Workaround. comment boxes","title":"0.2.0-alpha 2022-04-21 ANJARDEN SPRING"},{"location":"releases/#010-alpha-2022-03-02-alsia-well","text":"Initial alpha release outside Aberystwyth","title":"0.1.0-alpha 2022-03-02 ALSIA WELL"},{"location":"roadmap/","text":"Development roadmap This is a rough guide, and things may change! Next major release: 0.8.0 Reorganise the node palette Obtain user stories and feedback Documentation User guide Page on expr nodes documentation for properties of nodes for library use (e.g. expr nodes have \".expr\") How-to for common tasks (yes, still waiting for these) Future releases Calibration: the PCT detection node is fine, but does nothing! Obtain user stories for analysis of HK data (which could potentially get messy, as these are likely to be time series) Consider a vector type It might be useful if functions such as max(), sd() etc. produced a vector of a values rather than a single value in multiband image contexts. For example, a 4-band image with the first channel set to 1 while all others are zero could produce a mean vector of [1,0,0,0]. We would then perform a max() on this vector to get a single value. Preparing for filter aberration and de-hardwiring cameras: Actual values removed from filters.py and put into a config file PANCAM/AUPE camera types no longer hardwired but got from that config Filter aberration parameters added to this config Filter aberration Node (or func??) to convert aberration to image Calculate and process in canvas spectrum Calculate and process in spectrum node","title":"Roadmap"},{"location":"roadmap/#development-roadmap","text":"This is a rough guide, and things may change!","title":"Development roadmap"},{"location":"roadmap/#next-major-release-080","text":"Reorganise the node palette Obtain user stories and feedback Documentation User guide Page on expr nodes documentation for properties of nodes for library use (e.g. expr nodes have \".expr\") How-to for common tasks (yes, still waiting for these)","title":"Next major release: 0.8.0"},{"location":"roadmap/#future-releases","text":"Calibration: the PCT detection node is fine, but does nothing! Obtain user stories for analysis of HK data (which could potentially get messy, as these are likely to be time series) Consider a vector type It might be useful if functions such as max(), sd() etc. produced a vector of a values rather than a single value in multiband image contexts. For example, a 4-band image with the first channel set to 1 while all others are zero could produce a mean vector of [1,0,0,0]. We would then perform a max() on this vector to get a single value. Preparing for filter aberration and de-hardwiring cameras: Actual values removed from filters.py and put into a config file PANCAM/AUPE camera types no longer hardwired but got from that config Filter aberration parameters added to this config Filter aberration Node (or func??) to convert aberration to image Calculate and process in canvas spectrum Calculate and process in spectrum node","title":"Future releases"},{"location":"autodocs/","text":"Autodocs Below are automatically generated documents for certain entities in PCOT. The text in them is extracted from the Python source code, usually from \"docstring\" comments to classes or functions. They are generated by running the generate_autodocs.py script in the mkdocs directory. Nodes Nodes are the entities which make up a PCOT document's graph, taking inputs from various sources and manipulating them in various ways. PCT Patch Detection banddepth circle comment constant contrast stretch croproi crosscalib curve decorr stretch dqmod dummy edge errortest example expr gen gradient histequal histogram importroi in input 0 input 1 input 2 input 3 inset manual register mergetests multidot normimage offset out painted pct pixtest poly rect roidq roiexpr scalartest sink spectrum stitch stringtest striproi tvl1 autoreg Expr functions Below are functions which can be used in the expression evaluation node, expr . name params opt. params (default in brackets) description abs a Calculate absolute value addroi img,r Add an ROI to an image's ROIs clamp img clamp all channels of an image to 0-1 cos a Calculate cosine of an angle in radians crop img,x,y,w,h Crop an image to a rectangle curve img mul (1),add (0) impose a sigmoid curve on an image, y=1/(1+e^-(m(x-0.5)+a))) where m and a are parameters. Note from that equation that x is biased, so that x=0.5 is the inflection point if c=0. dqset img,bits sets DQ bits fliph img Flip an image horizontally flipv img Flip an image vertically grey img opencv (0) Greyscale conversion. If the optional second argument is nonzero, and the image has 3 channels, we'll use CV's conversion equation rather than just the mean. However, this loses uncertainty information. Otherwise uncertainty is calculated by adding together the channels in quadrature and then dividing the number of channels. interp img,factor w (-1) Using trilinear interpolation, generate an image by interpolating between the bands of an existing image. If an ROI is attached, the image generated will be interpolated from the pixels in the ROI. The width of the image will be either given in an optional parameter, or will be the same as the input image. WARNING - IS VERY SLOW makeunc v1,u1,v2,u2 marksat img mn (0),mx (1.0) mark pixels outside a certain range as SAT or ERROR in the DQ bits. Pixels outside any ROI will be ignored, as will any pixels already marked as BAD. max val... find the maximum of the nominal values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. mean val... find the mean\u00b1sd of the values of a set of images and/or scalars. Uncertainties in the data will be pooled. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. merge img1... merge a number of images into multiple bands of a single image. If the image has multiple bands they will all become bands in the new image. min val... find the minimum of the nominal values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. nominal d If input is an image, create an image made up of the nominal (mean) pixel values for all bands - i.e. an image with no uncertainty; if input is numeric, output the nominal value. Ignores ROIs. norm img splitchans (0) normalize all channels of an image to 0-1, operating on all channels combined (the default) or separately resize img,width,height method (linear) Resize an image to a new size using OpenCV's resize function. The method is one of: \"nearest\", \"linear\", \"cubic\", \"area\", \"lanczos4\" mapping to the OpenCV constants cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_LANCZOS rgb img create a 3-channel image consisting of the current RGB mapping of the input image. NOTE: changing the mapping on a node does NOT cause the downstream nodes to run - you will have to click \"Run All\" to make an expr node with rgb() recalculate itself. roi img Extract a single combined ROI from all ROIs on the image. If no ROIs are present, will return a single rectangular ROI covering the entire image. rotate img,angle Rotate an image anti-clockwise by an angle in degrees. The angle must be a multiple of 90 degrees. sd val... find the standard deviation of the values of a set of images and/or scalars. Uncertainties in the data will be pooled. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. setcwl img,cwl Given a 1-band image, create a 'fake' filter with a given centre wavelength and assign it. The transmission of the filter is 1.0, and the fwhm is 30. The image itself is unchanged. This is used in testing only. sin a Calculate sine of an angle in radians sqrt a Calculate square root striproi img Strip all regions of interest from an image sum val... find the sum of the values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. tan a Calculate tangent of an angle in radians testf arg1,arg2 This \"docstring\" is mandatory in datumfuncs, because it contains the description and argument types. The function calculates a+2*b, correctly combining sources and propagating uncertainty. testf2 a b (2) Calculates a+b and a-b, creating a custom object to store that data. testimg index Load a test image uncertainty d If input is an image, create an image made up of uncertainty data for all bands; if input is numeric, output the uncertainty. Ignores ROIs. v n,u dqbits (0) create a new value with uncertainty by combining two values. These can be either numbers or images. \" Ignores and discards ROIs. Expr properties Below are properties which can be used in the expression evaluation node, expr . Properties are names which can be used as identifiers on the right hand side of a \".\" operator, such as a.w to get the width of an image a . name type of x desc x.w img give the width of an image in pixels (if there are ROIs, give the width of the BB of the ROI union) x.w roi give the width of an ROI in pixels x.h img give the height of an image in pixels (if there are ROIs, give the width of the BB of the ROI union) x.h roi give the width of an ROI in pixels x.n img give the area of an image in pixels (if there are ROIs, give the number of pixels in the ROI union) x.n roi give the number of pixels in an ROI","title":"Autodocs"},{"location":"autodocs/#autodocs","text":"Below are automatically generated documents for certain entities in PCOT. The text in them is extracted from the Python source code, usually from \"docstring\" comments to classes or functions. They are generated by running the generate_autodocs.py script in the mkdocs directory.","title":"Autodocs"},{"location":"autodocs/#nodes","text":"Nodes are the entities which make up a PCOT document's graph, taking inputs from various sources and manipulating them in various ways. PCT Patch Detection banddepth circle comment constant contrast stretch croproi crosscalib curve decorr stretch dqmod dummy edge errortest example expr gen gradient histequal histogram importroi in input 0 input 1 input 2 input 3 inset manual register mergetests multidot normimage offset out painted pct pixtest poly rect roidq roiexpr scalartest sink spectrum stitch stringtest striproi tvl1 autoreg","title":"Nodes"},{"location":"autodocs/#expr-functions","text":"Below are functions which can be used in the expression evaluation node, expr . name params opt. params (default in brackets) description abs a Calculate absolute value addroi img,r Add an ROI to an image's ROIs clamp img clamp all channels of an image to 0-1 cos a Calculate cosine of an angle in radians crop img,x,y,w,h Crop an image to a rectangle curve img mul (1),add (0) impose a sigmoid curve on an image, y=1/(1+e^-(m(x-0.5)+a))) where m and a are parameters. Note from that equation that x is biased, so that x=0.5 is the inflection point if c=0. dqset img,bits sets DQ bits fliph img Flip an image horizontally flipv img Flip an image vertically grey img opencv (0) Greyscale conversion. If the optional second argument is nonzero, and the image has 3 channels, we'll use CV's conversion equation rather than just the mean. However, this loses uncertainty information. Otherwise uncertainty is calculated by adding together the channels in quadrature and then dividing the number of channels. interp img,factor w (-1) Using trilinear interpolation, generate an image by interpolating between the bands of an existing image. If an ROI is attached, the image generated will be interpolated from the pixels in the ROI. The width of the image will be either given in an optional parameter, or will be the same as the input image. WARNING - IS VERY SLOW makeunc v1,u1,v2,u2 marksat img mn (0),mx (1.0) mark pixels outside a certain range as SAT or ERROR in the DQ bits. Pixels outside any ROI will be ignored, as will any pixels already marked as BAD. max val... find the maximum of the nominal values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. mean val... find the mean\u00b1sd of the values of a set of images and/or scalars. Uncertainties in the data will be pooled. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. merge img1... merge a number of images into multiple bands of a single image. If the image has multiple bands they will all become bands in the new image. min val... find the minimum of the nominal values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. nominal d If input is an image, create an image made up of the nominal (mean) pixel values for all bands - i.e. an image with no uncertainty; if input is numeric, output the nominal value. Ignores ROIs. norm img splitchans (0) normalize all channels of an image to 0-1, operating on all channels combined (the default) or separately resize img,width,height method (linear) Resize an image to a new size using OpenCV's resize function. The method is one of: \"nearest\", \"linear\", \"cubic\", \"area\", \"lanczos4\" mapping to the OpenCV constants cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_LANCZOS rgb img create a 3-channel image consisting of the current RGB mapping of the input image. NOTE: changing the mapping on a node does NOT cause the downstream nodes to run - you will have to click \"Run All\" to make an expr node with rgb() recalculate itself. roi img Extract a single combined ROI from all ROIs on the image. If no ROIs are present, will return a single rectangular ROI covering the entire image. rotate img,angle Rotate an image anti-clockwise by an angle in degrees. The angle must be a multiple of 90 degrees. sd val... find the standard deviation of the values of a set of images and/or scalars. Uncertainties in the data will be pooled. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. setcwl img,cwl Given a 1-band image, create a 'fake' filter with a given centre wavelength and assign it. The transmission of the filter is 1.0, and the fwhm is 30. The image itself is unchanged. This is used in testing only. sin a Calculate sine of an angle in radians sqrt a Calculate square root striproi img Strip all regions of interest from an image sum val... find the sum of the values of a set of images and/or scalars. Images will be flattened into a list of values, so the result for multiband images may not be what you expect. tan a Calculate tangent of an angle in radians testf arg1,arg2 This \"docstring\" is mandatory in datumfuncs, because it contains the description and argument types. The function calculates a+2*b, correctly combining sources and propagating uncertainty. testf2 a b (2) Calculates a+b and a-b, creating a custom object to store that data. testimg index Load a test image uncertainty d If input is an image, create an image made up of uncertainty data for all bands; if input is numeric, output the uncertainty. Ignores ROIs. v n,u dqbits (0) create a new value with uncertainty by combining two values. These can be either numbers or images. \" Ignores and discards ROIs.","title":"Expr functions"},{"location":"autodocs/#expr-properties","text":"Below are properties which can be used in the expression evaluation node, expr . Properties are names which can be used as identifiers on the right hand side of a \".\" operator, such as a.w to get the width of an image a . name type of x desc x.w img give the width of an image in pixels (if there are ROIs, give the width of the BB of the ROI union) x.w roi give the width of an ROI in pixels x.h img give the height of an image in pixels (if there are ROIs, give the width of the BB of the ROI union) x.h roi give the width of an ROI in pixels x.n img give the area of an image in pixels (if there are ROIs, give the number of pixels in the ROI union) x.n roi give the number of pixels in an ROI","title":"Expr properties"},{"location":"autodocs/PCT_Patch_Detection/","text":"PCT Patch Detection Description A Node that takes in an image holding the ExoMars Rover PCT and outputs the centre coordinates in the image of each of the PCT patches. Connections Inputs Index Name Type Desc 0 img img (none) Outputs Index Name Type Desc 0 img+rois img (none)","title":"PCT Patch Detection"},{"location":"autodocs/PCT_Patch_Detection/#pct-patch-detection","text":"","title":"PCT Patch Detection"},{"location":"autodocs/PCT_Patch_Detection/#description","text":"A Node that takes in an image holding the ExoMars Rover PCT and outputs the centre coordinates in the image of each of the PCT patches.","title":"Description"},{"location":"autodocs/PCT_Patch_Detection/#connections","text":"","title":"Connections"},{"location":"autodocs/PCT_Patch_Detection/#inputs","text":"Index Name Type Desc 0 img img (none)","title":"Inputs"},{"location":"autodocs/PCT_Patch_Detection/#outputs","text":"Index Name Type Desc 0 img+rois img (none)","title":"Outputs"},{"location":"autodocs/banddepth/","text":"banddepth Description Calculate band depth using a linear weighted mean of the two bands either side. Reference: \"Revised CRISM spectral parameters... \" Viviano, Seelos et al. 2015. Issues: Ignores FWHM (bandwidth) of all bands. can't do weird stuff like Figs. 7c and 7d in the Viviano et al. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"banddepth"},{"location":"autodocs/banddepth/#banddepth","text":"","title":"banddepth"},{"location":"autodocs/banddepth/#description","text":"Calculate band depth using a linear weighted mean of the two bands either side. Reference: \"Revised CRISM spectral parameters... \" Viviano, Seelos et al. 2015. Issues: Ignores FWHM (bandwidth) of all bands. can't do weird stuff like Figs. 7c and 7d in the Viviano et al.","title":"Description"},{"location":"autodocs/banddepth/#connections","text":"","title":"Connections"},{"location":"autodocs/banddepth/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/banddepth/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/circle/","text":"circle Description Add a circular ROI to an image (see multidot for multiple circles). Can edit numerically. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. Connections Inputs Index Name Type Desc 0 input img (none) Outputs Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"circle"},{"location":"autodocs/circle/#circle","text":"","title":"circle"},{"location":"autodocs/circle/#description","text":"Add a circular ROI to an image (see multidot for multiple circles). Can edit numerically. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected.","title":"Description"},{"location":"autodocs/circle/#connections","text":"","title":"Connections"},{"location":"autodocs/circle/#inputs","text":"Index Name Type Desc 0 input img (none)","title":"Inputs"},{"location":"autodocs/circle/#outputs","text":"Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"Outputs"},{"location":"autodocs/comment/","text":"comment Description Comment box Connections","title":"comment"},{"location":"autodocs/comment/#comment","text":"","title":"comment"},{"location":"autodocs/comment/#description","text":"Comment box","title":"Description"},{"location":"autodocs/comment/#connections","text":"","title":"Connections"},{"location":"autodocs/constant/","text":"constant Description Generates a numeric value which can be typed directly into the node's box in the graph Connections Outputs Index Name Type Desc 0 (none) number (none)","title":"constant"},{"location":"autodocs/constant/#constant","text":"","title":"constant"},{"location":"autodocs/constant/#description","text":"Generates a numeric value which can be typed directly into the node's box in the graph","title":"Description"},{"location":"autodocs/constant/#connections","text":"","title":"Connections"},{"location":"autodocs/constant/#outputs","text":"Index Name Type Desc 0 (none) number (none)","title":"Outputs"},{"location":"autodocs/contrast_stretch/","text":"contrast stretch Description Perform a simple contrast stretch separately on each channel. The stretch is linear around the midpoint and excessive values are clamped. The knob controls the amount of stretch applied. Uncertainty is discarded. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"contrast stretch"},{"location":"autodocs/contrast_stretch/#contrast-stretch","text":"","title":"contrast stretch"},{"location":"autodocs/contrast_stretch/#description","text":"Perform a simple contrast stretch separately on each channel. The stretch is linear around the midpoint and excessive values are clamped. The knob controls the amount of stretch applied. Uncertainty is discarded.","title":"Description"},{"location":"autodocs/contrast_stretch/#connections","text":"","title":"Connections"},{"location":"autodocs/contrast_stretch/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/contrast_stretch/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/croproi/","text":"croproi Description Crops an image to a rectangle which is the union of its regions of interest. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"croproi"},{"location":"autodocs/croproi/#croproi","text":"","title":"croproi"},{"location":"autodocs/croproi/#description","text":"Crops an image to a rectangle which is the union of its regions of interest.","title":"Description"},{"location":"autodocs/croproi/#connections","text":"","title":"Connections"},{"location":"autodocs/croproi/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/croproi/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/crosscalib/","text":"crosscalib Description \"Cross-calibrate\" two images: given points S in a source image and corresponding points D in a destination image, find a vector of factors v for the bands such that S=vD, and transform S accordingly. Essentially, and crudely speaking, make the colours in S match those in D by sampling the same points in each. Bad pixels in the parent image will be ignored for getting the colours, and the new image will have the DQ bits from S. Uncertainty is not propagated through this node. DQ is propagated from the source image. Connections Inputs Index Name Type Desc 0 source img (none) 1 dest img (none) Outputs Index Name Type Desc 0 out img (none)","title":"crosscalib"},{"location":"autodocs/crosscalib/#crosscalib","text":"","title":"crosscalib"},{"location":"autodocs/crosscalib/#description","text":"\"Cross-calibrate\" two images: given points S in a source image and corresponding points D in a destination image, find a vector of factors v for the bands such that S=vD, and transform S accordingly. Essentially, and crudely speaking, make the colours in S match those in D by sampling the same points in each. Bad pixels in the parent image will be ignored for getting the colours, and the new image will have the DQ bits from S. Uncertainty is not propagated through this node. DQ is propagated from the source image.","title":"Description"},{"location":"autodocs/crosscalib/#connections","text":"","title":"Connections"},{"location":"autodocs/crosscalib/#inputs","text":"Index Name Type Desc 0 source img (none) 1 dest img (none)","title":"Inputs"},{"location":"autodocs/crosscalib/#outputs","text":"Index Name Type Desc 0 out img (none)","title":"Outputs"},{"location":"autodocs/curve/","text":"curve Description Maps the image channel intensities to a logistic sigmoid curve, y=1/(1+e^-(ax+b)), where a is \"mul\" and b is \"add\". Honours regions of interest. Ignores DQ and uncertainty Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"curve"},{"location":"autodocs/curve/#curve","text":"","title":"curve"},{"location":"autodocs/curve/#description","text":"Maps the image channel intensities to a logistic sigmoid curve, y=1/(1+e^-(ax+b)), where a is \"mul\" and b is \"add\". Honours regions of interest. Ignores DQ and uncertainty","title":"Description"},{"location":"autodocs/curve/#connections","text":"","title":"Connections"},{"location":"autodocs/curve/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/curve/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/decorr_stretch/","text":"decorr stretch Description Perform a decorrelation stretch on an RGB image Ignores DQ and uncertainty Connections Inputs Index Name Type Desc 0 rgb img (none) Outputs Index Name Type Desc 0 rgb img (none)","title":"decorr stretch"},{"location":"autodocs/decorr_stretch/#decorr-stretch","text":"","title":"decorr stretch"},{"location":"autodocs/decorr_stretch/#description","text":"Perform a decorrelation stretch on an RGB image Ignores DQ and uncertainty","title":"Description"},{"location":"autodocs/decorr_stretch/#connections","text":"","title":"Connections"},{"location":"autodocs/decorr_stretch/#inputs","text":"Index Name Type Desc 0 rgb img (none)","title":"Inputs"},{"location":"autodocs/decorr_stretch/#outputs","text":"Index Name Type Desc 0 rgb img (none)","title":"Outputs"},{"location":"autodocs/dqmod/","text":"dqmod Description Modify DQ bits based on conditions in the existing nominal or uncertainty for all bands or just a single band. **WARNING**: This may set \"bad\" bits which will be masked in any calculation. For some settings, these bad bits can mask bands other than those from which they are derived. Calculations involving pixels with these bits will be partially derived from the mask, but this information will not be tracked by the source mechanism. This means that some source tracking information can be lost. (Issue #69) Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"dqmod"},{"location":"autodocs/dqmod/#dqmod","text":"","title":"dqmod"},{"location":"autodocs/dqmod/#description","text":"Modify DQ bits based on conditions in the existing nominal or uncertainty for all bands or just a single band. **WARNING**: This may set \"bad\" bits which will be masked in any calculation. For some settings, these bad bits can mask bands other than those from which they are derived. Calculations involving pixels with these bits will be partially derived from the mask, but this information will not be tracked by the source mechanism. This means that some source tracking information can be lost. (Issue #69)","title":"Description"},{"location":"autodocs/dqmod/#connections","text":"","title":"Connections"},{"location":"autodocs/dqmod/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/dqmod/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/dummy/","text":"dummy Description A dummy node type used when the node type specified in a loaded file cannot be found - perhaps it is from an older PCOT version and is now deprecated, or it's part of a plugin? Connections","title":"dummy"},{"location":"autodocs/dummy/#dummy","text":"","title":"dummy"},{"location":"autodocs/dummy/#description","text":"A dummy node type used when the node type specified in a loaded file cannot be found - perhaps it is from an older PCOT version and is now deprecated, or it's part of a plugin?","title":"Description"},{"location":"autodocs/dummy/#connections","text":"","title":"Connections"},{"location":"autodocs/edge/","text":"edge Description This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab. DQ bits of the bands in the source are combined together for the single band of the result. Uncertainty is discarded. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"edge"},{"location":"autodocs/edge/#edge","text":"","title":"edge"},{"location":"autodocs/edge/#description","text":"This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab. DQ bits of the bands in the source are combined together for the single band of the result. Uncertainty is discarded.","title":"Description"},{"location":"autodocs/edge/#connections","text":"","title":"Connections"},{"location":"autodocs/edge/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/edge/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/errortest/","text":"errortest Description Check that a node produces an error. This node will run after all other nodes, but before its children. It checks that the string is the error code (e.g. 'DATA') Connections Inputs Index Name Type Desc 0 (none) any (none) Outputs Index Name Type Desc 0 (none) testresult (none)","title":"errortest"},{"location":"autodocs/errortest/#errortest","text":"","title":"errortest"},{"location":"autodocs/errortest/#description","text":"Check that a node produces an error. This node will run after all other nodes, but before its children. It checks that the string is the error code (e.g. 'DATA')","title":"Description"},{"location":"autodocs/errortest/#connections","text":"","title":"Connections"},{"location":"autodocs/errortest/#inputs","text":"Index Name Type Desc 0 (none) any (none)","title":"Inputs"},{"location":"autodocs/errortest/#outputs","text":"Index Name Type Desc 0 (none) testresult (none)","title":"Outputs"},{"location":"autodocs/example/","text":"example Description This object is not a node, but the singleton to which nodes of this type point to determine their behaviour. This docstring will form the help text for the node in the UI. Markdown is permitted and processed into HTML. Look at (say) XFormGradient for an example of how to write this. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"example"},{"location":"autodocs/example/#example","text":"","title":"example"},{"location":"autodocs/example/#description","text":"This object is not a node, but the singleton to which nodes of this type point to determine their behaviour. This docstring will form the help text for the node in the UI. Markdown is permitted and processed into HTML. Look at (say) XFormGradient for an example of how to write this.","title":"Description"},{"location":"autodocs/example/#connections","text":"","title":"Connections"},{"location":"autodocs/example/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/example/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/expr/","text":"expr Description Expression evaluator. The node box will show the text of the expression. The \"run\" button must be clicked to set the node to the new expression and perform it. The input can accept any type of data and the output type is determined when the node is run. The four inputs are assigned to the variables a, b, c, and d. They are typically (but not necessarily) images or numeric values. The standard operators +,/,*,- and ^ all have their usual meanings. When applied to images they work in a pixel-wise fashion, so if a is an image, 2*a will double the brightness. If b is also an image, a+b will add the two images, pixel by pixel. There are two non-standard operators: . for properties and $ for band extraction. These are described below. Image/numeric operators: operator description precedence (higher binds tighter) A + B add A to B (can act on ROIs) 10 A - B subtract A from B (can act on ROIs) 10 A / B divide A by B (can act on ROIs) 20 A * B multiply A by B (can act on ROIs) 20 A ^ B exponentiate A to the power B (can act on ROIs) 30 -A element-wise negation of A (can act on ROIs) 50 A.B property B of entity A (e.g. a.h is height of image a) 80 A$546 extract single band image of wavelength 546 100 A$_2 extract single band image from band 2 explicitly 100 A&B element-wise minimum of A and B (Zadeh's AND operator) 20 A|B element-wise maximum of A and B (Zadeh's OR operator) 20 !A element-wise 1-A (Zadeh's NOT operator) 50 All operators can act on images, 1D vectors and scalars with the exception of . and $ which have images on the left-hand side and identifiers or integers on the right-hand side. Those operators marked with (can act on ROIs) can also act on pairs of ROIs (regions of interest, see below). Binary operations on image pairs These act by performing the binary operation on the two underlying Numpy arrays. This means you may need to be careful about the ordering of the bands in the two images, because they will simply be operated on in the order they appear. For example, consider adding two images $a$ and $b$ with the same bands in a slightly different order: image a image b result of addition 480nm 480nm sum of 480nm bands 500nm 500nm sum of 500nm bands 610nm 670nm a 's 610nm band plus b 's 670nm band 670nm 610nm copy of previous band (addition being commutative) This probably isn't what you wanted. Note that this is obviously not an issue when an operation is being performed on bands in a single image. Binary operators on images with regions of interest If one of the two images has an ROI, the operation is only performed on that ROI; the remaining area of output is taken from the image without an ROI. If both images have an ROI an error will result - it is likely that this is a mistake on the user's part, and doing something more \"intelligent\" might conceal this. The desired result can be achieved using expr nodes on ROIs and an importroi node. Operations with vectors Some functions can generate vectors, such as mean for getting the means of the bands, and vec for generating vectors by hand. If an image is used in a binary operation with a vector on the other side, the vector must have the same number of elements as there are bands in the image. The operation will be performed on each band. Consider a 3-band image and the vector [2,3,4] . If we multiply them, the result will an image with the first band multiplied by 2, the second band multiplied by 3, and the third band multiplied by 4. Operators on ROIs themselves (as opposed to images with ROIs) operator description a+b union a*b intersection a-b difference You can source ROIs from the \"roi\" output of ROI nodes, and impose resulting ROIs on images with \"importroi\" node. Band extraction The notation $name or $wavelength takes an image on the left-hand side and extracts a single band, generating a new monochrome image. The right-hand side is either a filter name, a filter position, a wavelength or a band index preceded by \"_\". Depending on the camera, all these could be valid: expression meaning a$780 the 780nm band in image a a$_2 band 2 in the image a (a+b)$G0 the band named G0 in the image formed by adding images a and b ((a+b)/2)$780 the average of the 780nm bands of images a and b Be aware of caveats in the \"binary operations on image pairs\" section above: it may be better to extract the band before performing the operation, thus: old expression better expression (a+b)$G0 a$G0 + b$G0 ((a+b)/2)$780 (a$780+b$780)/2 Band extraction can also be performed with vectors provided the vector elements are numeric (i.e. wavelengths): a $ vec(640,550,440) is valid. Properties Properties are indicated by the . operator, e.g. a.w to find an image's width. Help on functions and properties A list of functions can be obtained by right-clicking on either the log pane or function entry pane and selecting \"List all functions.\" Help on an individual function can be found by hovering over the name of a function, right-clicking and selecting \"Get help on 'somefunction'\". Similar actions are supported for properties. Uncertainties are assumed to be independent in all binary operations While uncertainty is propagated through operations (as standard deviation) all quantities are assumed to be independent (calculating covariances is beyond the scope of this system). Be very careful here. For example, the uncertainty for the expression tan(a) will be calculated correctly, but if you try to use sin(a)/cos(a) the uncertainty will be incorrect because the nominator and denominator are not independent. Connections Inputs Index Name Type Desc 0 a any (none) 1 b any (none) 2 c any (none) 3 d any (none) Outputs Index Name Type Desc 0 (none) none (none)","title":"expr"},{"location":"autodocs/expr/#expr","text":"","title":"expr"},{"location":"autodocs/expr/#description","text":"Expression evaluator. The node box will show the text of the expression. The \"run\" button must be clicked to set the node to the new expression and perform it. The input can accept any type of data and the output type is determined when the node is run. The four inputs are assigned to the variables a, b, c, and d. They are typically (but not necessarily) images or numeric values. The standard operators +,/,*,- and ^ all have their usual meanings. When applied to images they work in a pixel-wise fashion, so if a is an image, 2*a will double the brightness. If b is also an image, a+b will add the two images, pixel by pixel. There are two non-standard operators: . for properties and $ for band extraction. These are described below.","title":"Description"},{"location":"autodocs/expr/#connections","text":"","title":"Connections"},{"location":"autodocs/expr/#inputs","text":"Index Name Type Desc 0 a any (none) 1 b any (none) 2 c any (none) 3 d any (none)","title":"Inputs"},{"location":"autodocs/expr/#outputs","text":"Index Name Type Desc 0 (none) none (none)","title":"Outputs"},{"location":"autodocs/gen/","text":"gen Description Generate an image with given channel values. Can also generate patterns. Each band is given a nominal value and uncertainty, along with a centre frequency and a mode (for patterns). Modes are: flat : N and U are used to fill the entire band ripple-n: the N value is not a value, but a multiplier applied to distance from centre - the sine of this gives the value. The U value is generated as in 'flat' ripple-u: as ripple-n, but this time U is used as a multiplier to generate the ripple pattern in uncertainty, while N is generated as in 'flat' ripple-un: both values are ripple multipliers. half: nominal is N on the left, U on the right. Uncertainty is 0.1. (Test value) checkx: nominal is a checquered pattern with each square of size N, offset by U in the x-axis. uncertainty=nominal. checky: nominal is a checquered pattern with each square of size N, offset by U in the y-axis. uncertainty=nominal. rand: both nom. and unc. are filled with non-negative pseudorandom uniform noise multiplied by N and U respectively gaussian: nom. is filled with gaussian noise centered around N with a std. dev. of U. U is zero. The RNG is seeded from the CWL. gradient-x: nom. is filled with a gradient from 0-1, U is zero. Number of steps is N (zero means smooth). gradient-y: nom. is filled with a gradient from 0-1, U is zero. Number of steps is N (zero means smooth). A useful pattern might be something like this: Chan 0: checkx, N=8, U=0 Chan 1: checkx, N=8, U=4 Chan 2: checky, N=8, U=4 To get variation in uncertainty, create a similar pattern but with a longer period using another gen node: Chan 0: checkx, N=16, U=0 Chan 1: checkx, N=16, U=8 Chan 2: checky, N=16, U=8 and merge the two together, using the first gen to create nominal values and the second to create uncertainty values, with an expr node using the expression v(a,b) . Connections Outputs Index Name Type Desc 0 (none) img (none)","title":"gen"},{"location":"autodocs/gen/#gen","text":"","title":"gen"},{"location":"autodocs/gen/#description","text":"Generate an image with given channel values. Can also generate patterns. Each band is given a nominal value and uncertainty, along with a centre frequency and a mode (for patterns). Modes are: flat : N and U are used to fill the entire band ripple-n: the N value is not a value, but a multiplier applied to distance from centre - the sine of this gives the value. The U value is generated as in 'flat' ripple-u: as ripple-n, but this time U is used as a multiplier to generate the ripple pattern in uncertainty, while N is generated as in 'flat' ripple-un: both values are ripple multipliers. half: nominal is N on the left, U on the right. Uncertainty is 0.1. (Test value) checkx: nominal is a checquered pattern with each square of size N, offset by U in the x-axis. uncertainty=nominal. checky: nominal is a checquered pattern with each square of size N, offset by U in the y-axis. uncertainty=nominal. rand: both nom. and unc. are filled with non-negative pseudorandom uniform noise multiplied by N and U respectively gaussian: nom. is filled with gaussian noise centered around N with a std. dev. of U. U is zero. The RNG is seeded from the CWL. gradient-x: nom. is filled with a gradient from 0-1, U is zero. Number of steps is N (zero means smooth). gradient-y: nom. is filled with a gradient from 0-1, U is zero. Number of steps is N (zero means smooth). A useful pattern might be something like this: Chan 0: checkx, N=8, U=0 Chan 1: checkx, N=8, U=4 Chan 2: checky, N=8, U=4 To get variation in uncertainty, create a similar pattern but with a longer period using another gen node: Chan 0: checkx, N=16, U=0 Chan 1: checkx, N=16, U=8 Chan 2: checky, N=16, U=8 and merge the two together, using the first gen to create nominal values and the second to create uncertainty values, with an expr node using the expression v(a,b) .","title":"Description"},{"location":"autodocs/gen/#connections","text":"","title":"Connections"},{"location":"autodocs/gen/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/gradient/","text":"gradient Description Convert a mono image to an RGB gradient image for better visibility. If the \"insetinto\" input has an image AND there is a valid ROI in the mono image, the image will be inset into the RGB of the insetinto image. NOTE: if you change the \"insetinto\" image's RGB mapping you may need to \"run all\" to see the the change reflected. Ignores DQ and uncertainty The gradient widget has the following behaviour: click and drag to move a colour point doubleclick to delete an existing colour point doubleclick to add a new colour point right click to edit an existing colour point Node parameters: gradient: utils.Gradient object containing gradient info colour: (r,g,b) [0:1] colour of text and border for in-image legend legendrect: (x,y,w,h) rectangle for in-image legend vertical: true if vertical legend fontscale: size of font thickness: border thickness legendPos: string describing position: 'In image', 'Top margin', 'Bottom margin', 'Left margin', 'Right margin', 'None' These are also defined as constants LEFT_MARGIN... IN_IMAGE (and None) Connections Inputs Index Name Type Desc 0 mono img (none) 1 background img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"gradient"},{"location":"autodocs/gradient/#gradient","text":"","title":"gradient"},{"location":"autodocs/gradient/#description","text":"Convert a mono image to an RGB gradient image for better visibility. If the \"insetinto\" input has an image AND there is a valid ROI in the mono image, the image will be inset into the RGB of the insetinto image. NOTE: if you change the \"insetinto\" image's RGB mapping you may need to \"run all\" to see the the change reflected. Ignores DQ and uncertainty The gradient widget has the following behaviour: click and drag to move a colour point doubleclick to delete an existing colour point doubleclick to add a new colour point right click to edit an existing colour point Node parameters: gradient: utils.Gradient object containing gradient info colour: (r,g,b) [0:1] colour of text and border for in-image legend legendrect: (x,y,w,h) rectangle for in-image legend vertical: true if vertical legend fontscale: size of font thickness: border thickness legendPos: string describing position: 'In image', 'Top margin', 'Bottom margin', 'Left margin', 'Right margin', 'None' These are also defined as constants LEFT_MARGIN... IN_IMAGE (and None)","title":"Description"},{"location":"autodocs/gradient/#connections","text":"","title":"Connections"},{"location":"autodocs/gradient/#inputs","text":"Index Name Type Desc 0 mono img (none) 1 background img (none)","title":"Inputs"},{"location":"autodocs/gradient/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/histequal/","text":"histequal Description Perform histogram equalisation on all channels of the image separately. Honours ROIs. Currently set to 2000 bins, but I may add a control for that. Ignores DQ bits and uncertainty Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"histequal"},{"location":"autodocs/histequal/#histequal","text":"","title":"histequal"},{"location":"autodocs/histequal/#description","text":"Perform histogram equalisation on all channels of the image separately. Honours ROIs. Currently set to 2000 bins, but I may add a control for that. Ignores DQ bits and uncertainty","title":"Description"},{"location":"autodocs/histequal/#connections","text":"","title":"Connections"},{"location":"autodocs/histequal/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/histequal/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/histogram/","text":"histogram Description Produce a histogram of intensities for each channel in the data - will be very messy if used on a multispectral image. Will only be performed on ROIs if there are active ROIs. BAD pixels in bands will be discounted. The output carries a table - columns are frequencies, rows are bands. Uncertainty is ignored. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 data data a CSV output (use 'dump' or 'sink' to read it)","title":"histogram"},{"location":"autodocs/histogram/#histogram","text":"","title":"histogram"},{"location":"autodocs/histogram/#description","text":"Produce a histogram of intensities for each channel in the data - will be very messy if used on a multispectral image. Will only be performed on ROIs if there are active ROIs. BAD pixels in bands will be discounted. The output carries a table - columns are frequencies, rows are bands. Uncertainty is ignored.","title":"Description"},{"location":"autodocs/histogram/#connections","text":"","title":"Connections"},{"location":"autodocs/histogram/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/histogram/#outputs","text":"Index Name Type Desc 0 data data a CSV output (use 'dump' or 'sink' to read it)","title":"Outputs"},{"location":"autodocs/importroi/","text":"importroi Description Import a ROI into an image which was originally set on another image. The 'roi' input takes either an ROI or an image. If the former, that ROI is imposed on the image passed into the main input. If the latter, all the ROIs from the 'roi' input image are imposed on the image input image. Connections Inputs Index Name Type Desc 0 (none) img (none) 1 roi any (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"importroi"},{"location":"autodocs/importroi/#importroi","text":"","title":"importroi"},{"location":"autodocs/importroi/#description","text":"Import a ROI into an image which was originally set on another image. The 'roi' input takes either an ROI or an image. If the former, that ROI is imposed on the image passed into the main input. If the latter, all the ROIs from the 'roi' input image are imposed on the image input image.","title":"Description"},{"location":"autodocs/importroi/#connections","text":"","title":"Connections"},{"location":"autodocs/importroi/#inputs","text":"Index Name Type Desc 0 (none) img (none) 1 roi any (none)","title":"Inputs"},{"location":"autodocs/importroi/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/in/","text":"in Description The macro input connector (used inside macro prototypes) Connections Outputs Index Name Type Desc 0 (none) variant (none)","title":"in"},{"location":"autodocs/in/#in","text":"","title":"in"},{"location":"autodocs/in/#description","text":"The macro input connector (used inside macro prototypes)","title":"Description"},{"location":"autodocs/in/#connections","text":"","title":"Connections"},{"location":"autodocs/in/#outputs","text":"Index Name Type Desc 0 (none) variant (none)","title":"Outputs"},{"location":"autodocs/input_0/","text":"input 0 Description Imports Input 0's data into the graph Connections Outputs Index Name Type Desc 0 (none) none (none)","title":"input 0"},{"location":"autodocs/input_0/#input-0","text":"","title":"input 0"},{"location":"autodocs/input_0/#description","text":"Imports Input 0's data into the graph","title":"Description"},{"location":"autodocs/input_0/#connections","text":"","title":"Connections"},{"location":"autodocs/input_0/#outputs","text":"Index Name Type Desc 0 (none) none (none)","title":"Outputs"},{"location":"autodocs/input_1/","text":"input 1 Description Imports Input 1's data into the graph Connections Outputs Index Name Type Desc 0 (none) none (none)","title":"input 1"},{"location":"autodocs/input_1/#input-1","text":"","title":"input 1"},{"location":"autodocs/input_1/#description","text":"Imports Input 1's data into the graph","title":"Description"},{"location":"autodocs/input_1/#connections","text":"","title":"Connections"},{"location":"autodocs/input_1/#outputs","text":"Index Name Type Desc 0 (none) none (none)","title":"Outputs"},{"location":"autodocs/input_2/","text":"input 2 Description Imports Input 2's data into the graph Connections Outputs Index Name Type Desc 0 (none) none (none)","title":"input 2"},{"location":"autodocs/input_2/#input-2","text":"","title":"input 2"},{"location":"autodocs/input_2/#description","text":"Imports Input 2's data into the graph","title":"Description"},{"location":"autodocs/input_2/#connections","text":"","title":"Connections"},{"location":"autodocs/input_2/#outputs","text":"Index Name Type Desc 0 (none) none (none)","title":"Outputs"},{"location":"autodocs/input_3/","text":"input 3 Description Imports Input 3's data into the graph Connections Outputs Index Name Type Desc 0 (none) none (none)","title":"input 3"},{"location":"autodocs/input_3/#input-3","text":"","title":"input 3"},{"location":"autodocs/input_3/#description","text":"Imports Input 3's data into the graph","title":"Description"},{"location":"autodocs/input_3/#connections","text":"","title":"Connections"},{"location":"autodocs/input_3/#outputs","text":"Index Name Type Desc 0 (none) none (none)","title":"Outputs"},{"location":"autodocs/inset/","text":"inset Description Inset an image inside another. Uses RGB versions of both images, as defined by the RGB mapping set in the previous nodes. Does not honour regions of interest. Note that there is no RGB mapping in the canvas for the tab - RGB mappings should be set in the input nodes. Ignores DQ and uncertainty Connections Inputs Index Name Type Desc 0 img img (none) 1 inset img (none) 2 roi roi (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"inset"},{"location":"autodocs/inset/#inset","text":"","title":"inset"},{"location":"autodocs/inset/#description","text":"Inset an image inside another. Uses RGB versions of both images, as defined by the RGB mapping set in the previous nodes. Does not honour regions of interest. Note that there is no RGB mapping in the canvas for the tab - RGB mappings should be set in the input nodes. Ignores DQ and uncertainty","title":"Description"},{"location":"autodocs/inset/#connections","text":"","title":"Connections"},{"location":"autodocs/inset/#inputs","text":"Index Name Type Desc 0 img img (none) 1 inset img (none) 2 roi roi (none)","title":"Inputs"},{"location":"autodocs/inset/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/manual_register/","text":"manual register Description Perform manual registration of two images. The output is a version of the 'moving' image with a projective transform applied to map points onto corresponding points in the 'fixed' image. The canvas view can show the moving input (also referred to as the \"source\"), the fixed image (also referred to as the \"destination\"), a blend of the two, or the result. All images are shown as greyscale (since the fixed and moving images will likely have different frequency bands). The transform will map a set of points in the moving image onto a set in the fixed image. Both sets of points can be changed, or a single set. Points are mapped onto the correspondingly numbered point. In \"translate\" mode only a single point is required (and only a single point will be shown from each set). Points are added to the source (moving) image by clicking with shift. Points are adding to the dest (fixed) image by clicking with ctrl. Note that this node does not currently display DQ or uncertainty data in its canvas If only the source or dest points are shown, either shift- or ctrl-clicking will add to the appropriate point set. The selected point can be deleted with the Delete key (but this will modify the numbering!) A point can be selected and dragged by clicking on it. This may be slow because the warping operation will take place every update; disabling 'auto-run on change' is a good idea! Uncertainty is warped along with the original image, as is DQ using nearest-neighbour ( which may not be sufficient ). Connections Inputs Index Name Type Desc 0 moving img (none) 1 fixed img (none) Outputs Index Name Type Desc 0 moved img (none)","title":"manual register"},{"location":"autodocs/manual_register/#manual-register","text":"","title":"manual register"},{"location":"autodocs/manual_register/#description","text":"Perform manual registration of two images. The output is a version of the 'moving' image with a projective transform applied to map points onto corresponding points in the 'fixed' image. The canvas view can show the moving input (also referred to as the \"source\"), the fixed image (also referred to as the \"destination\"), a blend of the two, or the result. All images are shown as greyscale (since the fixed and moving images will likely have different frequency bands). The transform will map a set of points in the moving image onto a set in the fixed image. Both sets of points can be changed, or a single set. Points are mapped onto the correspondingly numbered point. In \"translate\" mode only a single point is required (and only a single point will be shown from each set). Points are added to the source (moving) image by clicking with shift. Points are adding to the dest (fixed) image by clicking with ctrl. Note that this node does not currently display DQ or uncertainty data in its canvas If only the source or dest points are shown, either shift- or ctrl-clicking will add to the appropriate point set. The selected point can be deleted with the Delete key (but this will modify the numbering!) A point can be selected and dragged by clicking on it. This may be slow because the warping operation will take place every update; disabling 'auto-run on change' is a good idea! Uncertainty is warped along with the original image, as is DQ using nearest-neighbour ( which may not be sufficient ).","title":"Description"},{"location":"autodocs/manual_register/#connections","text":"","title":"Connections"},{"location":"autodocs/manual_register/#inputs","text":"Index Name Type Desc 0 moving img (none) 1 fixed img (none)","title":"Inputs"},{"location":"autodocs/manual_register/#outputs","text":"Index Name Type Desc 0 moved img (none)","title":"Outputs"},{"location":"autodocs/mergetests/","text":"mergetests Description Merge the results of many tests into a single list of failures. Test results are always lists of test failures, this simply concatenates those lists. Connections Inputs Index Name Type Desc 0 (none) testresult (none) 1 (none) testresult (none) 2 (none) testresult (none) 3 (none) testresult (none) 4 (none) testresult (none) 5 (none) testresult (none) 6 (none) testresult (none) 7 (none) testresult (none) Outputs Index Name Type Desc 0 results testresult (none)","title":"mergetests"},{"location":"autodocs/mergetests/#mergetests","text":"","title":"mergetests"},{"location":"autodocs/mergetests/#description","text":"Merge the results of many tests into a single list of failures. Test results are always lists of test failures, this simply concatenates those lists.","title":"Description"},{"location":"autodocs/mergetests/#connections","text":"","title":"Connections"},{"location":"autodocs/mergetests/#inputs","text":"Index Name Type Desc 0 (none) testresult (none) 1 (none) testresult (none) 2 (none) testresult (none) 3 (none) testresult (none) 4 (none) testresult (none) 5 (none) testresult (none) 6 (none) testresult (none) 7 (none) testresult (none)","title":"Inputs"},{"location":"autodocs/mergetests/#outputs","text":"Index Name Type Desc 0 results testresult (none)","title":"Outputs"},{"location":"autodocs/multidot/","text":"multidot Description Add multiple small ROIs which are either circular or painted. Painted modes can be created and edited with a circular brush or a flood fill. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROIs on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. This can also \"capture\" ROIs from the incoming image, so that they can be edited. This copies the ROIs from the image into the node, and suppresses the image's original ROIs. In addition to this, the \"convert circles\" button will convert all circular ROIs in the node into painted ROIs. Quick guide: To add and edit circular ROIs: select \"Circles\" on the left-hand side set the dot size to the desired radius shift-click to add and select a new ROI click to select an existing ROI (or deselect) drag to move the centre of the circle edit parameters like dot size, name, colour, etc. to change the current ROI or next created ROI To add and edit painted ROIs: select \"Painted\" on the left-hand side set the dot size to the desired radius set add/create mode to Brush shift-click to add and select a new painted ROI click inside an ROI to select it ctrl-click to add a circle to a selected painted ROI alt-click to \"unpaint\" a circle from a selected painted ROI To add and edit filled ROIs: select \"Painted\" on the left-hand side set add/create mode to Fill set tolerance to a low number (e.g. 0.1) shift-click to add and select a new filled ROI possibly undo (ctrl-Z) to remove the last fill, then change the tolerance! click inside an ROI to select it ctrl-click to add more flood fill to a selected ROI set add/create mode to \"Brush\" to paint circular brushstrokes on a ROI alt-click to \"unpaint\" a circle from a selected ROI General controls: Circles or Painted selects the type of new ROIs click inside an ROI (or very near a circle) to show and edit its properties Dot size is the size of the circle used for both creating circle ROIs and for circular painting in Painted mode. Scale is the font size for all annotations created by this node Thickness is the border size for (currently) all circles only Colour is the colour of the current ROI's annotation Recolour all will select random colours for all ROIs Name is the name of the current ROI Background is whether a background rectangle is used to make the name clearer for all ROIs Capture captures the ROIs from the incoming image, and suppresses the image's original ROIs Convert circles will convert all circular ROIs in the node into painted ROIs. tolerance is the colour difference between the current pixel and surrounding pixels required to stop flood filling. PICK CAREFULLY - it may need to be very small. add/create mode is whether we are new ROIs are created with a circular brush or flood fill in Painted mode Circle mode: shift-click to add a new ROI drag to move centre of circle Painted mode: shift-click to add a new painted ROI. Will use a circle if \"Paint Mode\" is circle, or a flood fill with the given tolerance if the mode is \"Fill\". ctrl-click to add a circle or flood fill to a selected painted ROI, provided we are in the same mode as the selected ROI. Circle or fill depends on Paint Mode. alt-click to \"unpaint\" a circle from a selected painted ROI (Internal: Note that this type doesn't inherit from XFormROI.) Connections Inputs Index Name Type Desc 0 input img (none) Outputs Index Name Type Desc 0 img img image with ROIs","title":"multidot"},{"location":"autodocs/multidot/#multidot","text":"","title":"multidot"},{"location":"autodocs/multidot/#description","text":"Add multiple small ROIs which are either circular or painted. Painted modes can be created and edited with a circular brush or a flood fill. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROIs on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. This can also \"capture\" ROIs from the incoming image, so that they can be edited. This copies the ROIs from the image into the node, and suppresses the image's original ROIs. In addition to this, the \"convert circles\" button will convert all circular ROIs in the node into painted ROIs.","title":"Description"},{"location":"autodocs/multidot/#connections","text":"","title":"Connections"},{"location":"autodocs/multidot/#inputs","text":"Index Name Type Desc 0 input img (none)","title":"Inputs"},{"location":"autodocs/multidot/#outputs","text":"Index Name Type Desc 0 img img image with ROIs","title":"Outputs"},{"location":"autodocs/normimage/","text":"normimage Description Normalise the image to a single range taken from all channels. Honours ROIs. If you need to normalise each channel separately, use the norm() function in the \"expr\" node which has an optional argument for this. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"normimage"},{"location":"autodocs/normimage/#normimage","text":"","title":"normimage"},{"location":"autodocs/normimage/#description","text":"Normalise the image to a single range taken from all channels. Honours ROIs. If you need to normalise each channel separately, use the norm() function in the \"expr\" node which has an optional argument for this.","title":"Description"},{"location":"autodocs/normimage/#connections","text":"","title":"Connections"},{"location":"autodocs/normimage/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/normimage/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/offset/","text":"offset Description offset an image. Will create a zero band on one edge and clip on the opposite. ROIs are not honoured, but are passed through. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"offset"},{"location":"autodocs/offset/#offset","text":"","title":"offset"},{"location":"autodocs/offset/#description","text":"offset an image. Will create a zero band on one edge and clip on the opposite. ROIs are not honoured, but are passed through.","title":"Description"},{"location":"autodocs/offset/#connections","text":"","title":"Connections"},{"location":"autodocs/offset/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/offset/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/out/","text":"out Description The macro output connector (used inside macro prototypes) Connections Inputs Index Name Type Desc 0 (none) variant (none)","title":"out"},{"location":"autodocs/out/#out","text":"","title":"out"},{"location":"autodocs/out/#description","text":"The macro output connector (used inside macro prototypes)","title":"Description"},{"location":"autodocs/out/#connections","text":"","title":"Connections"},{"location":"autodocs/out/#inputs","text":"Index Name Type Desc 0 (none) variant (none)","title":"Inputs"},{"location":"autodocs/painted/","text":"painted Description Add a painted ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. Connections Inputs Index Name Type Desc 0 input img (none) Outputs Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"painted"},{"location":"autodocs/painted/#painted","text":"","title":"painted"},{"location":"autodocs/painted/#description","text":"Add a painted ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected.","title":"Description"},{"location":"autodocs/painted/#connections","text":"","title":"Connections"},{"location":"autodocs/painted/#inputs","text":"Index Name Type Desc 0 input img (none)","title":"Inputs"},{"location":"autodocs/painted/#outputs","text":"Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"Outputs"},{"location":"autodocs/pct/","text":"pct Description Locates the PCT and generates calibration coefficients **Very incomplete at the moment** Connections Inputs Index Name Type Desc 0 img img (none)","title":"pct"},{"location":"autodocs/pct/#pct","text":"","title":"pct"},{"location":"autodocs/pct/#description","text":"Locates the PCT and generates calibration coefficients **Very incomplete at the moment**","title":"Description"},{"location":"autodocs/pct/#connections","text":"","title":"Connections"},{"location":"autodocs/pct/#inputs","text":"Index Name Type Desc 0 img img (none)","title":"Inputs"},{"location":"autodocs/pixtest/","text":"pixtest Description Used in testing, but may be useful for running automated tests for users. Contains a table of pixel positions and values and checks them in the input image, flagging any errors. The output is numeric, and is the number of failing tests. Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 results testresult (none)","title":"pixtest"},{"location":"autodocs/pixtest/#pixtest","text":"","title":"pixtest"},{"location":"autodocs/pixtest/#description","text":"Used in testing, but may be useful for running automated tests for users. Contains a table of pixel positions and values and checks them in the input image, flagging any errors. The output is numeric, and is the number of failing tests.","title":"Description"},{"location":"autodocs/pixtest/#connections","text":"","title":"Connections"},{"location":"autodocs/pixtest/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/pixtest/#outputs","text":"Index Name Type Desc 0 results testresult (none)","title":"Outputs"},{"location":"autodocs/poly/","text":"poly Description Add a polygonal ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. Connections Inputs Index Name Type Desc 0 input img (none) Outputs Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"poly"},{"location":"autodocs/poly/#poly","text":"","title":"poly"},{"location":"autodocs/poly/#description","text":"Add a polygonal ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected.","title":"Description"},{"location":"autodocs/poly/#connections","text":"","title":"Connections"},{"location":"autodocs/poly/#inputs","text":"Index Name Type Desc 0 input img (none)","title":"Inputs"},{"location":"autodocs/poly/#outputs","text":"Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"Outputs"},{"location":"autodocs/rect/","text":"rect Description Add a rectangular ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected. Connections Inputs Index Name Type Desc 0 input img (none) Outputs Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"rect"},{"location":"autodocs/rect/#rect","text":"","title":"rect"},{"location":"autodocs/rect/#description","text":"Add a rectangular ROI to an image. Most subsequent operations will only be performed on the union of all regions of interest. Also outputs an RGB image annotated with the ROI on the 'ann' RGB input, or the input image converted to RGB if that input is not connected.","title":"Description"},{"location":"autodocs/rect/#connections","text":"","title":"Connections"},{"location":"autodocs/rect/#inputs","text":"Index Name Type Desc 0 input img (none)","title":"Inputs"},{"location":"autodocs/rect/#outputs","text":"Index Name Type Desc 0 img img image with ROI 1 roi roi the region of interest","title":"Outputs"},{"location":"autodocs/roidq/","text":"roidq Description Automatically generate an ROI from DQ bits in a band or in all bands. **WARNING**: the ROI will be generated from DQ data from any bands in this image. It can then be applied to any other image or band - but this information is not tracked by the source mechanism. This means that some source tracking information can be lost. (Issue #68) Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 img img (none) 1 roi roi (none)","title":"roidq"},{"location":"autodocs/roidq/#roidq","text":"","title":"roidq"},{"location":"autodocs/roidq/#description","text":"Automatically generate an ROI from DQ bits in a band or in all bands. **WARNING**: the ROI will be generated from DQ data from any bands in this image. It can then be applied to any other image or band - but this information is not tracked by the source mechanism. This means that some source tracking information can be lost. (Issue #68)","title":"Description"},{"location":"autodocs/roidq/#connections","text":"","title":"Connections"},{"location":"autodocs/roidq/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/roidq/#outputs","text":"Index Name Type Desc 0 img img (none) 1 roi roi (none)","title":"Outputs"},{"location":"autodocs/roiexpr/","text":"roiexpr Description This node allows a region of interest to be composed from several regions of interest using an expression and imposed on an image. It is not a node for creating several ROIs at once - the output is always a single ROI . ROIs can be created for use within the expression by using the \"Add ROI\" button. These will be assigned to the variables a,b,c.. within the expression, and can be edited by: clicking on their label in the left-most column of the table (to select the entire row) and then clicking and dragging on the canvas, double clicking on the description text in the table to open a numerical editor (not for poly or painted). Additional ROIs can be connected to the p, q, r inputs; these will be assigned to those variables within the expression. The input image's ROIs are combined into a single ROI and assigned to the variable 'i'. The input image itself is available as the variable 'img'. Other properties of the image are available and other calculations may be made, but the result of the expression must be an ROI. Examples: a+b : the union of ROIs 'a' and 'b' from the node's ROI list a*b : the intersection of ROIs 'a' and 'b' a-b : ROI 'a' with ROI 'b' removed -a : the negative of ROI 'a' (i.e. the entire image area as an ROI but with a hole in it) roi(img) - p : any ROIs on the image already, but with the ROI on input 'p' cut out Connections Inputs Index Name Type Desc 0 (none) img Image input 1 p roi ROI which appears as 'p' in expression 2 q roi ROI which appears as 'q' in expression 3 r roi ROI which appears as 'r' in expression Outputs Index Name Type Desc 0 (none) img Output image with ROI from expression result imposed 1 (none) roi The ROI generated from the expression","title":"roiexpr"},{"location":"autodocs/roiexpr/#roiexpr","text":"","title":"roiexpr"},{"location":"autodocs/roiexpr/#description","text":"This node allows a region of interest to be composed from several regions of interest using an expression and imposed on an image. It is not a node for creating several ROIs at once - the output is always a single ROI . ROIs can be created for use within the expression by using the \"Add ROI\" button. These will be assigned to the variables a,b,c.. within the expression, and can be edited by: clicking on their label in the left-most column of the table (to select the entire row) and then clicking and dragging on the canvas, double clicking on the description text in the table to open a numerical editor (not for poly or painted). Additional ROIs can be connected to the p, q, r inputs; these will be assigned to those variables within the expression. The input image's ROIs are combined into a single ROI and assigned to the variable 'i'. The input image itself is available as the variable 'img'. Other properties of the image are available and other calculations may be made, but the result of the expression must be an ROI. Examples: a+b : the union of ROIs 'a' and 'b' from the node's ROI list a*b : the intersection of ROIs 'a' and 'b' a-b : ROI 'a' with ROI 'b' removed -a : the negative of ROI 'a' (i.e. the entire image area as an ROI but with a hole in it) roi(img) - p : any ROIs on the image already, but with the ROI on input 'p' cut out","title":"Description"},{"location":"autodocs/roiexpr/#connections","text":"","title":"Connections"},{"location":"autodocs/roiexpr/#inputs","text":"Index Name Type Desc 0 (none) img Image input 1 p roi ROI which appears as 'p' in expression 2 q roi ROI which appears as 'q' in expression 3 r roi ROI which appears as 'r' in expression","title":"Inputs"},{"location":"autodocs/roiexpr/#outputs","text":"Index Name Type Desc 0 (none) img Output image with ROI from expression result imposed 1 (none) roi The ROI generated from the expression","title":"Outputs"},{"location":"autodocs/scalartest/","text":"scalartest Description Test a scalar against a value Connections Inputs Index Name Type Desc 0 (none) number (none) Outputs Index Name Type Desc 0 results testresult (none)","title":"scalartest"},{"location":"autodocs/scalartest/#scalartest","text":"","title":"scalartest"},{"location":"autodocs/scalartest/#description","text":"Test a scalar against a value","title":"Description"},{"location":"autodocs/scalartest/#connections","text":"","title":"Connections"},{"location":"autodocs/scalartest/#inputs","text":"Index Name Type Desc 0 (none) number (none)","title":"Inputs"},{"location":"autodocs/scalartest/#outputs","text":"Index Name Type Desc 0 results testresult (none)","title":"Outputs"},{"location":"autodocs/sink/","text":"sink Description This provides a simple way to view any kind of data - images will be shown on a canvas, other data will be converted to text. Connections Inputs Index Name Type Desc 0 (none) any (none)","title":"sink"},{"location":"autodocs/sink/#sink","text":"","title":"sink"},{"location":"autodocs/sink/#description","text":"This provides a simple way to view any kind of data - images will be shown on a canvas, other data will be converted to text.","title":"Description"},{"location":"autodocs/sink/#connections","text":"","title":"Connections"},{"location":"autodocs/sink/#inputs","text":"Index Name Type Desc 0 (none) any (none)","title":"Inputs"},{"location":"autodocs/spectrum/","text":"spectrum Description Show the mean intensities for each frequency band in each region of interest (ROI) in each input. If an input has no ROI, the intensities of all the pixels in the input are used. It's quite possible for the different inputs to be different images, to permit comparison. Each region (or input) has a separate line in the resulting plot, labelled with the annotation for the ROI (or \"inputN\" for an input with no ROI). If ROIs in different inputs have the same annotation, they are labelled as \"inN:annotation\" where N is the input number. Each pixel has its own variance, so the shown variance is the pooled variance of all the pixels in the region. This is calculated as the variance of the means, plus the mean of the variances. If a point has data with BAD DQ bits in a band, those pixels are ignored in that band. If there are no good points, the point is not plotted for that band. A table of the values is also produced, and this output as CSV text. The table has one row per ROI or input, and the columns name - the name of the ROI or input m wavelength - the mean intensity for the given wavelength band s wavelength - the standard deviation of the mean intensity for the given wavelength band p wavelength - the number of pixels in the given wavelength band (usually the same as the number of pixels in the ROI, but may be fewer if the ROI has \"bad\" pixels in that band) The last two columns are repeated for each wavelength band. Connections Inputs Index Name Type Desc 0 0 img a single line in the plot 1 1 img a single line in the plot 2 2 img a single line in the plot 3 3 img a single line in the plot 4 4 img a single line in the plot 5 5 img a single line in the plot 6 6 img a single line in the plot 7 7 img a single line in the plot Outputs Index Name Type Desc 0 data data a CSV output (use 'dump' or 'sink' to read it)","title":"spectrum"},{"location":"autodocs/spectrum/#spectrum","text":"","title":"spectrum"},{"location":"autodocs/spectrum/#description","text":"Show the mean intensities for each frequency band in each region of interest (ROI) in each input. If an input has no ROI, the intensities of all the pixels in the input are used. It's quite possible for the different inputs to be different images, to permit comparison. Each region (or input) has a separate line in the resulting plot, labelled with the annotation for the ROI (or \"inputN\" for an input with no ROI). If ROIs in different inputs have the same annotation, they are labelled as \"inN:annotation\" where N is the input number. Each pixel has its own variance, so the shown variance is the pooled variance of all the pixels in the region. This is calculated as the variance of the means, plus the mean of the variances. If a point has data with BAD DQ bits in a band, those pixels are ignored in that band. If there are no good points, the point is not plotted for that band. A table of the values is also produced, and this output as CSV text. The table has one row per ROI or input, and the columns name - the name of the ROI or input m wavelength - the mean intensity for the given wavelength band s wavelength - the standard deviation of the mean intensity for the given wavelength band p wavelength - the number of pixels in the given wavelength band (usually the same as the number of pixels in the ROI, but may be fewer if the ROI has \"bad\" pixels in that band) The last two columns are repeated for each wavelength band.","title":"Description"},{"location":"autodocs/spectrum/#connections","text":"","title":"Connections"},{"location":"autodocs/spectrum/#inputs","text":"Index Name Type Desc 0 0 img a single line in the plot 1 1 img a single line in the plot 2 2 img a single line in the plot 3 3 img a single line in the plot 4 4 img a single line in the plot 5 5 img a single line in the plot 6 6 img a single line in the plot 7 7 img a single line in the plot","title":"Inputs"},{"location":"autodocs/spectrum/#outputs","text":"Index Name Type Desc 0 data data a CSV output (use 'dump' or 'sink' to read it)","title":"Outputs"},{"location":"autodocs/stitch/","text":"stitch Description This node performs manual stitching of multiple images into a single image. Connections Inputs Index Name Type Desc 0 0 img Input image 0 1 1 img Input image 1 2 2 img Input image 2 3 3 img Input image 3 4 4 img Input image 4 5 5 img Input image 5 6 6 img Input image 6 7 7 img Input image 7 Outputs Index Name Type Desc 0 (none) img Output image","title":"stitch"},{"location":"autodocs/stitch/#stitch","text":"","title":"stitch"},{"location":"autodocs/stitch/#description","text":"This node performs manual stitching of multiple images into a single image.","title":"Description"},{"location":"autodocs/stitch/#connections","text":"","title":"Connections"},{"location":"autodocs/stitch/#inputs","text":"Index Name Type Desc 0 0 img Input image 0 1 1 img Input image 1 2 2 img Input image 2 3 3 img Input image 3 4 4 img Input image 4 5 5 img Input image 5 6 6 img Input image 6 7 7 img Input image 7","title":"Inputs"},{"location":"autodocs/stitch/#outputs","text":"Index Name Type Desc 0 (none) img Output image","title":"Outputs"},{"location":"autodocs/stringtest/","text":"stringtest Description Convert the output of a node into string. Assert that this matches a given string. Both strings are stripped of whitespace and CRLF is converted to LF. Connections Inputs Index Name Type Desc 0 (none) any (none) Outputs Index Name Type Desc 0 (none) testresult (none)","title":"stringtest"},{"location":"autodocs/stringtest/#stringtest","text":"","title":"stringtest"},{"location":"autodocs/stringtest/#description","text":"Convert the output of a node into string. Assert that this matches a given string. Both strings are stripped of whitespace and CRLF is converted to LF.","title":"Description"},{"location":"autodocs/stringtest/#connections","text":"","title":"Connections"},{"location":"autodocs/stringtest/#inputs","text":"Index Name Type Desc 0 (none) any (none)","title":"Inputs"},{"location":"autodocs/stringtest/#outputs","text":"Index Name Type Desc 0 (none) testresult (none)","title":"Outputs"},{"location":"autodocs/striproi/","text":"striproi Description Strip ROIs from an image Connections Inputs Index Name Type Desc 0 (none) img (none) Outputs Index Name Type Desc 0 (none) img (none)","title":"striproi"},{"location":"autodocs/striproi/#striproi","text":"","title":"striproi"},{"location":"autodocs/striproi/#description","text":"Strip ROIs from an image","title":"Description"},{"location":"autodocs/striproi/#connections","text":"","title":"Connections"},{"location":"autodocs/striproi/#inputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Inputs"},{"location":"autodocs/striproi/#outputs","text":"Index Name Type Desc 0 (none) img (none)","title":"Outputs"},{"location":"autodocs/tvl1_autoreg/","text":"tvl1 autoreg Description Use the TV-L1 solver to find an optical flow field for transforming one image into another. Not generally advised, and very slow. The node will output a version of the 'moving' image, distorted to map onto the 'fixed' image. Propagates uncertainty of the moving image by distorting that of the source image, and propagates DQs using nearest neighbour. Connections Inputs Index Name Type Desc 0 moving img (none) 1 fixed img (none) Outputs Index Name Type Desc 0 moved img (none)","title":"tvl1 autoreg"},{"location":"autodocs/tvl1_autoreg/#tvl1-autoreg","text":"","title":"tvl1 autoreg"},{"location":"autodocs/tvl1_autoreg/#description","text":"Use the TV-L1 solver to find an optical flow field for transforming one image into another. Not generally advised, and very slow. The node will output a version of the 'moving' image, distorted to map onto the 'fixed' image. Propagates uncertainty of the moving image by distorting that of the source image, and propagates DQs using nearest neighbour.","title":"Description"},{"location":"autodocs/tvl1_autoreg/#connections","text":"","title":"Connections"},{"location":"autodocs/tvl1_autoreg/#inputs","text":"Index Name Type Desc 0 moving img (none) 1 fixed img (none)","title":"Inputs"},{"location":"autodocs/tvl1_autoreg/#outputs","text":"Index Name Type Desc 0 moved img (none)","title":"Outputs"},{"location":"devguide/","text":"Overview These pages describe how to develop plugins for PCOT and use PCOT as a library, and may also contain various internal developer notes. Much of this documentation is quite brief - for developing plugins and libraries it's a good idea to supplement your knowledge by looking at examples and reading the source code! Important classes - probably read this first. Using PCOT as a library Writing PCOT plugins Miscellaneous notes on type internals and adding new types . notes on how operations/functions on Datum and Value work test/scratchpad of LaTeX support .","title":"Overview"},{"location":"devguide/#overview","text":"These pages describe how to develop plugins for PCOT and use PCOT as a library, and may also contain various internal developer notes. Much of this documentation is quite brief - for developing plugins and libraries it's a good idea to supplement your knowledge by looking at examples and reading the source code! Important classes - probably read this first. Using PCOT as a library Writing PCOT plugins","title":"Overview"},{"location":"devguide/#miscellaneous","text":"notes on type internals and adding new types . notes on how operations/functions on Datum and Value work test/scratchpad of LaTeX support .","title":"Miscellaneous"},{"location":"devguide/classes/","text":"Important classes pcot.document.Document, and an overview PCOT keeps all its user data in a pcot.document.Document : This contains: a pcot.documentsettings.DocumentSettings object a pcot.inputs.inp.InputManager object handling the inputs a pcot.document.UndoRedoStore which uses the serialisation/deserialisation system to handle an undo stack A dictionary of pcot.macros.XFormMacro objects - the user macros most importantly a pcot.xform.XFormGraph - a set of nodes of type pcot.xform.XForm connected together to do things. Each XForm points to a pcot.xform.XFormType singleton which controls its behaviour. Nodes (XForms) communicate by passing pcot.datum.Datum objects. Each Datum points to a pcot.datumtype.Type subclass singleton providing serialisation, copy, and display facilities. They also have values - two common value classes are pcot.imagecube.ImageCube for images, and pcot.value.Value objects for other arrays and scalars. ImageCubes can generate SubImageCube objects which are subsets of the image covered by ROIs and with bad pixels masked out (\"bad\" according to the data quality bits). pcot.xform.XFormGraph This represents the graph of nodes which take data from inputs and perform operations on them. There is one inside the document, and instances of macros also contain their own graphs (and the macro itself contains a \"template\" graph from which these are created). A graph contains: A set of pcot.xform.XForm objects (usually called nodes ) connected together by their inputs fields, which are tuples of (source node, index of output). A graph runs by finding those nodes which have no inputs and performing them; the entire graph will be recursively traversed. Nodes are only run if their inputs have data (if their parent nodes have run). pcot.xform.XForm All nodes are of the same type. Polymorphism - different nodes behaving differently - is accomplished through each node having a reference to a pcot.xform.XFormType object in its type member that controls its behaviour. Nodes communicate by passing pcot.datum.Datum objects. When a node runs its perform method It reads the inputs by deferencing the inputs fields and getting the input node and index of the output of that node, and reading the Datum stored in that output. It processes the data and stores the results in its outputs field as Datum objects. It then performs its \"child\" nodes. pcot.datum.Datum This is the fundamental data type, used both for communicating between nodes and handling values in the expr node. A Datum has a type , which is a pcot.datumtypes.Type object a value , whose type depends on the type field a source indicating where the data came from If the value is None , the Datum is null. There is a constant Datum.null for null data. pcot.datumtypes.Type The DatumType object provides methods for serialisation, copying, and display. Each is a singleton. It's easy to create custom DatumTypes. The most commonly used builtins are: Datum.IMG : contains an ImageCube Datum.NUMBER : contains a Value (these names are for the singleton objects, not their types - for example, Datum.IMG has the type pcot.datumtypes.ImgType .) pcot.imagecube.ImageCube This is the fundamental image type, consisting of image data - 2D (H x W) if there is only one band (channel), 3D otherwise (H x W x D). Type is float32. uncertainty data , same shape and type as image data. This is the standard deviation of each pixel. DQ data . This a 16-bit bitfield for each pixel. Some of these are considered errors - these are called \"bad\" DQ bits (e.g. no uncertainty, saturated, results from a division by zero). regions of interest annotations that have been added to the image mapping used to render the image as RGB sources for each band pcot.imagecube.SubImageCube These objects can be generated by calling the subimg() method on an ImageCube. They are the subset of an image covered by the regions of interest it has, along with a mask for those regions. Additionally, \"bad\" parts of the image (which can be different in different bands) can be masked out. Operations on images are typically done on these subimages, and then modifyWithSub is called on the imagecube to return a copy of that imagecube with the modified subimage spliced in. Useful subimage methods include: masked() : return the masked nominal image data maskedUncertainty() : return the masked uncertainty data maskedDQ() : return the masked DQ bits All these return numpy masked arrays. pcot.value.Value This is the fundamental numeric type, consisting of nominal value uncertainty value (standard deviation) DQ bits It's usually used for scalars but can also hold array data - internally ImageCubes (or parts of them) are converted into array Values for maths. If it does hold array data, the three elements must be the same shape. It's possible to create 1D vectors in an expr node using square brackets, and some functions and operations return such vectors. This type supports mathematical operations which propagate uncertainty and DQ. More on how Values work here Note : You may wonder why ImageCube and SubImageCube don't use Value internally. The answer is simply historical reasons: they were created a very long time before Value, and refactoring now could cause huge problems.","title":"Important classes"},{"location":"devguide/classes/#important-classes","text":"","title":"Important classes"},{"location":"devguide/classes/#pcotdocumentdocument-and-an-overview","text":"PCOT keeps all its user data in a pcot.document.Document : This contains: a pcot.documentsettings.DocumentSettings object a pcot.inputs.inp.InputManager object handling the inputs a pcot.document.UndoRedoStore which uses the serialisation/deserialisation system to handle an undo stack A dictionary of pcot.macros.XFormMacro objects - the user macros most importantly a pcot.xform.XFormGraph - a set of nodes of type pcot.xform.XForm connected together to do things. Each XForm points to a pcot.xform.XFormType singleton which controls its behaviour. Nodes (XForms) communicate by passing pcot.datum.Datum objects. Each Datum points to a pcot.datumtype.Type subclass singleton providing serialisation, copy, and display facilities. They also have values - two common value classes are pcot.imagecube.ImageCube for images, and pcot.value.Value objects for other arrays and scalars. ImageCubes can generate SubImageCube objects which are subsets of the image covered by ROIs and with bad pixels masked out (\"bad\" according to the data quality bits).","title":"pcot.document.Document, and an overview"},{"location":"devguide/classes/#pcotxformxformgraph","text":"This represents the graph of nodes which take data from inputs and perform operations on them. There is one inside the document, and instances of macros also contain their own graphs (and the macro itself contains a \"template\" graph from which these are created). A graph contains: A set of pcot.xform.XForm objects (usually called nodes ) connected together by their inputs fields, which are tuples of (source node, index of output). A graph runs by finding those nodes which have no inputs and performing them; the entire graph will be recursively traversed. Nodes are only run if their inputs have data (if their parent nodes have run).","title":"pcot.xform.XFormGraph"},{"location":"devguide/classes/#pcotxformxform","text":"All nodes are of the same type. Polymorphism - different nodes behaving differently - is accomplished through each node having a reference to a pcot.xform.XFormType object in its type member that controls its behaviour. Nodes communicate by passing pcot.datum.Datum objects. When a node runs its perform method It reads the inputs by deferencing the inputs fields and getting the input node and index of the output of that node, and reading the Datum stored in that output. It processes the data and stores the results in its outputs field as Datum objects. It then performs its \"child\" nodes.","title":"pcot.xform.XForm"},{"location":"devguide/classes/#pcotdatumdatum","text":"This is the fundamental data type, used both for communicating between nodes and handling values in the expr node. A Datum has a type , which is a pcot.datumtypes.Type object a value , whose type depends on the type field a source indicating where the data came from If the value is None , the Datum is null. There is a constant Datum.null for null data.","title":"pcot.datum.Datum"},{"location":"devguide/classes/#pcotdatumtypestype","text":"The DatumType object provides methods for serialisation, copying, and display. Each is a singleton. It's easy to create custom DatumTypes. The most commonly used builtins are: Datum.IMG : contains an ImageCube Datum.NUMBER : contains a Value (these names are for the singleton objects, not their types - for example, Datum.IMG has the type pcot.datumtypes.ImgType .)","title":"pcot.datumtypes.Type"},{"location":"devguide/classes/#pcotimagecubeimagecube","text":"This is the fundamental image type, consisting of image data - 2D (H x W) if there is only one band (channel), 3D otherwise (H x W x D). Type is float32. uncertainty data , same shape and type as image data. This is the standard deviation of each pixel. DQ data . This a 16-bit bitfield for each pixel. Some of these are considered errors - these are called \"bad\" DQ bits (e.g. no uncertainty, saturated, results from a division by zero). regions of interest annotations that have been added to the image mapping used to render the image as RGB sources for each band","title":"pcot.imagecube.ImageCube"},{"location":"devguide/classes/#pcotimagecubesubimagecube","text":"These objects can be generated by calling the subimg() method on an ImageCube. They are the subset of an image covered by the regions of interest it has, along with a mask for those regions. Additionally, \"bad\" parts of the image (which can be different in different bands) can be masked out. Operations on images are typically done on these subimages, and then modifyWithSub is called on the imagecube to return a copy of that imagecube with the modified subimage spliced in. Useful subimage methods include: masked() : return the masked nominal image data maskedUncertainty() : return the masked uncertainty data maskedDQ() : return the masked DQ bits All these return numpy masked arrays.","title":"pcot.imagecube.SubImageCube"},{"location":"devguide/classes/#pcotvaluevalue","text":"This is the fundamental numeric type, consisting of nominal value uncertainty value (standard deviation) DQ bits It's usually used for scalars but can also hold array data - internally ImageCubes (or parts of them) are converted into array Values for maths. If it does hold array data, the three elements must be the same shape. It's possible to create 1D vectors in an expr node using square brackets, and some functions and operations return such vectors. This type supports mathematical operations which propagate uncertainty and DQ. More on how Values work here Note : You may wonder why ImageCube and SubImageCube don't use Value internally. The answer is simply historical reasons: they were created a very long time before Value, and refactoring now could cause huge problems.","title":"pcot.value.Value"},{"location":"devguide/foo/","text":"Writing PCOT plugins The plugin path PCOT can be extended by adding Python scripts to a directory in the plugin path. By default this is just pcotplugins in the user's home directory, but it can be changed by editing the .pcot.ini file in that directory and modifying the pluginpath value in the Locations section. This should be a semicolon-separated list of directories. You may be tempted to use quotes in the names of you plugin directories: don't. It should work fine, even if your directories have spaces in them. For example, use ~/blah/RIM Dewarping and not ~/blah/\"RIM Dewarping\" Adding new types This is covered in a separate document , as it's not often done and is a little involved. Adding new Datum functions (for use in expr and Python code) The functions used in the expr expression node can also be used in Python programs which use PCOT as a library (this is covered here ). These are called Datum functions because they both take and return Datum objects. To create a Datum function: use the @datumfunc decorator. This will register the function and wrap it in two separate wrappers: one for use in expr , the other for use in Python. write a docstring in the correct format, as illustrated below. Here's an example which declares a function to take two numbers a,b and calculate a+b*2. Note that as in all PCOT code we have to make sure the sources are handled correctly. @datumfunc def example_func(a, b): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param a: number: first number @param b: number: second number \"\"\" return a + b * Datum.k(2) This is a trivial example that relies on Datum objects having operator overloads, but note that we need to multiply b by a Datum, not a number. To do this we use Datum.k to create a scalar constant. The docstring This should consist of a number of lines describing the function followed by a number of @param lines, one for each parameter. These contain the following, separated by colons: The string `@param' A Datum type name - these can be found in the constructors of datum type objects in datumtypes.py , but the most common are number , img , roi , string . A description of the parameter Optional numeric/string arguments Optional arguments with defaults can be provided, but only if they are numeric or strings (because these are the only types which make sense for the default values). In this case the defaults will be converted to Datum objects if they are used. Here is an example of a function which multiplies an image by a constant, with the default being 2: @datumfunc def example_func(img, k=2): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param img:img:the image @param k:number:the multiplier \"\"\" # no need to construct a Datum with Datum.k(), because k is already # a Datum. return img * k Here's another example which adds two numbers or multiplies them, depending on a string - and the default is to add: @datumfunc def stringexample(a, b, op='add'): \"\"\" String argument example @param a: number: first number @param b: number: second number @param op: string: operation to perform \"\"\" if op.get(Datum.STRING) == 'add': return a + b elif op.get(Datum.STRING) == 'sub': return a - b else: raise ValueError(\"Unknown operation\") Note that you usually have to extract the actual value from the Datum objects, as we do with the op argument above. In previous examples, we take advantage of Datum's extensive operator overloading. Variadic arguments For a variable number of arguments, use the *args keyword. Here, you have to check the types by hand. For example, this function will sum numbers: @datumfunc def sumall(*args): \"\"\" Sum all arguments \"\"\" s = sum([x.get(Datum.NUMBER).n for x in args]) return Datum(Datum.NUMBER, Value(s, 0, NOUNCERTAINTY), nullSourceSet) Note the use of Value here to construct a scalar value with standard deviation (zero here) and DQ bits (indicating no uncertainty data). Also note the mandatory use of a source set - just the nullSourceSet here to indicate there is no source; this is just a test function. In a real function we would combine the input sources. For more examples of functions, look at the ExpressionEvaluator constructor in the pcot.expressions.eval module. Adding new menu items This is done by adding a function to a list of functions called when a a main window is opened. Writing code here will require some knowledge of Qt. Here is a menu option added to the File menu which will look for an input 0 node and save its output image (if there is one) to an ENVI file. import pcot import os from PySide2 import QtWidgets from PySide2.QtWidgets import QAction, QMessageBox from pcot.dataformats import envi def saveEnvi(w): \"\"\"Function takes a PCOT main window. It finds the input 0 if it can, and then saves an ENVI from that image.\"\"\" try: node = w.doc.getNodeByName(\"input 0\") except NameError: print(\"cannot find node\") return res = QtWidgets.QFileDialog.getSaveFileName(w, \"ENVI file \", os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')), \"ENVI files (*.hdr)\") if res[0] != '': (root, ext) = os.path.splitext(res[0]) # get the output of that input 0 node img = node.getOutput(0,pcot.datum.Datum.IMG) envi.write(root,img) # the function to add the new menu item. This will take a single parameter: # the window to which the menu should be added. def addMenus(w): \"\"\"Add an item to the Edit menu\"\"\" # create the menu action (i.e. the item) act = QAction(\"save to ENVI\",parent=w) # find (or create) the Edit menu and add the action to it w.findOrAddMenu(\"Edit\").addAction(act) # link the action to the saveEnvi function, using a closure to # make sure the window argument is passed into that function. act.triggered.connect(lambda: saveEnvi(w)) # Add the addMenus functions to the list of functions called as the # main window opens. pcot.config.addMainWindowHook(addMenus) Adding new node types Node types are represented by singletons of subclasses of XFormType (Nodes themselves are of type XForm , which stands for transform for historical reasons). Developing new XFormType subclasses is largely beyond the scope of this document, but you can learn a lot from looking at the pcot.xforms package and the modules therein. To create a new node type, declare a new subclass of XFormType and decorate it with the @xformtype decorator. This will make the type autoregister: the singleton will be constructed and added to the internal type registry. You will need to write the following methods in your subclass: __init__(self) to construct the type object (NOT the individual nodes). This will call the superconstructor to set the type's name, group (for the palette), and version. It will add the input and output connectors. init(self, node) will initialise any private data in the node itself (which is an XForm ). Don't confuse this with __init__ ! createTab(self, node, window) will create a new node area (i.e. tab). Often, this can be a TabData which will look at the node's out attribute, which should be a Datum. perform(self, node) will actually perform the node's action, reading inputs and setting outputs. Remember: there is only one XFormType object for each node type. All nodes are of type XForm , and they link to an XFormType object to tell them how to behave. This might seem a really odd way to do things, but it follows \"favour composition over inheritance\" and saves messiness elsewhere. Here is an example which does edge detection with OpenCV: import cv2 as cv import numpy as np from pcot.sources import SourceSet from pcot.xform import XFormType, xformtype, Datum from pcot.xforms.tabdata import TabData from pcot.imagecube import ImageCube import pcot.config # The first part of the plugin creates a new type of node. # this decorator will cause the node to auto-register. @xformtype class XFormEdgeDetect(XFormType): \"\"\"This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab.\"\"\" def __init__(self): # this node should appear in the maths group. super().__init__(\"edge\", \"maths\", \"0.0.0\") # set input and output - they are images and are unnamed. self.addInputConnector(\"\", Datum.IMG) self.addOutputConnector(\"\", Datum.IMG) def createTab(self, n, w): # there is no custom tab, we just use a data canvas. This expects \"node.out\" to be set to # either None or a Datum. return TabData(n, w) def init(self, n): # No initialisation required. pass def perform(self, node): # get the input image img = node.getInput(0, Datum.IMG) if img is not None: # find mean of all channels - construct a transform array and then use it. mat = np.array([1 / img.channels] * img.channels).reshape((1, img.channels)) grey = cv.transform(img.img, mat) # convert to 8-bit integer from 32-bit float img8 = (grey * 255).astype('uint8') # Perform edge detection out = cv.Canny(img8, 100, 200) # Convert back to 32-bit float out = (out / 255).astype('float32') # create the imagecube and set node.out for the canvas in the tab img = ImageCube(out, None, img.sources) node.out = Datum(Datum.IMG, img) else: # no image on the input, set node.out to None node.out = Datum.null # output node.out node.setOutput(0, node.out) Writing custom Tabs As noted above, a new XFormType subclass (i.e. a new node type) can often just use TabData , which will display the Datum stored on its first output (output 0). Sometimes, however, a custom tab needs to be written. This can be a complex task, but an example is given in xformexample.py in the xforms package. All the standard XFormTypes are in this package, so you can also look at them. The basic idea is: Create a subclass of pcot.ui.tabs.Tab Write the constructor to call the superclass constructor and create the UI (or load a Designer-created UI by passing an argument to the superclass. constructor), and call self.nodeChanged() at the end to refresh the tab from the node. Override onNodeChanged() to update the tab from the node. Use the Qt signal/slot mechanism to connect the tab's controls to methods in the tab class and write code to update the node from the tab in these methods, calling self.changed() at the end of each method. The tab will have a node field which addresses the node it is viewing (but see below for a \"gotcha\" - the value of this field will change after an undo operation!) Using Canvas in custom tabs Creating a Canvas programmatically is straightforward, and there is an example of this in xformexample.py . If you are creating a tab in Designer, you need to add a canvas as a QWidget which you then \"promote\" to a custom control (the canvas). In the promote dialog, the class should be Canvas and the header file pcot.ui.canvas (i.e. the package name). In your onNodeChanged() method you will need to update the canvas. This involves doing a little setup, then getting the image we want to display - usually the output - and telling the canvas to display it: # do some setup self.canvas.setNode(self.node) # then display the image img = self.node.getOutput(0, Datum.IMG) self.canvas.display(img) The setup synchronises the canvas with the node, telling the canvas about the RGB mappings and that it should store data in the node for serialisation. We have to set that up each time because of how undo works, which is discussed in the next section. Undo and references to data in nodes This is a major \"gotcha.\" Whenever an undo occurs, the old node is discarded and a new node created from a previously archived version in memory. This means that the node field changes. Because of this, your tab must not store references to objects inside the node, because after an undo those references will be stale. Instead, always use self.node... to access data. It is OK to use the tab to store UI-only data which is not persisted (saved to a file or to the undo mechanism).","title":"Writing PCOT plugins"},{"location":"devguide/foo/#writing-pcot-plugins","text":"","title":"Writing PCOT plugins"},{"location":"devguide/foo/#the-plugin-path","text":"PCOT can be extended by adding Python scripts to a directory in the plugin path. By default this is just pcotplugins in the user's home directory, but it can be changed by editing the .pcot.ini file in that directory and modifying the pluginpath value in the Locations section. This should be a semicolon-separated list of directories. You may be tempted to use quotes in the names of you plugin directories: don't. It should work fine, even if your directories have spaces in them. For example, use ~/blah/RIM Dewarping and not ~/blah/\"RIM Dewarping\"","title":"The plugin path"},{"location":"devguide/foo/#adding-new-types","text":"This is covered in a separate document , as it's not often done and is a little involved.","title":"Adding new types"},{"location":"devguide/foo/#adding-new-datum-functions-for-use-in-expr-and-python-code","text":"The functions used in the expr expression node can also be used in Python programs which use PCOT as a library (this is covered here ). These are called Datum functions because they both take and return Datum objects. To create a Datum function: use the @datumfunc decorator. This will register the function and wrap it in two separate wrappers: one for use in expr , the other for use in Python. write a docstring in the correct format, as illustrated below. Here's an example which declares a function to take two numbers a,b and calculate a+b*2. Note that as in all PCOT code we have to make sure the sources are handled correctly. @datumfunc def example_func(a, b): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param a: number: first number @param b: number: second number \"\"\" return a + b * Datum.k(2) This is a trivial example that relies on Datum objects having operator overloads, but note that we need to multiply b by a Datum, not a number. To do this we use Datum.k to create a scalar constant.","title":"Adding new Datum functions (for use in expr and Python code)"},{"location":"devguide/foo/#the-docstring","text":"This should consist of a number of lines describing the function followed by a number of @param lines, one for each parameter. These contain the following, separated by colons: The string `@param' A Datum type name - these can be found in the constructors of datum type objects in datumtypes.py , but the most common are number , img , roi , string . A description of the parameter","title":"The docstring"},{"location":"devguide/foo/#optional-numericstring-arguments","text":"Optional arguments with defaults can be provided, but only if they are numeric or strings (because these are the only types which make sense for the default values). In this case the defaults will be converted to Datum objects if they are used. Here is an example of a function which multiplies an image by a constant, with the default being 2: @datumfunc def example_func(img, k=2): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param img:img:the image @param k:number:the multiplier \"\"\" # no need to construct a Datum with Datum.k(), because k is already # a Datum. return img * k Here's another example which adds two numbers or multiplies them, depending on a string - and the default is to add: @datumfunc def stringexample(a, b, op='add'): \"\"\" String argument example @param a: number: first number @param b: number: second number @param op: string: operation to perform \"\"\" if op.get(Datum.STRING) == 'add': return a + b elif op.get(Datum.STRING) == 'sub': return a - b else: raise ValueError(\"Unknown operation\") Note that you usually have to extract the actual value from the Datum objects, as we do with the op argument above. In previous examples, we take advantage of Datum's extensive operator overloading.","title":"Optional numeric/string arguments"},{"location":"devguide/foo/#variadic-arguments","text":"For a variable number of arguments, use the *args keyword. Here, you have to check the types by hand. For example, this function will sum numbers: @datumfunc def sumall(*args): \"\"\" Sum all arguments \"\"\" s = sum([x.get(Datum.NUMBER).n for x in args]) return Datum(Datum.NUMBER, Value(s, 0, NOUNCERTAINTY), nullSourceSet) Note the use of Value here to construct a scalar value with standard deviation (zero here) and DQ bits (indicating no uncertainty data). Also note the mandatory use of a source set - just the nullSourceSet here to indicate there is no source; this is just a test function. In a real function we would combine the input sources. For more examples of functions, look at the ExpressionEvaluator constructor in the pcot.expressions.eval module.","title":"Variadic arguments"},{"location":"devguide/foo/#adding-new-menu-items","text":"This is done by adding a function to a list of functions called when a a main window is opened. Writing code here will require some knowledge of Qt. Here is a menu option added to the File menu which will look for an input 0 node and save its output image (if there is one) to an ENVI file. import pcot import os from PySide2 import QtWidgets from PySide2.QtWidgets import QAction, QMessageBox from pcot.dataformats import envi def saveEnvi(w): \"\"\"Function takes a PCOT main window. It finds the input 0 if it can, and then saves an ENVI from that image.\"\"\" try: node = w.doc.getNodeByName(\"input 0\") except NameError: print(\"cannot find node\") return res = QtWidgets.QFileDialog.getSaveFileName(w, \"ENVI file \", os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')), \"ENVI files (*.hdr)\") if res[0] != '': (root, ext) = os.path.splitext(res[0]) # get the output of that input 0 node img = node.getOutput(0,pcot.datum.Datum.IMG) envi.write(root,img) # the function to add the new menu item. This will take a single parameter: # the window to which the menu should be added. def addMenus(w): \"\"\"Add an item to the Edit menu\"\"\" # create the menu action (i.e. the item) act = QAction(\"save to ENVI\",parent=w) # find (or create) the Edit menu and add the action to it w.findOrAddMenu(\"Edit\").addAction(act) # link the action to the saveEnvi function, using a closure to # make sure the window argument is passed into that function. act.triggered.connect(lambda: saveEnvi(w)) # Add the addMenus functions to the list of functions called as the # main window opens. pcot.config.addMainWindowHook(addMenus)","title":"Adding new menu items"},{"location":"devguide/foo/#adding-new-node-types","text":"Node types are represented by singletons of subclasses of XFormType (Nodes themselves are of type XForm , which stands for transform for historical reasons). Developing new XFormType subclasses is largely beyond the scope of this document, but you can learn a lot from looking at the pcot.xforms package and the modules therein. To create a new node type, declare a new subclass of XFormType and decorate it with the @xformtype decorator. This will make the type autoregister: the singleton will be constructed and added to the internal type registry. You will need to write the following methods in your subclass: __init__(self) to construct the type object (NOT the individual nodes). This will call the superconstructor to set the type's name, group (for the palette), and version. It will add the input and output connectors. init(self, node) will initialise any private data in the node itself (which is an XForm ). Don't confuse this with __init__ ! createTab(self, node, window) will create a new node area (i.e. tab). Often, this can be a TabData which will look at the node's out attribute, which should be a Datum. perform(self, node) will actually perform the node's action, reading inputs and setting outputs. Remember: there is only one XFormType object for each node type. All nodes are of type XForm , and they link to an XFormType object to tell them how to behave. This might seem a really odd way to do things, but it follows \"favour composition over inheritance\" and saves messiness elsewhere. Here is an example which does edge detection with OpenCV: import cv2 as cv import numpy as np from pcot.sources import SourceSet from pcot.xform import XFormType, xformtype, Datum from pcot.xforms.tabdata import TabData from pcot.imagecube import ImageCube import pcot.config # The first part of the plugin creates a new type of node. # this decorator will cause the node to auto-register. @xformtype class XFormEdgeDetect(XFormType): \"\"\"This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab.\"\"\" def __init__(self): # this node should appear in the maths group. super().__init__(\"edge\", \"maths\", \"0.0.0\") # set input and output - they are images and are unnamed. self.addInputConnector(\"\", Datum.IMG) self.addOutputConnector(\"\", Datum.IMG) def createTab(self, n, w): # there is no custom tab, we just use a data canvas. This expects \"node.out\" to be set to # either None or a Datum. return TabData(n, w) def init(self, n): # No initialisation required. pass def perform(self, node): # get the input image img = node.getInput(0, Datum.IMG) if img is not None: # find mean of all channels - construct a transform array and then use it. mat = np.array([1 / img.channels] * img.channels).reshape((1, img.channels)) grey = cv.transform(img.img, mat) # convert to 8-bit integer from 32-bit float img8 = (grey * 255).astype('uint8') # Perform edge detection out = cv.Canny(img8, 100, 200) # Convert back to 32-bit float out = (out / 255).astype('float32') # create the imagecube and set node.out for the canvas in the tab img = ImageCube(out, None, img.sources) node.out = Datum(Datum.IMG, img) else: # no image on the input, set node.out to None node.out = Datum.null # output node.out node.setOutput(0, node.out)","title":"Adding new node types"},{"location":"devguide/foo/#writing-custom-tabs","text":"As noted above, a new XFormType subclass (i.e. a new node type) can often just use TabData , which will display the Datum stored on its first output (output 0). Sometimes, however, a custom tab needs to be written. This can be a complex task, but an example is given in xformexample.py in the xforms package. All the standard XFormTypes are in this package, so you can also look at them. The basic idea is: Create a subclass of pcot.ui.tabs.Tab Write the constructor to call the superclass constructor and create the UI (or load a Designer-created UI by passing an argument to the superclass. constructor), and call self.nodeChanged() at the end to refresh the tab from the node. Override onNodeChanged() to update the tab from the node. Use the Qt signal/slot mechanism to connect the tab's controls to methods in the tab class and write code to update the node from the tab in these methods, calling self.changed() at the end of each method. The tab will have a node field which addresses the node it is viewing (but see below for a \"gotcha\" - the value of this field will change after an undo operation!)","title":"Writing custom Tabs"},{"location":"devguide/foo/#using-canvas-in-custom-tabs","text":"Creating a Canvas programmatically is straightforward, and there is an example of this in xformexample.py . If you are creating a tab in Designer, you need to add a canvas as a QWidget which you then \"promote\" to a custom control (the canvas). In the promote dialog, the class should be Canvas and the header file pcot.ui.canvas (i.e. the package name). In your onNodeChanged() method you will need to update the canvas. This involves doing a little setup, then getting the image we want to display - usually the output - and telling the canvas to display it: # do some setup self.canvas.setNode(self.node) # then display the image img = self.node.getOutput(0, Datum.IMG) self.canvas.display(img) The setup synchronises the canvas with the node, telling the canvas about the RGB mappings and that it should store data in the node for serialisation. We have to set that up each time because of how undo works, which is discussed in the next section.","title":"Using Canvas in custom tabs"},{"location":"devguide/foo/#undo-and-references-to-data-in-nodes","text":"This is a major \"gotcha.\" Whenever an undo occurs, the old node is discarded and a new node created from a previously archived version in memory. This means that the node field changes. Because of this, your tab must not store references to objects inside the node, because after an undo those references will be stale. Instead, always use self.node... to access data. It is OK to use the tab to store UI-only data which is not persisted (saved to a file or to the undo mechanism).","title":"Undo and references to data in nodes"},{"location":"devguide/latex/","text":"A quick test of LaTeX support Please ignore this page - I use it as a scratchpad for various LaTeX tests. It might seem a bit unpleasant to have this as part of the live documentation, but that absolutely guarantees that it works everywhere! LaTeX in these documents is handled with the pymdownx.arithmatex plugin, which really just hands processing off to MathJax. MathJax then renders the LaTeX using JavaScript. Lots and lots of very clever JavaScript. Here are some tests: Inline equation: y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) should work. Inline equation: $y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right)$ should work. Block equations. This has to use double-backslash: \\[ y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) \\] \\\\[ y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) \\\\] but this one doesn't: \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} Align. Note that the reference doesn't work! \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} Matrix. Note I've had to wrap in an equation. \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation} \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation} \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation}","title":"A quick test of LaTeX support"},{"location":"devguide/latex/#a-quick-test-of-latex-support","text":"Please ignore this page - I use it as a scratchpad for various LaTeX tests. It might seem a bit unpleasant to have this as part of the live documentation, but that absolutely guarantees that it works everywhere! LaTeX in these documents is handled with the pymdownx.arithmatex plugin, which really just hands processing off to MathJax. MathJax then renders the LaTeX using JavaScript. Lots and lots of very clever JavaScript. Here are some tests: Inline equation: y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) should work. Inline equation: $y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right)$ should work. Block equations. This has to use double-backslash: \\[ y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) \\] \\\\[ y = \\sigma\\left(b+\\sum_i w_i x_i (1+h) s_i\\right) \\\\] but this one doesn't: \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} \\begin{equation} f(1,0)=1 \\iff w_1 \\ge -b \\label{eq:f101} \\end{equation} Align. Note that the reference doesn't work! \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} \\begin{align} f(x,y) &= H (b+w_1 x + w_2 y)&\\text{(Eq.~\\ref{eq:f101})}\\\\ 0 &= H(b+w_1+w_2)&\\text{(subst.)}\\\\ H(b+w_1+w_2) &= 0\\label{eq:f110in}\\\\ b+w_1+w_2 & < 0 & \\text{(Heaviside step)}\\\\ w_1+w_2 & < -b. \\end{align} Matrix. Note I've had to wrap in an equation. \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation} \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation} \\begin{equation} X= \\left(\\begin{matrix} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1 \\end{matrix}\\right) \\end{equation}","title":"A quick test of LaTeX support"},{"location":"devguide/library/","text":"Using PCOT as a library As well as being a stand-alone application, PCOT can be used as a library by other Python programs. This page discusses three ways to do this, although the various elements can be easily blended in a single program: Loading a PCOT document, reading some data and passing it through the document's graph; Building a PCOT document programmatically and passing data through it; Using PCOT functions and data types without a graph. While the latter two techniques can be useful for quick ad-hoc work, on the whole we feel it is better to exchange PCOT documents for traceability and clarity. Loading and running PCOT documents A typical example might be a script to read a PCOT document and run some data through that document's graph. You could do that like this: # This example opens a graph, process some ENVI files through that graph, # and saves them back to an ENVI. It assumes the graph has an \"input 0\" node # which receives an image and a \"sink\" node which receives the processed # image. import pcot from pcot.document import Document from pcot.datum import Datum from pcot.dataformats.envi import write # initialise PCOT pcot.setup() # load the document doc = Document(\"1.pcot\") # run the graph for some ENVI files. We'll just do one here, the ENVI # document contained in the files 1.hdr and 1.dat (an ENVI document # consists of two files: header and data). for file in (\"1\",): # load the given ENVI file into input 0 rv = doc.setInputENVI(0, file+\".hdr\") if rv is not None: raise Exception(f\"{rv}\") # run the document's graph doc.run() # get the \"sink\" node outNode = doc.getNodeByName(\"sink\") # get its output img = outNode.out.get(Datum.IMG) # write to new ENVI, e.g. 1b.hdr write(file+\"b\",img) Building a PCOT document It's also possible to build a PCOT document, creating nodes within its graph. Consider the graph Figure: A simple graph. Click on image to expand. This could be built and run for a particular file with the following code: #!/usr/bin/env python import pcot import pcot.document from pcot.datum import Datum pcot.setup() doc = pcot.document.Document() result = doc.setInputENVI(0, \"/home/white/PCOT/fff.hdr\") is not None assert result is not None # create a document with just an input node in it, to bring that input into the document's graph innode = doc.graph.create(\"input 0\") # add a region of interest (ROI) node to the image roinode = doc.graph.create(\"circle\") # set the circle to be centred at (32,32) with a radius of 3 pixels roinode.roi.set(32,32,3) # connect its first input to the input node's first output # args: # input on this node to connect # node to get connection from # index on that node to connect to. roinode.connect(0,innode,0) # connect a node to the ROI node which takes the resulting image-with-ROI # and plots the spectrum of that ROI specnode = doc.graph.create(\"spectrum\") specnode.connect(0,roinode,0) # run the document doc.run() # get the output of the spectrum node, which will be a Datum, # and dereference the Datum, ensuring that the data is of the right type. # The result will be a Table object. output = specnode.getOutput(0, Datum.DATA) # print the table as CSV. print(output) Using PCOT functions and data types without a graph Often it is much simpler to just use the underlying PCOT data types without a graph. The operation described in the previous section is an example of this. We could use the dataformats.load package to load the data directly and manipulate it: #!/usr/bin/env python import pcot from pcot.datum import Datum from pcot.dataformats import load from pcot.rois import ROICircle from pcot.utils.spectrum import SpectrumSet pcot.setup() # load the ENVI file as a Datum object. Will raise an exception # if there is a problem datum = load.envi(\"/home/white/PCOT/fff.hdr\") # retrieve the image, ensuring it's an IMG datum img = datum.get(Datum.IMG) # add a region of interest (ROI) node to the image: # a circle to be centred at (32,32) with a radius of 3 pixels img.rois.append(ROICircle(32,32,3)) # construct a spectrum set from this image - this can create spectra # for multiple sources and combine them. Here we are just using a single # source - the image we are working with - and we're calling it \"in.\" ss = SpectrumSet({\"in\": img}) # Generate a table from the results and print it (as a CSV table). print(ss.table())","title":"Using PCOT as a library"},{"location":"devguide/library/#using-pcot-as-a-library","text":"As well as being a stand-alone application, PCOT can be used as a library by other Python programs. This page discusses three ways to do this, although the various elements can be easily blended in a single program: Loading a PCOT document, reading some data and passing it through the document's graph; Building a PCOT document programmatically and passing data through it; Using PCOT functions and data types without a graph. While the latter two techniques can be useful for quick ad-hoc work, on the whole we feel it is better to exchange PCOT documents for traceability and clarity.","title":"Using PCOT as a library"},{"location":"devguide/library/#loading-and-running-pcot-documents","text":"A typical example might be a script to read a PCOT document and run some data through that document's graph. You could do that like this: # This example opens a graph, process some ENVI files through that graph, # and saves them back to an ENVI. It assumes the graph has an \"input 0\" node # which receives an image and a \"sink\" node which receives the processed # image. import pcot from pcot.document import Document from pcot.datum import Datum from pcot.dataformats.envi import write # initialise PCOT pcot.setup() # load the document doc = Document(\"1.pcot\") # run the graph for some ENVI files. We'll just do one here, the ENVI # document contained in the files 1.hdr and 1.dat (an ENVI document # consists of two files: header and data). for file in (\"1\",): # load the given ENVI file into input 0 rv = doc.setInputENVI(0, file+\".hdr\") if rv is not None: raise Exception(f\"{rv}\") # run the document's graph doc.run() # get the \"sink\" node outNode = doc.getNodeByName(\"sink\") # get its output img = outNode.out.get(Datum.IMG) # write to new ENVI, e.g. 1b.hdr write(file+\"b\",img)","title":"Loading and running PCOT documents"},{"location":"devguide/library/#building-a-pcot-document","text":"It's also possible to build a PCOT document, creating nodes within its graph. Consider the graph Figure: A simple graph. Click on image to expand. This could be built and run for a particular file with the following code: #!/usr/bin/env python import pcot import pcot.document from pcot.datum import Datum pcot.setup() doc = pcot.document.Document() result = doc.setInputENVI(0, \"/home/white/PCOT/fff.hdr\") is not None assert result is not None # create a document with just an input node in it, to bring that input into the document's graph innode = doc.graph.create(\"input 0\") # add a region of interest (ROI) node to the image roinode = doc.graph.create(\"circle\") # set the circle to be centred at (32,32) with a radius of 3 pixels roinode.roi.set(32,32,3) # connect its first input to the input node's first output # args: # input on this node to connect # node to get connection from # index on that node to connect to. roinode.connect(0,innode,0) # connect a node to the ROI node which takes the resulting image-with-ROI # and plots the spectrum of that ROI specnode = doc.graph.create(\"spectrum\") specnode.connect(0,roinode,0) # run the document doc.run() # get the output of the spectrum node, which will be a Datum, # and dereference the Datum, ensuring that the data is of the right type. # The result will be a Table object. output = specnode.getOutput(0, Datum.DATA) # print the table as CSV. print(output)","title":"Building a PCOT document"},{"location":"devguide/library/#using-pcot-functions-and-data-types-without-a-graph","text":"Often it is much simpler to just use the underlying PCOT data types without a graph. The operation described in the previous section is an example of this. We could use the dataformats.load package to load the data directly and manipulate it: #!/usr/bin/env python import pcot from pcot.datum import Datum from pcot.dataformats import load from pcot.rois import ROICircle from pcot.utils.spectrum import SpectrumSet pcot.setup() # load the ENVI file as a Datum object. Will raise an exception # if there is a problem datum = load.envi(\"/home/white/PCOT/fff.hdr\") # retrieve the image, ensuring it's an IMG datum img = datum.get(Datum.IMG) # add a region of interest (ROI) node to the image: # a circle to be centred at (32,32) with a radius of 3 pixels img.rois.append(ROICircle(32,32,3)) # construct a spectrum set from this image - this can create spectra # for multiple sources and combine them. Here we are just using a single # source - the image we are working with - and we're calling it \"in.\" ss = SpectrumSet({\"in\": img}) # Generate a table from the results and print it (as a CSV table). print(ss.table())","title":"Using PCOT functions and data types without a graph"},{"location":"devguide/operators/","text":"Behaviour of binary and unary operators on Value and Datum scalar refers to a Value or NUMBER Datum with a single numeric value vector refers to a Value or NUMBER Datum with a 1D vector Datum within expr node See the Expr node docs.","title":"Behaviour of binary and unary operators on Value and Datum"},{"location":"devguide/operators/#behaviour-of-binary-and-unary-operators-on-value-and-datum","text":"scalar refers to a Value or NUMBER Datum with a single numeric value vector refers to a Value or NUMBER Datum with a 1D vector","title":"Behaviour of binary and unary operators on Value and Datum"},{"location":"devguide/operators/#datum-within-expr-node","text":"See the Expr node docs.","title":"Datum within expr node"},{"location":"devguide/plugins/","text":"Writing PCOT plugins The plugin path PCOT can be extended by adding Python scripts to a directory in the plugin path. By default this is just pcotplugins in the user's home directory, but it can be changed by editing the .pcot.ini file in that directory and modifying the pluginpath value in the Locations section. This should be a semicolon-separated list of directories. You may be tempted to use quotes in the names of you plugin directories: don't. It should work fine, even if your directories have spaces in them. For example, use ~/blah/RIM Dewarping and not ~/blah/\"RIM Dewarping\" Adding new types This is covered in a separate document , as it's not often done and is a little involved. Adding new Datum functions (for use in expr and Python code) The functions used in the expr expression node can also be used in Python programs which use PCOT as a library (this is covered here ). These are called Datum functions because they both take and return Datum objects. To create a Datum function: use the @datumfunc decorator. This will register the function and wrap it in two separate wrappers: one for use in expr , the other for use in Python. write a docstring in the correct format, as illustrated below. Here's an example which declares a function to take two numbers a,b and calculate a+b*2. Note that as in all PCOT code we have to make sure the sources are handled correctly. @datumfunc def example_func(a, b): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param a: number: first number @param b: number: second number \"\"\" return a + b * Datum.k(2) This is a trivial example that relies on Datum objects having operator overloads, but note that we need to multiply b by a Datum, not a number. To do this we use Datum.k to create a scalar constant. The docstring This should consist of a number of lines describing the function followed by a number of @param lines, one for each parameter. These contain the following, separated by colons: The string `@param' A Datum type name - these can be found in the constructors of datum type objects in datumtypes.py , but the most common are number , img , roi , string . A description of the parameter Optional numeric/string arguments Optional arguments with defaults can be provided, but only if they are numeric or strings (because these are the only types which make sense for the default values). In this case the defaults will be converted to Datum objects if they are used. Here is an example of a function which multiplies an image by a constant, with the default being 2: @datumfunc def example_func(img, k=2): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param img:img:the image @param k:number:the multiplier \"\"\" # no need to construct a Datum with Datum.k(), because k is already # a Datum. return img * k Here's another example which adds two numbers or multiplies them, depending on a string - and the default is to add: @datumfunc def stringexample(a, b, op='add'): \"\"\" String argument example @param a: number: first number @param b: number: second number @param op: string: operation to perform \"\"\" if op.get(Datum.STRING) == 'add': return a + b elif op.get(Datum.STRING) == 'sub': return a - b else: raise ValueError(\"Unknown operation\") Note that you usually have to extract the actual value from the Datum objects, as we do with the op argument above. In previous examples, we take advantage of Datum's extensive operator overloading. Variadic arguments For a variable number of arguments, use the *args keyword. Here, you have to check the types by hand. For example, this function will sum numbers: @datumfunc def sumall(*args): \"\"\" Sum all arguments \"\"\" s = sum([x.get(Datum.NUMBER).n for x in args]) return Datum(Datum.NUMBER, Value(s, 0, NOUNCERTAINTY), nullSourceSet) Note the use of Value here to construct a scalar value with standard deviation (zero here) and DQ bits (indicating no uncertainty data). Also note the mandatory use of a source set - just the nullSourceSet here to indicate there is no source; this is just a test function. In a real function we would combine the input sources. For more examples of functions, look at the ExpressionEvaluator constructor in the pcot.expressions.eval module. Adding new menu items This is done by adding a function to a list of functions called when a a main window is opened. Writing code here will require some knowledge of Qt. Here is a menu option added to the File menu which will look for selected node in the document's graph, fetch its first output, and save it as an ENVI file (assuming it is an image - error checking is left as an exercise for the reader). import pcot import os from PySide2 import QtWidgets from PySide2.QtWidgets import QAction, QMessageBox from pcot.dataformats import envi def saveEnvi(w): \"\"\"Function takes a PCOT main window. It finds the first selected node, gets its output 0, and then saves an ENVI from that image.\"\"\" sel = w.doc.getSelection() if len(sel) == 0: ui.log(\"no selected node\") return node = sel[0] directory = os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')) res = QtWidgets.QFileDialog.getSaveFileName(w, \"ENVI file \", os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')), \"ENVI files (*.hdr)\") if res[0] != '': # get the output of that node (root, ext) = os.path.splitext(res[0]) img = node.getOutput(0, pcot.datum.Datum.IMG) envi.write(root, img) # the function to add the new menu item. This will take a single parameter: # the window to which the menu should be added. def addMenus(w): \"\"\"Add an item to the Edit menu\"\"\" # create the menu action (i.e. the item) act = QAction(\"save to ENVI\",parent=w) # find (or create) the Edit menu and add the action to it w.findOrAddMenu(\"Edit\").addAction(act) # link the action to the saveEnvi function, using a closure to # make sure the window argument is passed into that function. act.triggered.connect(lambda: saveEnvi(w)) # Add the addMenus functions to the list of functions called as the # main window opens. pcot.config.addMainWindowHook(addMenus) Adding new node types Node types are represented by singletons of subclasses of XFormType (Nodes themselves are of type XForm , which stands for transform for historical reasons). Developing new XFormType subclasses is largely beyond the scope of this document, but you can learn a lot from looking at the pcot.xforms package and the modules therein. To create a new node type, declare a new subclass of XFormType and decorate it with the @xformtype decorator. This will make the type autoregister: the singleton will be constructed and added to the internal type registry. You will need to write the following methods in your subclass: __init__(self) to construct the type object (NOT the individual nodes). This will call the superconstructor to set the type's name, group (for the palette), and version. It will add the input and output connectors. init(self, node) will initialise any private data in the node itself (which is an XForm ). Don't confuse this with __init__ ! createTab(self, node, window) will create a new node area (i.e. tab). Often, this can be a TabData which will look at the node's out attribute, which should be a Datum. perform(self, node) will actually perform the node's action, reading inputs and setting outputs. Remember: there is only one XFormType object for each node type. All nodes are of type XForm , and they link to an XFormType object to tell them how to behave. This might seem a really odd way to do things, but it follows \"favour composition over inheritance\" and saves messiness elsewhere. Here is an example which does edge detection with OpenCV: import cv2 as cv import numpy as np from pcot.sources import SourceSet from pcot.xform import XFormType, xformtype, Datum from pcot.xforms.tabdata import TabData from pcot.imagecube import ImageCube import pcot.config # The first part of the plugin creates a new type of node. # this decorator will cause the node to auto-register. @xformtype class XFormEdgeDetect(XFormType): \"\"\"This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab.\"\"\" def __init__(self): # this node should appear in the maths group. super().__init__(\"edge\", \"maths\", \"0.0.0\") # set input and output - they are images and are unnamed. self.addInputConnector(\"\", Datum.IMG) self.addOutputConnector(\"\", Datum.IMG) def createTab(self, n, w): # there is no custom tab, we just use a data canvas. This expects \"node.out\" to be set to # either None or a Datum. return TabData(n, w) def init(self, n): # No initialisation required. pass def perform(self, node): # get the input image img = node.getInput(0, Datum.IMG) if img is not None: # find mean of all channels - construct a transform array and then use it. mat = np.array([1 / img.channels] * img.channels).reshape((1, img.channels)) grey = cv.transform(img.img, mat) # convert to 8-bit integer from 32-bit float img8 = (grey * 255).astype('uint8') # Perform edge detection out = cv.Canny(img8, 100, 200) # Convert back to 32-bit float out = (out / 255).astype('float32') # create the imagecube and set node.out for the canvas in the tab img = ImageCube(out, None, img.sources) node.out = Datum(Datum.IMG, img) else: # no image on the input, set node.out to None node.out = Datum.null # output node.out node.setOutput(0, node.out) Writing custom Tabs As noted above, a new XFormType subclass (i.e. a new node type) can often just use TabData , which will display the Datum stored on its first output (output 0). Sometimes, however, a custom tab needs to be written. This can be a complex task, but an example is given in xformexample.py in the xforms package. All the standard XFormTypes are in this package, so you can also look at them. The basic idea is: Create a subclass of pcot.ui.tabs.Tab Write the constructor to call the superclass constructor and create the UI (or load a Designer-created UI by passing an argument to the superclass. constructor), and call self.nodeChanged() at the end to refresh the tab from the node. Override onNodeChanged() to update the tab from the node. Use the Qt signal/slot mechanism to connect the tab's controls to methods in the tab class and write code to update the node from the tab in these methods, calling self.changed() at the end of each method. The tab will have a node field which addresses the node it is viewing (but see below for a \"gotcha\" - the value of this field will change after an undo operation!) Using Canvas in custom tabs Creating a Canvas programmatically is straightforward, and there is an example of this in xformexample.py . If you are creating a tab in Designer, you need to add a canvas as a QWidget which you then \"promote\" to a custom control (the canvas). In the promote dialog, the class should be Canvas and the header file pcot.ui.canvas (i.e. the package name). In your onNodeChanged() method you will need to update the canvas. This involves doing a little setup, then getting the image we want to display - usually the output - and telling the canvas to display it: # do some setup self.canvas.setNode(self.node) # then display the image img = self.node.getOutput(0, Datum.IMG) self.canvas.display(img) The setup synchronises the canvas with the node, telling the canvas about the RGB mappings and that it should store data in the node for serialisation. We have to set that up each time because of how undo works, which is discussed in the next section. Undo and references to data in nodes This is a major \"gotcha.\" Whenever an undo occurs, the old node is discarded and a new node created from a previously archived version in memory. This means that the node field changes. Because of this, your tab must not store references to objects inside the node, because after an undo those references will be stale. Instead, always use self.node... to access data. It is OK to use the tab to store UI-only data which is not persisted (saved to a file or to the undo mechanism).","title":"Writing PCOT plugins"},{"location":"devguide/plugins/#writing-pcot-plugins","text":"","title":"Writing PCOT plugins"},{"location":"devguide/plugins/#the-plugin-path","text":"PCOT can be extended by adding Python scripts to a directory in the plugin path. By default this is just pcotplugins in the user's home directory, but it can be changed by editing the .pcot.ini file in that directory and modifying the pluginpath value in the Locations section. This should be a semicolon-separated list of directories. You may be tempted to use quotes in the names of you plugin directories: don't. It should work fine, even if your directories have spaces in them. For example, use ~/blah/RIM Dewarping and not ~/blah/\"RIM Dewarping\"","title":"The plugin path"},{"location":"devguide/plugins/#adding-new-types","text":"This is covered in a separate document , as it's not often done and is a little involved.","title":"Adding new types"},{"location":"devguide/plugins/#adding-new-datum-functions-for-use-in-expr-and-python-code","text":"The functions used in the expr expression node can also be used in Python programs which use PCOT as a library (this is covered here ). These are called Datum functions because they both take and return Datum objects. To create a Datum function: use the @datumfunc decorator. This will register the function and wrap it in two separate wrappers: one for use in expr , the other for use in Python. write a docstring in the correct format, as illustrated below. Here's an example which declares a function to take two numbers a,b and calculate a+b*2. Note that as in all PCOT code we have to make sure the sources are handled correctly. @datumfunc def example_func(a, b): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param a: number: first number @param b: number: second number \"\"\" return a + b * Datum.k(2) This is a trivial example that relies on Datum objects having operator overloads, but note that we need to multiply b by a Datum, not a number. To do this we use Datum.k to create a scalar constant.","title":"Adding new Datum functions (for use in expr and Python code)"},{"location":"devguide/plugins/#the-docstring","text":"This should consist of a number of lines describing the function followed by a number of @param lines, one for each parameter. These contain the following, separated by colons: The string `@param' A Datum type name - these can be found in the constructors of datum type objects in datumtypes.py , but the most common are number , img , roi , string . A description of the parameter","title":"The docstring"},{"location":"devguide/plugins/#optional-numericstring-arguments","text":"Optional arguments with defaults can be provided, but only if they are numeric or strings (because these are the only types which make sense for the default values). In this case the defaults will be converted to Datum objects if they are used. Here is an example of a function which multiplies an image by a constant, with the default being 2: @datumfunc def example_func(img, k=2): \"\"\" Example function that takes two numbers a,b and returns a+b*2 @param img:img:the image @param k:number:the multiplier \"\"\" # no need to construct a Datum with Datum.k(), because k is already # a Datum. return img * k Here's another example which adds two numbers or multiplies them, depending on a string - and the default is to add: @datumfunc def stringexample(a, b, op='add'): \"\"\" String argument example @param a: number: first number @param b: number: second number @param op: string: operation to perform \"\"\" if op.get(Datum.STRING) == 'add': return a + b elif op.get(Datum.STRING) == 'sub': return a - b else: raise ValueError(\"Unknown operation\") Note that you usually have to extract the actual value from the Datum objects, as we do with the op argument above. In previous examples, we take advantage of Datum's extensive operator overloading.","title":"Optional numeric/string arguments"},{"location":"devguide/plugins/#variadic-arguments","text":"For a variable number of arguments, use the *args keyword. Here, you have to check the types by hand. For example, this function will sum numbers: @datumfunc def sumall(*args): \"\"\" Sum all arguments \"\"\" s = sum([x.get(Datum.NUMBER).n for x in args]) return Datum(Datum.NUMBER, Value(s, 0, NOUNCERTAINTY), nullSourceSet) Note the use of Value here to construct a scalar value with standard deviation (zero here) and DQ bits (indicating no uncertainty data). Also note the mandatory use of a source set - just the nullSourceSet here to indicate there is no source; this is just a test function. In a real function we would combine the input sources. For more examples of functions, look at the ExpressionEvaluator constructor in the pcot.expressions.eval module.","title":"Variadic arguments"},{"location":"devguide/plugins/#adding-new-menu-items","text":"This is done by adding a function to a list of functions called when a a main window is opened. Writing code here will require some knowledge of Qt. Here is a menu option added to the File menu which will look for selected node in the document's graph, fetch its first output, and save it as an ENVI file (assuming it is an image - error checking is left as an exercise for the reader). import pcot import os from PySide2 import QtWidgets from PySide2.QtWidgets import QAction, QMessageBox from pcot.dataformats import envi def saveEnvi(w): \"\"\"Function takes a PCOT main window. It finds the first selected node, gets its output 0, and then saves an ENVI from that image.\"\"\" sel = w.doc.getSelection() if len(sel) == 0: ui.log(\"no selected node\") return node = sel[0] directory = os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')) res = QtWidgets.QFileDialog.getSaveFileName(w, \"ENVI file \", os.path.expanduser(pcot.config.getDefaultDir('pcotfiles')), \"ENVI files (*.hdr)\") if res[0] != '': # get the output of that node (root, ext) = os.path.splitext(res[0]) img = node.getOutput(0, pcot.datum.Datum.IMG) envi.write(root, img) # the function to add the new menu item. This will take a single parameter: # the window to which the menu should be added. def addMenus(w): \"\"\"Add an item to the Edit menu\"\"\" # create the menu action (i.e. the item) act = QAction(\"save to ENVI\",parent=w) # find (or create) the Edit menu and add the action to it w.findOrAddMenu(\"Edit\").addAction(act) # link the action to the saveEnvi function, using a closure to # make sure the window argument is passed into that function. act.triggered.connect(lambda: saveEnvi(w)) # Add the addMenus functions to the list of functions called as the # main window opens. pcot.config.addMainWindowHook(addMenus)","title":"Adding new menu items"},{"location":"devguide/plugins/#adding-new-node-types","text":"Node types are represented by singletons of subclasses of XFormType (Nodes themselves are of type XForm , which stands for transform for historical reasons). Developing new XFormType subclasses is largely beyond the scope of this document, but you can learn a lot from looking at the pcot.xforms package and the modules therein. To create a new node type, declare a new subclass of XFormType and decorate it with the @xformtype decorator. This will make the type autoregister: the singleton will be constructed and added to the internal type registry. You will need to write the following methods in your subclass: __init__(self) to construct the type object (NOT the individual nodes). This will call the superconstructor to set the type's name, group (for the palette), and version. It will add the input and output connectors. init(self, node) will initialise any private data in the node itself (which is an XForm ). Don't confuse this with __init__ ! createTab(self, node, window) will create a new node area (i.e. tab). Often, this can be a TabData which will look at the node's out attribute, which should be a Datum. perform(self, node) will actually perform the node's action, reading inputs and setting outputs. Remember: there is only one XFormType object for each node type. All nodes are of type XForm , and they link to an XFormType object to tell them how to behave. This might seem a really odd way to do things, but it follows \"favour composition over inheritance\" and saves messiness elsewhere. Here is an example which does edge detection with OpenCV: import cv2 as cv import numpy as np from pcot.sources import SourceSet from pcot.xform import XFormType, xformtype, Datum from pcot.xforms.tabdata import TabData from pcot.imagecube import ImageCube import pcot.config # The first part of the plugin creates a new type of node. # this decorator will cause the node to auto-register. @xformtype class XFormEdgeDetect(XFormType): \"\"\"This is an edge detector node. It takes an image and performs Canny edge detection, currently with fixed thresholds. It does not take account of ROIs, since this would be pointless when we're converting from a potentially multispectral image to greyscale (well, boolean). Exercise for the reader - add variable thresholds, either as numeric inputs or as numeric parameters settable from the node tab.\"\"\" def __init__(self): # this node should appear in the maths group. super().__init__(\"edge\", \"maths\", \"0.0.0\") # set input and output - they are images and are unnamed. self.addInputConnector(\"\", Datum.IMG) self.addOutputConnector(\"\", Datum.IMG) def createTab(self, n, w): # there is no custom tab, we just use a data canvas. This expects \"node.out\" to be set to # either None or a Datum. return TabData(n, w) def init(self, n): # No initialisation required. pass def perform(self, node): # get the input image img = node.getInput(0, Datum.IMG) if img is not None: # find mean of all channels - construct a transform array and then use it. mat = np.array([1 / img.channels] * img.channels).reshape((1, img.channels)) grey = cv.transform(img.img, mat) # convert to 8-bit integer from 32-bit float img8 = (grey * 255).astype('uint8') # Perform edge detection out = cv.Canny(img8, 100, 200) # Convert back to 32-bit float out = (out / 255).astype('float32') # create the imagecube and set node.out for the canvas in the tab img = ImageCube(out, None, img.sources) node.out = Datum(Datum.IMG, img) else: # no image on the input, set node.out to None node.out = Datum.null # output node.out node.setOutput(0, node.out)","title":"Adding new node types"},{"location":"devguide/plugins/#writing-custom-tabs","text":"As noted above, a new XFormType subclass (i.e. a new node type) can often just use TabData , which will display the Datum stored on its first output (output 0). Sometimes, however, a custom tab needs to be written. This can be a complex task, but an example is given in xformexample.py in the xforms package. All the standard XFormTypes are in this package, so you can also look at them. The basic idea is: Create a subclass of pcot.ui.tabs.Tab Write the constructor to call the superclass constructor and create the UI (or load a Designer-created UI by passing an argument to the superclass. constructor), and call self.nodeChanged() at the end to refresh the tab from the node. Override onNodeChanged() to update the tab from the node. Use the Qt signal/slot mechanism to connect the tab's controls to methods in the tab class and write code to update the node from the tab in these methods, calling self.changed() at the end of each method. The tab will have a node field which addresses the node it is viewing (but see below for a \"gotcha\" - the value of this field will change after an undo operation!)","title":"Writing custom Tabs"},{"location":"devguide/plugins/#using-canvas-in-custom-tabs","text":"Creating a Canvas programmatically is straightforward, and there is an example of this in xformexample.py . If you are creating a tab in Designer, you need to add a canvas as a QWidget which you then \"promote\" to a custom control (the canvas). In the promote dialog, the class should be Canvas and the header file pcot.ui.canvas (i.e. the package name). In your onNodeChanged() method you will need to update the canvas. This involves doing a little setup, then getting the image we want to display - usually the output - and telling the canvas to display it: # do some setup self.canvas.setNode(self.node) # then display the image img = self.node.getOutput(0, Datum.IMG) self.canvas.display(img) The setup synchronises the canvas with the node, telling the canvas about the RGB mappings and that it should store data in the node for serialisation. We have to set that up each time because of how undo works, which is discussed in the next section.","title":"Using Canvas in custom tabs"},{"location":"devguide/plugins/#undo-and-references-to-data-in-nodes","text":"This is a major \"gotcha.\" Whenever an undo occurs, the old node is discarded and a new node created from a previously archived version in memory. This means that the node field changes. Because of this, your tab must not store references to objects inside the node, because after an undo those references will be stale. Instead, always use self.node... to access data. It is OK to use the tab to store UI-only data which is not persisted (saved to a file or to the undo mechanism).","title":"Undo and references to data in nodes"},{"location":"devguide/types/","text":"Notes on types Built-in types of data (i.e. of Datum objects) are kept in datum.py . A type specifies: its name whether it is an image subtype (it's tricky to write a method for this, since ImgType is defined after Type) whether it is an \"internal type\" used in the expression evaluator and not for connections (e.g. IdentType, FuncType and NoneType) optional serialisation and deserialisation methods taking and returning Datum, which convert to and from JSON-serialisable values - i.e. primitive types, tuples, lists and dicts; no objects. The Datum class has a type list, which contains singletons of all the type objects. To register a type: create a type object append to the Datum types list if required, register a connector brush with connbrushes.register. Deal with binary and unary operators in expr expressions if required (see below). An example This code is taken from the example1.py plugin file. This (in part) declares a function which takes two numbers, and produces an object containing the dividend and remainder of those numbers. These are held in a custom object: # Our data will be an object of class TestObject. class TestObject: def __init__(self, div, rem): self.div = div self.rem = rem def __str__(self): return f\"({self.div}, {self.rem})\" Now we need to provide the type singleton: # now the type singleton, which controls how Datum objects which hold TestObjects # serialise and deserialise themselves (turn themselves into JSON-serialisable # data and back). # I'm naming the class with an underscore - the type object will be without this. class _TestObjectType(Type): def __init__(self): # just call the superconstructor telling it the name of the type # and in this case, that the stringification (the result of __str__ on the # value) is short enough to fit into an expr node's graph box. super().__init__('testtuple', outputStringShort=True) # now we have to write code which converts Datums of this type into # stuff which can be converted to JSON and back again. Converting # into JSON-serialisable is termed \"serialisation\" and reconstructing # the original Datum object and all its data is \"deserialisation\". def serialise(self, d): # how to serialise data of this type: serialise() methods must return # a tuple of typename and contents. # The contents must be JSON-serialisable, and must contain both the # data to be saved and the serialised source information. # First convert TestObject to something we can serialise serialisedObject = d.val.div, d.val.rem # and create the serialised datum of the name and contents return self.name, (serialisedObject, d.getSources().serialise()) def deserialise(self, d, document): # given a serialised tuple generated by serialise(), produce a Datum # of this type. serialisedObject, serialisedSources = d # first generate the contents # deserialise the serialised sources data sources = SourceSet.deserialise(serialisedSources, document) # then pass to the datum constructor along with the type singleton. return Datum(self, serialisedObject, sources) We register the type singleton, but keep a reference to the object so we can use it in our own code when we create Datum objects. We also provide a connector brush, so that connections of this type are rendered differently in the graph: # create the singleton and register it, but keep hold of the variable so # we can use it to create new Datum objects. TestObjectType = _TestObjectType() Datum.registerType(TestObjectType) # add a brush for the connections in the graph pcot.connbrushes.register(TestObjectType, QColor(\"darkMagenta\")) See example1.py for how this new type is used. Operators Work in progress - read the Value documentation to see how this works in detail, particularly with numeric and image data (i.e. data which can be expressed as (mean,sd,DQ) triples). Previously operators were entirely hardwired in utils.ops. This stopped us creating new types. We could define something like binop(self,other) in the type classes, but this wouldn't allow us to add new types as the RHS for operations which have built-in types as the LHS. The Simplest Thing That Can Possibly Work is a dictionary of operation functions keyed (in the case of binops) by a tuple of types. So this is how operators work now, relying on two registration processes. The first is the registration of the operator lexeme (e.g. \"*\" or \"+\") and precedence, and an associated function to call. This happens as part of Parser. The function calls a binop() function in the ops module, passing in the operator ID. The second is the registration of operator ID (e.g. Operator.ADD) and types in the ops module, with an associated function to call. This is often a wrapper function around a lambda: the wrapper knows to unpack (say) image and number data, and the lambda says they should be processed with addition. Adding a new type with operator semantics Create a subclass of datum.Type add serialisation methods if required call Datum.registerType() with the type If required, add a new connector brush with connbrushes.register() To use the type, use the Type object with the Datum constructor and Datum.get() method. Adding operator semantics call ops.registerBinop and ops.registerUnop to register functions to perform the required operations. The function should take Datum objects and return a Datum.","title":"Notes on types"},{"location":"devguide/types/#notes-on-types","text":"Built-in types of data (i.e. of Datum objects) are kept in datum.py . A type specifies: its name whether it is an image subtype (it's tricky to write a method for this, since ImgType is defined after Type) whether it is an \"internal type\" used in the expression evaluator and not for connections (e.g. IdentType, FuncType and NoneType) optional serialisation and deserialisation methods taking and returning Datum, which convert to and from JSON-serialisable values - i.e. primitive types, tuples, lists and dicts; no objects. The Datum class has a type list, which contains singletons of all the type objects. To register a type: create a type object append to the Datum types list if required, register a connector brush with connbrushes.register. Deal with binary and unary operators in expr expressions if required (see below).","title":"Notes on types"},{"location":"devguide/types/#an-example","text":"This code is taken from the example1.py plugin file. This (in part) declares a function which takes two numbers, and produces an object containing the dividend and remainder of those numbers. These are held in a custom object: # Our data will be an object of class TestObject. class TestObject: def __init__(self, div, rem): self.div = div self.rem = rem def __str__(self): return f\"({self.div}, {self.rem})\" Now we need to provide the type singleton: # now the type singleton, which controls how Datum objects which hold TestObjects # serialise and deserialise themselves (turn themselves into JSON-serialisable # data and back). # I'm naming the class with an underscore - the type object will be without this. class _TestObjectType(Type): def __init__(self): # just call the superconstructor telling it the name of the type # and in this case, that the stringification (the result of __str__ on the # value) is short enough to fit into an expr node's graph box. super().__init__('testtuple', outputStringShort=True) # now we have to write code which converts Datums of this type into # stuff which can be converted to JSON and back again. Converting # into JSON-serialisable is termed \"serialisation\" and reconstructing # the original Datum object and all its data is \"deserialisation\". def serialise(self, d): # how to serialise data of this type: serialise() methods must return # a tuple of typename and contents. # The contents must be JSON-serialisable, and must contain both the # data to be saved and the serialised source information. # First convert TestObject to something we can serialise serialisedObject = d.val.div, d.val.rem # and create the serialised datum of the name and contents return self.name, (serialisedObject, d.getSources().serialise()) def deserialise(self, d, document): # given a serialised tuple generated by serialise(), produce a Datum # of this type. serialisedObject, serialisedSources = d # first generate the contents # deserialise the serialised sources data sources = SourceSet.deserialise(serialisedSources, document) # then pass to the datum constructor along with the type singleton. return Datum(self, serialisedObject, sources) We register the type singleton, but keep a reference to the object so we can use it in our own code when we create Datum objects. We also provide a connector brush, so that connections of this type are rendered differently in the graph: # create the singleton and register it, but keep hold of the variable so # we can use it to create new Datum objects. TestObjectType = _TestObjectType() Datum.registerType(TestObjectType) # add a brush for the connections in the graph pcot.connbrushes.register(TestObjectType, QColor(\"darkMagenta\")) See example1.py for how this new type is used.","title":"An example"},{"location":"devguide/types/#operators","text":"Work in progress - read the Value documentation to see how this works in detail, particularly with numeric and image data (i.e. data which can be expressed as (mean,sd,DQ) triples). Previously operators were entirely hardwired in utils.ops. This stopped us creating new types. We could define something like binop(self,other) in the type classes, but this wouldn't allow us to add new types as the RHS for operations which have built-in types as the LHS. The Simplest Thing That Can Possibly Work is a dictionary of operation functions keyed (in the case of binops) by a tuple of types. So this is how operators work now, relying on two registration processes. The first is the registration of the operator lexeme (e.g. \"*\" or \"+\") and precedence, and an associated function to call. This happens as part of Parser. The function calls a binop() function in the ops module, passing in the operator ID. The second is the registration of operator ID (e.g. Operator.ADD) and types in the ops module, with an associated function to call. This is often a wrapper function around a lambda: the wrapper knows to unpack (say) image and number data, and the lambda says they should be processed with addition.","title":"Operators"},{"location":"devguide/types/#adding-a-new-type-with-operator-semantics","text":"Create a subclass of datum.Type add serialisation methods if required call Datum.registerType() with the type If required, add a new connector brush with connbrushes.register() To use the type, use the Type object with the Datum constructor and Datum.get() method. Adding operator semantics call ops.registerBinop and ops.registerUnop to register functions to perform the required operations. The function should take Datum objects and return a Datum.","title":"Adding a new type with operator semantics"},{"location":"devguide/values/","text":"How Values work Value is PCOT's fundamental type for working with uncertain numerical data. Values are triplets of: a 32-bit floating point nominal value (i.e. a mean), a 32-bit floating point uncertainty value (as standard deviation) around that mean, and a set of 16 data quality (DQ) bits. These are usually scalar, but 1D vectors can also be created. As a user of PCOT you may never encounter Values, but they are used internally whenever any operations involving uncertainty are done. Here are the typical situations where that occurs: Binary and unary operations on Value objects This is the \"base case\". Values have dunder methods for operations. These are usually quite simple, although some (such as exponentiation) are nasty. They: perform the operation to get the new nominal value. This is usually done with a lambda function, relying on Numpy's broadcasting to do the \"right thing\" with what it's given. call a function to calculate the new uncertainty value call a function to combine the two DQs pass the three results into Value's constructor to get a new value Examples - the multiplication operator: def __mul__(self, other): return Value(self.n * other.n, mul_unc(self.n, self.u, other.n, other.u), combineDQs(self, other)) The AND operator: def __and__(self, other): \"\"\"The & operator actually finds the minimum (Zadeh fuzzy op)\"\"\" n = np.where(self.n > other.n, other.n, self.n) u = np.where(self.n > other.n, other.u, self.u) d = np.where(self.n > other.n, other.dq, self.dq) return Value(n, u, d) Binary and unary operations on Datum objects This is for binops - unary operations are much the same, but a lot simpler (although imageUnop currently takes the underlying numpy array). Datum dunder function runs - this is usually very simple. For example, for addition it's just return ops.binop(ops.Operator.ADD, self, other) . Calls pcot.expressions.ops.binop with the appopriate ops.Operator code ops.binop runs converting any raw numbers to Datum.NUMBER data with zero uncertainty. Each possible triple (operator, leftdatum, rightdatum) of operator and Datum types will have a \"binop semantics Datum wrapper\" method registered for it by ops.initOps. For example, (multiplication, Datum.IMG, Datum.NUMBER) will call ops.imageNumberBinop with the lambda lambda x,y: x*y where the latter will take and return Value. ops.binop calls the binop semantics method for the two Datum types For pairings of numbers and/or images, The binop semantics Datum wrapper converts any image data into a Value after performing subimage extraction, then calls the provided lambda. This calls the appropriate dunder method on the two Values. In fact, we could replace lambda x,y: x*y with Value.__mul__ etc., but it's much clearer this way. see above for details of this. In imageNumberBinop (or equivalent) the returned array Value will be stitched back into the left-hand image using ImageCube.modifyWithSub , and that new image will be returned wrapped in a Datum. If the semantic wrapper was numberBinop, so that a number is returned, we just wrap that in a Datum. For other types - not Datum.NUMBER or Datum.IMG - other semantics methods will have been written. For example, the ROI types have binary operators which construct ROIBinop objects using dunder methods: def regROIBinopSemantics(op, fn): \"\"\"Used to register binops for ROIs, which support a subset of ops.\"\"\" registerBinopSemantics(op, Datum.ROI, Datum.ROI, lambda dx, dy: ROIBinop(dx, dy, fn)) regROIBinopSemantics(Operator.ADD, lambda x, y: x + y) regROIBinopSemantics(Operator.SUB, lambda x, y: x - y) regROIBinopSemantics(Operator.MUL, lambda x, y: x * y) regROIBinopSemantics(Operator.DIV, lambda x, y: x / y) regROIBinopSemantics(Operator.POW, lambda x, y: x ** y) Binary and unary operations in an expr node pcot.expressions.parse.InstOp runs, popping two Datum objects and calling a callback function stored in binopRegistry. This callback will have been registered by Parser.registerBinop, and is simply a lambda calling pcot.expressions.ops.binop with an Operator code for the operator, e.g. p.registerBinop('*', 20, lambda a, b: binop(Operator.MUL, a, b)) (20 is the precedence) The callback will call ops.binop, which is step 3 in the preceding section. Again, unary operations are very similar but rather simpler. functions of Value objects Value generally has a method for each supported function. For example, the tan method will perform the trigonometric tan with uncertainty, returning a new Value. This is rarely called directly - instead, we generally work with Datum objects. datumfuncs: functions of Datum objects These are registered with the @datumfunc operator (see the plugins documentation ) which can be found in pcot.expressions.register. This registers two separate wrappers: the expression wrapper, used when the function is called from an expr node; and the Python function wrapper, called when the function is called from inside Python. They can take any number of arguments, but they must be either Datum objects or numeric. Datumfuncs called from Python The @datumfunc decorator will have wrapped the function in the Python decorator, so that subsequent calls from Python will go through the wrapper. This wrapper will set default values for missing arguments (which must be string or numeric) convert numeric and string arguments to Datum objects call the original function and return the result (which must be Datum) Datumfuncs called from expr nodes The @datumfunc decorator also wraps the function in another wrapper - the exprwrapper - and registers the wrapped function with the expression parser. The wrapper itself is simpler than the Python wrapper because it can make use of facilities in the parser to convert values and check for errors. When data reaches the exprwrapper, we already know that the arguments are all Datum objects. Datumfuncs of single numeric/scalar arguments Functions of a single numeric or image Datum are sometimes written using an \"inner wrapper\", which will turn imagecubes and numbers into Value objects in a similar way to how the semantic binop wrappers work for operations. It will also wrap the resulting Value in a Datum. An example is pcot.datumfuncs.func_wrapper . Here is the datumfunc sin in full: @datumfunc def sin(a): \"\"\" Calculate sine of an angle in radians @param a:img,number:the angle (or image in which each pixel is a single angle) \"\"\" return func_wrapper(lambda xx: xx.sin(), a) We don't need worry about whether the argument is an image or a scalar, because the func_wrapper will deal with it. However, func_wrapper can only deal with images and scalars. The stats wrapper Another datumfunc inner wrapper which can be used is stats_wrapper. This wraps a function which takes a (nominal,uncertainty,dq) tuple and returns another tuple of the same type. It does the following: If provided with a numeric value, calls the wrapped function and creates a Value from the returned data. If provided with an ImageCube, gets the subimage, splits it into bands, and calls the wrapped function on the non-BAD values in each band. The results for each band - assumed to be scalar - are converted into a vector Value with one value for each band. This lets us write the mean like this: @datumfunc def mean(val): return stats_wrapper(val, lambda n, u, d: (np.mean(n), pooled_sd(n, u), pcot.dq.NONE)) and it will work on scalar Values, vector Values and images - in the latter case, producing a vector Value.","title":"How Values work"},{"location":"devguide/values/#how-values-work","text":"Value is PCOT's fundamental type for working with uncertain numerical data. Values are triplets of: a 32-bit floating point nominal value (i.e. a mean), a 32-bit floating point uncertainty value (as standard deviation) around that mean, and a set of 16 data quality (DQ) bits. These are usually scalar, but 1D vectors can also be created. As a user of PCOT you may never encounter Values, but they are used internally whenever any operations involving uncertainty are done. Here are the typical situations where that occurs:","title":"How Values work"},{"location":"devguide/values/#binary-and-unary-operations-on-value-objects","text":"This is the \"base case\". Values have dunder methods for operations. These are usually quite simple, although some (such as exponentiation) are nasty. They: perform the operation to get the new nominal value. This is usually done with a lambda function, relying on Numpy's broadcasting to do the \"right thing\" with what it's given. call a function to calculate the new uncertainty value call a function to combine the two DQs pass the three results into Value's constructor to get a new value Examples - the multiplication operator: def __mul__(self, other): return Value(self.n * other.n, mul_unc(self.n, self.u, other.n, other.u), combineDQs(self, other)) The AND operator: def __and__(self, other): \"\"\"The & operator actually finds the minimum (Zadeh fuzzy op)\"\"\" n = np.where(self.n > other.n, other.n, self.n) u = np.where(self.n > other.n, other.u, self.u) d = np.where(self.n > other.n, other.dq, self.dq) return Value(n, u, d)","title":"Binary and unary operations on Value objects"},{"location":"devguide/values/#binary-and-unary-operations-on-datum-objects","text":"This is for binops - unary operations are much the same, but a lot simpler (although imageUnop currently takes the underlying numpy array). Datum dunder function runs - this is usually very simple. For example, for addition it's just return ops.binop(ops.Operator.ADD, self, other) . Calls pcot.expressions.ops.binop with the appopriate ops.Operator code ops.binop runs converting any raw numbers to Datum.NUMBER data with zero uncertainty. Each possible triple (operator, leftdatum, rightdatum) of operator and Datum types will have a \"binop semantics Datum wrapper\" method registered for it by ops.initOps. For example, (multiplication, Datum.IMG, Datum.NUMBER) will call ops.imageNumberBinop with the lambda lambda x,y: x*y where the latter will take and return Value. ops.binop calls the binop semantics method for the two Datum types For pairings of numbers and/or images, The binop semantics Datum wrapper converts any image data into a Value after performing subimage extraction, then calls the provided lambda. This calls the appropriate dunder method on the two Values. In fact, we could replace lambda x,y: x*y with Value.__mul__ etc., but it's much clearer this way. see above for details of this. In imageNumberBinop (or equivalent) the returned array Value will be stitched back into the left-hand image using ImageCube.modifyWithSub , and that new image will be returned wrapped in a Datum. If the semantic wrapper was numberBinop, so that a number is returned, we just wrap that in a Datum. For other types - not Datum.NUMBER or Datum.IMG - other semantics methods will have been written. For example, the ROI types have binary operators which construct ROIBinop objects using dunder methods: def regROIBinopSemantics(op, fn): \"\"\"Used to register binops for ROIs, which support a subset of ops.\"\"\" registerBinopSemantics(op, Datum.ROI, Datum.ROI, lambda dx, dy: ROIBinop(dx, dy, fn)) regROIBinopSemantics(Operator.ADD, lambda x, y: x + y) regROIBinopSemantics(Operator.SUB, lambda x, y: x - y) regROIBinopSemantics(Operator.MUL, lambda x, y: x * y) regROIBinopSemantics(Operator.DIV, lambda x, y: x / y) regROIBinopSemantics(Operator.POW, lambda x, y: x ** y)","title":"Binary and unary operations on Datum objects"},{"location":"devguide/values/#binary-and-unary-operations-in-an-expr-node","text":"pcot.expressions.parse.InstOp runs, popping two Datum objects and calling a callback function stored in binopRegistry. This callback will have been registered by Parser.registerBinop, and is simply a lambda calling pcot.expressions.ops.binop with an Operator code for the operator, e.g. p.registerBinop('*', 20, lambda a, b: binop(Operator.MUL, a, b)) (20 is the precedence) The callback will call ops.binop, which is step 3 in the preceding section. Again, unary operations are very similar but rather simpler.","title":"Binary and unary operations in an expr node"},{"location":"devguide/values/#functions-of-value-objects","text":"Value generally has a method for each supported function. For example, the tan method will perform the trigonometric tan with uncertainty, returning a new Value. This is rarely called directly - instead, we generally work with Datum objects.","title":"functions of Value objects"},{"location":"devguide/values/#datumfuncs-functions-of-datum-objects","text":"These are registered with the @datumfunc operator (see the plugins documentation ) which can be found in pcot.expressions.register. This registers two separate wrappers: the expression wrapper, used when the function is called from an expr node; and the Python function wrapper, called when the function is called from inside Python. They can take any number of arguments, but they must be either Datum objects or numeric.","title":"datumfuncs: functions of Datum objects"},{"location":"devguide/values/#datumfuncs-called-from-python","text":"The @datumfunc decorator will have wrapped the function in the Python decorator, so that subsequent calls from Python will go through the wrapper. This wrapper will set default values for missing arguments (which must be string or numeric) convert numeric and string arguments to Datum objects call the original function and return the result (which must be Datum)","title":"Datumfuncs called from Python"},{"location":"devguide/values/#datumfuncs-called-from-expr-nodes","text":"The @datumfunc decorator also wraps the function in another wrapper - the exprwrapper - and registers the wrapped function with the expression parser. The wrapper itself is simpler than the Python wrapper because it can make use of facilities in the parser to convert values and check for errors. When data reaches the exprwrapper, we already know that the arguments are all Datum objects.","title":"Datumfuncs called from expr nodes"},{"location":"devguide/values/#datumfuncs-of-single-numericscalar-arguments","text":"Functions of a single numeric or image Datum are sometimes written using an \"inner wrapper\", which will turn imagecubes and numbers into Value objects in a similar way to how the semantic binop wrappers work for operations. It will also wrap the resulting Value in a Datum. An example is pcot.datumfuncs.func_wrapper . Here is the datumfunc sin in full: @datumfunc def sin(a): \"\"\" Calculate sine of an angle in radians @param a:img,number:the angle (or image in which each pixel is a single angle) \"\"\" return func_wrapper(lambda xx: xx.sin(), a) We don't need worry about whether the argument is an image or a scalar, because the func_wrapper will deal with it. However, func_wrapper can only deal with images and scalars.","title":"Datumfuncs of single numeric/scalar arguments"},{"location":"devguide/values/#the-stats-wrapper","text":"Another datumfunc inner wrapper which can be used is stats_wrapper. This wraps a function which takes a (nominal,uncertainty,dq) tuple and returns another tuple of the same type. It does the following: If provided with a numeric value, calls the wrapped function and creates a Value from the returned data. If provided with an ImageCube, gets the subimage, splits it into bands, and calls the wrapped function on the non-BAD values in each band. The results for each band - assumed to be scalar - are converted into a vector Value with one value for each band. This lets us write the mean like this: @datumfunc def mean(val): return stats_wrapper(val, lambda n, u, d: (np.mean(n), pooled_sd(n, u), pcot.dq.NONE)) and it will work on scalar Values, vector Values and images - in the latter case, producing a vector Value.","title":"The stats wrapper"},{"location":"gettingstarted/","text":"Overview This provides a basic introduction to PCOT, telling you how to install it, providing important information on the core concepts behind PCOT, and giving a basic tutorial. Once you have tinkered with PCOT a little the User Guide will provide reference information. How to install and run PCOT Introduction to core PCOT concepts A first tutorial Video introduction There is a brief video introduction to PCOT.","title":"Overview"},{"location":"gettingstarted/#overview","text":"This provides a basic introduction to PCOT, telling you how to install it, providing important information on the core concepts behind PCOT, and giving a basic tutorial. Once you have tinkered with PCOT a little the User Guide will provide reference information. How to install and run PCOT Introduction to core PCOT concepts A first tutorial","title":"Overview"},{"location":"gettingstarted/#video-introduction","text":"There is a brief video introduction to PCOT.","title":"Video introduction"},{"location":"gettingstarted/concepts/","text":"Concepts How PCOT works (and why) PCOT was originally designed to help scientists and engineers analyse PanCam data and produce useful secondary data. It acts downstream from the Rover Operations Control Centre (ROCC) on images which have already been processed to some extent, and is a successor to ExoSpec 1 . As such, its primary purpose is to generate relative reflectance images and spectral parameter maps, although it will also be able to produce spectra from regions of interest. Indeed, it should be flexible enough to perform a wide range of unforeseen calculations. PCOT can also handle many other kinds of data. It is particularly suited to processing multispectral images with uncertainty and error data, and can currently read PDS4 and ENVI formats, alongside more common RGB formats which can be collated into multispectral images. Of paramount importance is the verifiability and reproducibility of data generated from PCOT. To this end, a PCOT document is a data product which can be shared between users, which also fully describes how the data was generated from the initial import to the final output. Users are encouraged to exchange PCOT documents in addition to, or instead of, the generated images or data. The Graph To achieve this, a PCOT document manipulates data in a graph - a network of nodes, each of which takes some data, works on it, perhaps displays some data, and perhaps outputs derived data. Technically speaking, this is a \"directed acyclic graph\": each connection has a direction, going from the output of one node to the input of another, and there can't be any loops. As an example, consider that we might want to overlay some kind of spectral parameter map, converted to a colour gradient, over an RGB image (note: I'm not a geologist, I'm a software engineer, so perhaps this is a very artificial example). One way to do it might be this: Figure: An example graph. Click on image to expand. We can see the graph in the panel on the right, showing each node as a box with connections to other nodes (ignore the green numbers, they just show how many times each node has run - it's a debugging aid!) Here's what each node in the graph is doing: The input 0 node reads input number 0 into the graph. The inputs are set up separately from the graph, and can be multispectral images or other data (e.g. housekeeping) from outside PCOT. The rect node lets the user draw a rectangle on the image to define a region of interest. Images can have many regions of interest and several different kinds are available. The node with 4 inputs a,b,c,d is an expr node, which calculates the result of a mathematical expression performed on each pixel. The node is showing the expression it is running: a$671 / a$438 . This will read the bands whose wavelengths are 671nm and 438nm in the node's a input, and find their ratio for every pixel. The result will be a single-band image. Expr nodes can perform much more complex calculations than this. The gradient node will convert a single-band image into an RGB image with a user-defined gradient and inset it into the RGB representation of another image - here we are insetting into the input image, using the RGB representation used by that node. Here is another example, showing a spectral plot: The input node again brings a multispectral image into the graph. The multidot node adds a number of small, circular regions of interest. Each has a different name and colour, in this case set automatically to just numbers and random colours. Creating the regions is as easy as clicking on the image. The spectrum node plots a spectrogram of the regions present in the image for all the wavelengths in that image. Figure: Spectrogram example. Click on image to expand. Here I have \"undocked\" the spectrum node's view to be a separate window for easy viewing. The spectrum can also be saved as a PDF or converted into CSV data. I'm also showing the entire app, including the menu bar and four input buttons. The Document A PCOT document is a file which can be shared among users. It consists of The inputs - data loaded from sources external to PCOT. These are kept separate from the graph, because you might want to use a different graph on the same inputs, or the load the same inputs into a different graph. There are currently up to four inputs, but this can easily be changed. The graph - a set of nodes and connections between them which define operations to be performed on inputs, as shown above. The settings - these are global to the entire application. Macros - these are sets of nodes which can be used multiple times and appear as single nodes in the graph, although each one has its own \"private\" graph. Currently very experimental (and largely undocumented). Important The data being sent out of the inputs into the graph is saved and loaded with the document, so the original source data does not need to be stored - you can send the document to someone and it will still work even if they don't have the sources. Quantities All numerical quantities in PCOT - whether scalar values or pixels in images - consist of three values: nominal value, uncertainty and data quality (DQ) bits. Uncertainty and DQ bitss can be viewed as overlays in the canvas (PCOT's image viewer component). Uncertainty The uncertainty is expressed as standard deviation, and is propagated through most operations. Be careful here, however - all values are assumed to be independent. This can lead to grossly incorrect results . For example, we could set up a graph consisting of a node with the value 0\\pm1 0\\pm1 , feed it into a mathematical expression ( expr ) node as variable *a), and set the node's expression to a-a a-a : Figure: Incorrect uncertainty calculation. Click on image to expand. The result, 0\\pm\\sqrt{2} 0\\pm\\sqrt{2} , is a consequence of the subtraction assuming that all quantities are independent even when they are clearly the same quantity. Data quality bits Each pixel in an image has a set of bits describing its quality in the source data, which are propagated through all operations. Additionally, other DQ bits may be added as the data moves through the graph - for example, division by zero may occur, or an operation whose result is undefined for those values. While DQ bits are flexible, the following are probably stable: Bit Meaning NODATA no data present (typically for a particular pixel) NOUNCERTAINTY Pixel has no uncertainty (again typically for pixels) SAT Data is saturated DIVZERO A division by zero has occurred UNDEF Result of operation is undefined COMPLEX Data is real part of complex result ERROR Unspecified error There may also be user-defined or \"test\" bits for use in development. Move on to a First Tutorial Allender, Elyse J., Roger B. Stabbins, Matthew D. Gunn, Claire R. Cousins, and Andrew J. Coates. \"The ExoMars spectral tool (ExoSpec): An image analysis tool for ExoMars 2020 PanCam imagery.\" In Image and Signal Processing for Remote Sensing XXIV , vol. 10789, pp. 163-181. SPIE, 2018. link to PDF \u21a9","title":"PCOT concepts"},{"location":"gettingstarted/concepts/#concepts","text":"How PCOT works (and why) PCOT was originally designed to help scientists and engineers analyse PanCam data and produce useful secondary data. It acts downstream from the Rover Operations Control Centre (ROCC) on images which have already been processed to some extent, and is a successor to ExoSpec 1 . As such, its primary purpose is to generate relative reflectance images and spectral parameter maps, although it will also be able to produce spectra from regions of interest. Indeed, it should be flexible enough to perform a wide range of unforeseen calculations. PCOT can also handle many other kinds of data. It is particularly suited to processing multispectral images with uncertainty and error data, and can currently read PDS4 and ENVI formats, alongside more common RGB formats which can be collated into multispectral images. Of paramount importance is the verifiability and reproducibility of data generated from PCOT. To this end, a PCOT document is a data product which can be shared between users, which also fully describes how the data was generated from the initial import to the final output. Users are encouraged to exchange PCOT documents in addition to, or instead of, the generated images or data.","title":"Concepts"},{"location":"gettingstarted/concepts/#the-graph","text":"To achieve this, a PCOT document manipulates data in a graph - a network of nodes, each of which takes some data, works on it, perhaps displays some data, and perhaps outputs derived data. Technically speaking, this is a \"directed acyclic graph\": each connection has a direction, going from the output of one node to the input of another, and there can't be any loops. As an example, consider that we might want to overlay some kind of spectral parameter map, converted to a colour gradient, over an RGB image (note: I'm not a geologist, I'm a software engineer, so perhaps this is a very artificial example). One way to do it might be this: Figure: An example graph. Click on image to expand. We can see the graph in the panel on the right, showing each node as a box with connections to other nodes (ignore the green numbers, they just show how many times each node has run - it's a debugging aid!) Here's what each node in the graph is doing: The input 0 node reads input number 0 into the graph. The inputs are set up separately from the graph, and can be multispectral images or other data (e.g. housekeeping) from outside PCOT. The rect node lets the user draw a rectangle on the image to define a region of interest. Images can have many regions of interest and several different kinds are available. The node with 4 inputs a,b,c,d is an expr node, which calculates the result of a mathematical expression performed on each pixel. The node is showing the expression it is running: a$671 / a$438 . This will read the bands whose wavelengths are 671nm and 438nm in the node's a input, and find their ratio for every pixel. The result will be a single-band image. Expr nodes can perform much more complex calculations than this. The gradient node will convert a single-band image into an RGB image with a user-defined gradient and inset it into the RGB representation of another image - here we are insetting into the input image, using the RGB representation used by that node. Here is another example, showing a spectral plot: The input node again brings a multispectral image into the graph. The multidot node adds a number of small, circular regions of interest. Each has a different name and colour, in this case set automatically to just numbers and random colours. Creating the regions is as easy as clicking on the image. The spectrum node plots a spectrogram of the regions present in the image for all the wavelengths in that image. Figure: Spectrogram example. Click on image to expand. Here I have \"undocked\" the spectrum node's view to be a separate window for easy viewing. The spectrum can also be saved as a PDF or converted into CSV data. I'm also showing the entire app, including the menu bar and four input buttons.","title":"The Graph"},{"location":"gettingstarted/concepts/#the-document","text":"A PCOT document is a file which can be shared among users. It consists of The inputs - data loaded from sources external to PCOT. These are kept separate from the graph, because you might want to use a different graph on the same inputs, or the load the same inputs into a different graph. There are currently up to four inputs, but this can easily be changed. The graph - a set of nodes and connections between them which define operations to be performed on inputs, as shown above. The settings - these are global to the entire application. Macros - these are sets of nodes which can be used multiple times and appear as single nodes in the graph, although each one has its own \"private\" graph. Currently very experimental (and largely undocumented). Important The data being sent out of the inputs into the graph is saved and loaded with the document, so the original source data does not need to be stored - you can send the document to someone and it will still work even if they don't have the sources.","title":"The Document"},{"location":"gettingstarted/concepts/#quantities","text":"All numerical quantities in PCOT - whether scalar values or pixels in images - consist of three values: nominal value, uncertainty and data quality (DQ) bits. Uncertainty and DQ bitss can be viewed as overlays in the canvas (PCOT's image viewer component).","title":"Quantities"},{"location":"gettingstarted/concepts/#uncertainty","text":"The uncertainty is expressed as standard deviation, and is propagated through most operations. Be careful here, however - all values are assumed to be independent. This can lead to grossly incorrect results . For example, we could set up a graph consisting of a node with the value 0\\pm1 0\\pm1 , feed it into a mathematical expression ( expr ) node as variable *a), and set the node's expression to a-a a-a : Figure: Incorrect uncertainty calculation. Click on image to expand. The result, 0\\pm\\sqrt{2} 0\\pm\\sqrt{2} , is a consequence of the subtraction assuming that all quantities are independent even when they are clearly the same quantity.","title":"Uncertainty"},{"location":"gettingstarted/concepts/#data-quality-bits","text":"Each pixel in an image has a set of bits describing its quality in the source data, which are propagated through all operations. Additionally, other DQ bits may be added as the data moves through the graph - for example, division by zero may occur, or an operation whose result is undefined for those values. While DQ bits are flexible, the following are probably stable: Bit Meaning NODATA no data present (typically for a particular pixel) NOUNCERTAINTY Pixel has no uncertainty (again typically for pixels) SAT Data is saturated DIVZERO A division by zero has occurred UNDEF Result of operation is undefined COMPLEX Data is real part of complex result ERROR Unspecified error There may also be user-defined or \"test\" bits for use in development. Move on to a First Tutorial Allender, Elyse J., Roger B. Stabbins, Matthew D. Gunn, Claire R. Cousins, and Andrew J. Coates. \"The ExoMars spectral tool (ExoSpec): An image analysis tool for ExoMars 2020 PanCam imagery.\" In Image and Signal Processing for Remote Sensing XXIV , vol. 10789, pp. 163-181. SPIE, 2018. link to PDF \u21a9","title":"Data quality bits"},{"location":"gettingstarted/help/","text":"Getting help Nodes Inside the PCOT app there are three ways to get help on a node: Double-clicking on the little blue box in the top right corner of that node in the graph, Using the right-click context menu on a node, Using the right-click context menu on a button in the palette. The node help texts are also available in the automatically generated documentation . If this isn't enough, don't hesitate to contact the Aberystwyth team. Expressions Doing the above on an expr node will tell you what operations, functions and properties are available. This text is also available in the automatically generated documentation for this node . You can also get help on properties and functions by right clicking in the log box at the bottom and selecting the appropriate option. Inside the expression box in the expr node, you can right-click on most things and ask for help.","title":"Getting help"},{"location":"gettingstarted/help/#getting-help","text":"","title":"Getting help"},{"location":"gettingstarted/help/#nodes","text":"Inside the PCOT app there are three ways to get help on a node: Double-clicking on the little blue box in the top right corner of that node in the graph, Using the right-click context menu on a node, Using the right-click context menu on a button in the palette. The node help texts are also available in the automatically generated documentation . If this isn't enough, don't hesitate to contact the Aberystwyth team.","title":"Nodes"},{"location":"gettingstarted/help/#expressions","text":"Doing the above on an expr node will tell you what operations, functions and properties are available. This text is also available in the automatically generated documentation for this node . You can also get help on properties and functions by right clicking in the log box at the bottom and selecting the appropriate option. Inside the expression box in the expr node, you can right-click on most things and ask for help.","title":"Expressions"},{"location":"gettingstarted/inputs/","text":"Loading different image formats The examples in the tutorial use ENVI images, but other image formats are available: plain RGB \"multiband\" images (multiple monochrome PNG images, one per band) PDS4 products (work in progress) Loading an ENVI image To reiterate, this is how to load an image from an ENVI file: Click an input button to open an input, and select ENVI. Click on \"Select Directory\" and choose a directory with ENVI files. Double click on .hdr files in the main file view. This will load the file into the input. Note that ENVI is a \"two-file\" format - the .dat files contain the data, while .hdr files of the same name in the same directory contain information about the data. We only show the .hdr file in the file view, but the corresponding .dat file must be present too. ENVI files are a useful multispectral format, and we can provide scripts to convert raw image sets into ENVI if required. We currently only support images which are 32-bit floating point in the BSQ (band-sequential) format. Loading an RGB image To load RGB, open an input and click the RGB button. A dialog will appear which is very similar to the ENVI file dialog above , but showing ENVI header files instead of image files. Double-click on such a file to load its associated data, which is assumed to be in the same directory with the same name. RGB images don't have filters, so there is no wavelength information - instead, the channels are named R, G and B and these names can be used in expr nodes (e.g. a$R ). Loading a \"multiband\" image Sometimes data is provided as a set of monochrome PNG files, although this is clearly far from ideal. In this case we need to tell PCOT how to work out which filter is being used for each file. Again, we open the dialog by clicking on an input button and clicking the appropriate method - \"Multifile\" in this case. This will open the ENVI dialog, which is rather more complex than the RGB or ENVI dialogs: Figure: An open Multiband input. Click on image to expand. The procedure here is roughly this: Make sure the file names contain the the lens (left or right) as L or R, and the filter position number. Work out a regular expression which can find these in the file name. Hopefully you can just use the default, which assumes that the filename contains a string like LWAC01 for left lens, position 01; or RWAC10 for right lens, position 10. If your filenames don't have this format and it's too difficult to rename them, you'll have to write an RE yourself or find a handy IT person to help. Get the files into a single directory and open the input dialog as shown above. Determine which camera configuration produced the images (PANCAM or AUPE are supported but more can be added) and set the Camera option accordingly. Different setups use different filter sets, and will translate filter positions into different filter wavelengths and names. Click the \"Get Directory\" button. In the new dialog, select a directory containing the image files and and click \"Choose.\" A lot of files will appear in the Files box. Double-click images to preview them as a single channel. If they are dark, select an appropriate multiplier and double-click again. Click in image checkboxes to select them; selected images will be combined into a single multispectral image. Close the dialog when you have the selected images you want. It might be a good idea to create an input node to examine the resulting multispectral image. There is more detail on the regular expression syntax here . PDS4 products This is very much work in progress - please contact the developers if you need this functionality soon.","title":"Loading other image formats"},{"location":"gettingstarted/inputs/#loading-different-image-formats","text":"The examples in the tutorial use ENVI images, but other image formats are available: plain RGB \"multiband\" images (multiple monochrome PNG images, one per band) PDS4 products (work in progress)","title":"Loading different image formats"},{"location":"gettingstarted/inputs/#loading-an-envi-image","text":"To reiterate, this is how to load an image from an ENVI file: Click an input button to open an input, and select ENVI. Click on \"Select Directory\" and choose a directory with ENVI files. Double click on .hdr files in the main file view. This will load the file into the input. Note that ENVI is a \"two-file\" format - the .dat files contain the data, while .hdr files of the same name in the same directory contain information about the data. We only show the .hdr file in the file view, but the corresponding .dat file must be present too. ENVI files are a useful multispectral format, and we can provide scripts to convert raw image sets into ENVI if required. We currently only support images which are 32-bit floating point in the BSQ (band-sequential) format.","title":"Loading an ENVI image"},{"location":"gettingstarted/inputs/#loading-an-rgb-image","text":"To load RGB, open an input and click the RGB button. A dialog will appear which is very similar to the ENVI file dialog above , but showing ENVI header files instead of image files. Double-click on such a file to load its associated data, which is assumed to be in the same directory with the same name. RGB images don't have filters, so there is no wavelength information - instead, the channels are named R, G and B and these names can be used in expr nodes (e.g. a$R ).","title":"Loading an RGB image"},{"location":"gettingstarted/inputs/#loading-a-multiband-image","text":"Sometimes data is provided as a set of monochrome PNG files, although this is clearly far from ideal. In this case we need to tell PCOT how to work out which filter is being used for each file. Again, we open the dialog by clicking on an input button and clicking the appropriate method - \"Multifile\" in this case. This will open the ENVI dialog, which is rather more complex than the RGB or ENVI dialogs: Figure: An open Multiband input. Click on image to expand. The procedure here is roughly this: Make sure the file names contain the the lens (left or right) as L or R, and the filter position number. Work out a regular expression which can find these in the file name. Hopefully you can just use the default, which assumes that the filename contains a string like LWAC01 for left lens, position 01; or RWAC10 for right lens, position 10. If your filenames don't have this format and it's too difficult to rename them, you'll have to write an RE yourself or find a handy IT person to help. Get the files into a single directory and open the input dialog as shown above. Determine which camera configuration produced the images (PANCAM or AUPE are supported but more can be added) and set the Camera option accordingly. Different setups use different filter sets, and will translate filter positions into different filter wavelengths and names. Click the \"Get Directory\" button. In the new dialog, select a directory containing the image files and and click \"Choose.\" A lot of files will appear in the Files box. Double-click images to preview them as a single channel. If they are dark, select an appropriate multiplier and double-click again. Click in image checkboxes to select them; selected images will be combined into a single multispectral image. Close the dialog when you have the selected images you want. It might be a good idea to create an input node to examine the resulting multispectral image. There is more detail on the regular expression syntax here .","title":"Loading a \"multiband\" image"},{"location":"gettingstarted/inputs/#pds4-products","text":"This is very much work in progress - please contact the developers if you need this functionality soon.","title":"PDS4 products"},{"location":"gettingstarted/installrun/","text":"Installing and running PCOT PCOT is a Python program (and library) with a number of dependencies, notably numpy and PySide2 (the official Python interface to Qt). We find the best way to manage these is to use Anaconda and Poetry . Installation has been tested on Windows 10, MacOS and Ubuntu 20.04. Install Anaconda The first thing you will need to do is install Anaconda, which can be done from here: Windows: https://docs.anaconda.com/anaconda/install/windows/ Linux: https://docs.anaconda.com/anaconda/install/linux/ MacOS: https://docs.anaconda.com/anaconda/install/mac-os/ (untested) Obtain the software This can be done by either downloading the archive from Github and extracting it into a new directory, or cloning the repository. In both cases, the top level directory should be called PCOT (this isn't really mandatory but makes the instructions below simpler). The best way to download is this: Open an Anaconda shell window (see below) If you have an SSH key set up for GitHub, type this command into the shell ( changing the repository address if it is different ): git clone git@github.com:AU-ExoMars/PCOT.git Otherwise type this: git clone https://github.com/AU-ExoMars/PCOT.git You should now have a PCOT directory which will contain this file (as README.md) and quite a few others. Opening Anaconda's shell on different OSs Windows: Open the Anaconda Powershell Prompt application, which will have been installed when you installed Anaconda. This can be found in the Start menu under Anaconda3, or in the applications list in Anaconda Navigator (which is also in the Start menu in the same place). Linux and MacOS : just open a Bash shell Installing on Ubuntu / MacOS Assuming you have successfully installed Anaconda and cloned or downloaded PCOT as above: Open a bash shell cd to the PCOT directory (which contains this file). Run the command conda create -n pcot python=3.8 poetry . This will create an environment called pcot which uses Python 3.8 and the Poetry dependency and packaging manager. It may take some time. Activate the environment with conda activate pcot . Now run poetry install . This will set up all the packages PCOT is dependent on and install PCOT. You should now be able to run pcot to start the application. Installing on Windows Assuming you have successfully installed Anaconda and cloned or downloaded PCOT as above: Open the Anaconda PowerShell Prompt application from the Start Menu. cd to the PCOT directory (which contains this file). Run the command conda create -n pcot python=3.8 poetry . This will create an environment called pcot which uses Python 3.8 and the Poetry dependency and packaging manager. It may take some time. Activate the environment with conda activate pcot . Now run poetry install . This will set up all the packages PCOT is dependent on and install PCOT. You should now be able to run pcot to start the application. Running PCOT Once you have installed PCOT as above, you can run it by opening an Anaconda shell and entering the following commands: conda activate pcot pcot Running PCOT inside Pycharm These instructions may be useful if you want to run PCOT inside a debugger - for example, if you are testing a custom node. First set up the Conda environment and interpreter: Open PyCharm and open the PCOT directory as an existing project. Open File/Settings.. (Ctrl+Alt+S) Select Project:PCOT / Python Interpreter If the Python Interpreter is not already Python 3.8 with something like anaconda3/envs/pcot/bin/python Select the cogwheel to the right of the Python Interpreter dropdown and then select Add . Select Conda Environment . Select Existing Environment . Select the environment: it should be something like anaconda3/envs/pcot/bin/python . Select OK . Now set up the run configuration: Select Edit Configurations... (or it might be Add Configuration... ) from the configurations drop down in the menu bar Add a new configuration (the + symbol) and select Python Set Script Path to PCOT/src/pcot/__main__.py Make sure the interpreter is something like Project Default (Python 3.8 (pcot)) , i.e. the Python interpreter of the pcot environment. You should now be able to run and debug PCOT. Environment variables It's a good idea, but not mandatory, to set the environment variable PCOT_USER to a string of the form name . For example, in Linux I have added the following to my .bashrc file: export PCOT_USER=\"Jim Finnis \" This data is added to all saved PCOT graphs. If the environment variable is not set, the username returned by Python's getpass module is used (e.g. 'jcf12'). In case of problems There are a few things which can stop PCOT running - see issues .","title":"Installing and running"},{"location":"gettingstarted/installrun/#installing-and-running-pcot","text":"PCOT is a Python program (and library) with a number of dependencies, notably numpy and PySide2 (the official Python interface to Qt). We find the best way to manage these is to use Anaconda and Poetry . Installation has been tested on Windows 10, MacOS and Ubuntu 20.04.","title":"Installing and running PCOT"},{"location":"gettingstarted/installrun/#install-anaconda","text":"The first thing you will need to do is install Anaconda, which can be done from here: Windows: https://docs.anaconda.com/anaconda/install/windows/ Linux: https://docs.anaconda.com/anaconda/install/linux/ MacOS: https://docs.anaconda.com/anaconda/install/mac-os/ (untested)","title":"Install Anaconda"},{"location":"gettingstarted/installrun/#obtain-the-software","text":"This can be done by either downloading the archive from Github and extracting it into a new directory, or cloning the repository. In both cases, the top level directory should be called PCOT (this isn't really mandatory but makes the instructions below simpler). The best way to download is this: Open an Anaconda shell window (see below) If you have an SSH key set up for GitHub, type this command into the shell ( changing the repository address if it is different ): git clone git@github.com:AU-ExoMars/PCOT.git Otherwise type this: git clone https://github.com/AU-ExoMars/PCOT.git You should now have a PCOT directory which will contain this file (as README.md) and quite a few others.","title":"Obtain the software"},{"location":"gettingstarted/installrun/#opening-anacondas-shell-on-different-oss","text":"Windows: Open the Anaconda Powershell Prompt application, which will have been installed when you installed Anaconda. This can be found in the Start menu under Anaconda3, or in the applications list in Anaconda Navigator (which is also in the Start menu in the same place). Linux and MacOS : just open a Bash shell","title":"Opening Anaconda's shell on different OSs"},{"location":"gettingstarted/installrun/#installing-on-ubuntu-macos","text":"Assuming you have successfully installed Anaconda and cloned or downloaded PCOT as above: Open a bash shell cd to the PCOT directory (which contains this file). Run the command conda create -n pcot python=3.8 poetry . This will create an environment called pcot which uses Python 3.8 and the Poetry dependency and packaging manager. It may take some time. Activate the environment with conda activate pcot . Now run poetry install . This will set up all the packages PCOT is dependent on and install PCOT. You should now be able to run pcot to start the application.","title":"Installing on Ubuntu / MacOS"},{"location":"gettingstarted/installrun/#installing-on-windows","text":"Assuming you have successfully installed Anaconda and cloned or downloaded PCOT as above: Open the Anaconda PowerShell Prompt application from the Start Menu. cd to the PCOT directory (which contains this file). Run the command conda create -n pcot python=3.8 poetry . This will create an environment called pcot which uses Python 3.8 and the Poetry dependency and packaging manager. It may take some time. Activate the environment with conda activate pcot . Now run poetry install . This will set up all the packages PCOT is dependent on and install PCOT. You should now be able to run pcot to start the application.","title":"Installing on Windows"},{"location":"gettingstarted/installrun/#running-pcot","text":"Once you have installed PCOT as above, you can run it by opening an Anaconda shell and entering the following commands: conda activate pcot pcot","title":"Running PCOT"},{"location":"gettingstarted/installrun/#running-pcot-inside-pycharm","text":"These instructions may be useful if you want to run PCOT inside a debugger - for example, if you are testing a custom node. First set up the Conda environment and interpreter: Open PyCharm and open the PCOT directory as an existing project. Open File/Settings.. (Ctrl+Alt+S) Select Project:PCOT / Python Interpreter If the Python Interpreter is not already Python 3.8 with something like anaconda3/envs/pcot/bin/python Select the cogwheel to the right of the Python Interpreter dropdown and then select Add . Select Conda Environment . Select Existing Environment . Select the environment: it should be something like anaconda3/envs/pcot/bin/python . Select OK . Now set up the run configuration: Select Edit Configurations... (or it might be Add Configuration... ) from the configurations drop down in the menu bar Add a new configuration (the + symbol) and select Python Set Script Path to PCOT/src/pcot/__main__.py Make sure the interpreter is something like Project Default (Python 3.8 (pcot)) , i.e. the Python interpreter of the pcot environment. You should now be able to run and debug PCOT.","title":"Running PCOT inside Pycharm"},{"location":"gettingstarted/installrun/#environment-variables","text":"It's a good idea, but not mandatory, to set the environment variable PCOT_USER to a string of the form name . For example, in Linux I have added the following to my .bashrc file: export PCOT_USER=\"Jim Finnis \" This data is added to all saved PCOT graphs. If the environment variable is not set, the username returned by Python's getpass module is used (e.g. 'jcf12').","title":"Environment variables"},{"location":"gettingstarted/installrun/#in-case-of-problems","text":"There are a few things which can stop PCOT running - see issues .","title":"In case of problems"},{"location":"gettingstarted/issues/","text":"Common runtime issues Can't start Qt on Linux This sometimes happens: qt.qpa.plugin: Could not load the Qt platform plugin \"xcb\" in \"\" even though it was found. This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem. Available platform plugins are: eglfs, linuxfb, minimal, minimalegl, offscreen, vnc, wayland-egl, wayland, wayland-xcomposite-egl, wayland-xcomposite-glx, webgl, xcb. Try this: export QT_DEBUG_PLUGINS=1 pcot to run the program again, and look at the output. You might see errors like this (I've removed some stuff): QFactoryLoader::QFactoryLoader() checking directory path \"[...]envs/pcot/bin/platforms\" ... Cannot load library [...]/plugins/platforms/libqxcb.so: (libxcb-xinerama.so.0: cannot open shared object file: No such file or directory) QLibraryPrivate::loadPlugin failed on \"...[stuff removed].. (libxcb-xinerama.so.0: cannot open shared object file: No such file or directory)\" If that's the case, install the missing package: sudo apt install libxcb-xinerama0 That might help. Otherwise, send a message to us with the output from the QT_DEBUG_PLUGINS run and we will investigate. Conda fails on Windows I have once seen an error involving OpenSSH not being correctly installed on Windows when the conda create... command was run. This happened toward the end of the installation. To fix it, I just ran the command again - it installed OpenSSH correctly and I was able to proceed.","title":"Known issues"},{"location":"gettingstarted/issues/#common-runtime-issues","text":"","title":"Common runtime issues"},{"location":"gettingstarted/issues/#cant-start-qt-on-linux","text":"This sometimes happens: qt.qpa.plugin: Could not load the Qt platform plugin \"xcb\" in \"\" even though it was found. This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem. Available platform plugins are: eglfs, linuxfb, minimal, minimalegl, offscreen, vnc, wayland-egl, wayland, wayland-xcomposite-egl, wayland-xcomposite-glx, webgl, xcb. Try this: export QT_DEBUG_PLUGINS=1 pcot to run the program again, and look at the output. You might see errors like this (I've removed some stuff): QFactoryLoader::QFactoryLoader() checking directory path \"[...]envs/pcot/bin/platforms\" ... Cannot load library [...]/plugins/platforms/libqxcb.so: (libxcb-xinerama.so.0: cannot open shared object file: No such file or directory) QLibraryPrivate::loadPlugin failed on \"...[stuff removed].. (libxcb-xinerama.so.0: cannot open shared object file: No such file or directory)\" If that's the case, install the missing package: sudo apt install libxcb-xinerama0 That might help. Otherwise, send a message to us with the output from the QT_DEBUG_PLUGINS run and we will investigate.","title":"Can't start Qt on Linux"},{"location":"gettingstarted/issues/#conda-fails-on-windows","text":"I have once seen an error involving OpenSSH not being correctly installed on Windows when the conda create... command was run. This happened toward the end of the installation. To fix it, I just ran the command again - it installed OpenSSH correctly and I was able to proceed.","title":"Conda fails on Windows"},{"location":"gettingstarted/tutold/","text":"A first tutorial This page is a gentle introduction to PCOT's user interface elements, and will walk you through loading image data and performing some simple manipulations. There is a video guide - it may be a little out of date, because development is progressing quickly but the basics will be the same (this also applies to this document). Be aware that this is very much an early version and there are no doubt a lot of serious problems! Preparation PCOT is primarily designed to work with multispectral images. For this tutorial we have prepared an image and packaged it with the rest of the system, but you will need to uncompress it: it's an ENVI image and these can be rather large. It can be found in the exampleData directory as obj4-sand-subset.zip . Uncompress it somewhere (perhaps into the same directory) producing two files: 1.hdr (the header file) and 1.dat (the data itself). This is a 256x256 image with taken using AUPE (Aberystwyth University Pancam Emulator), with 11 bands of data. It is a subset of a larger image used in testing, and is shown below as an RGB image: Figure: The example image (640nm, 550nm and 440nm bands). Click on image to expand. Now you have this image you can start PCOT. Introduction to the user interface First, a quick look at the UI with a document open. The image below shows the PCOT interface with text in red describing each part. Figure: The PCOT user interface. Click on image to expand. The window is divided into several areas: At the top, the input buttons each open one of PCOT's input windows. These describe how information is read into PCOT. Below this and to the left is the node area , which will be empty on startup. Double-clicking on a node in the graph will open a detailed view for that node. You can have several views open in this area looking at different nodes, and you can undock by double-clicking on the tab at the top of the view. To the right of the node area is the graph . This shows the nodes in the document and their connections. A new document always has an input 0 node, which brings input 0 into the graph. To the right of the graph is the palette . Clicking on a button in the palette will add a node of the given type to the graph. At the bottom is the log area , the status bar , and a set of global controls . Working with graph nodes Selecting in the graph When you open the program the first thing you will see is a graph containing a single input node, with no nodes open. You can select a node in the graph by clicking on it, or by dragging a box which intersects the node. A selected node will be tinted blue. Opening a node for editing Double-clicking on a node will open that node for editing. If you double-click on the input node, it will turn a dark cyan colour and a view of the node will appear on the left, along with a tab to select that view when multiple node views are open: Figure: An open but unused input node. Click on image to expand. The node is cyan because the current view's node is tinted green, and the node is already tinted blue because it is selected in the graph. There are two selection models: Multiple nodes can be selected in the graph; these are tinted blue. A single node's view can be open and currently being edited, this node is tinted green in the graph. Nodes which are both being edited and are selected are tinted cyan. The view for this particular node - input 0 - shows what external input is currently being read into PCOT on the input numbered 0. In this case it shows \"None\" because there is currently no input. Creating a new node This can be done in two ways: Clicking on a node type in the palette will create a new instance of that type in the graph, hopefully somewhere sensible. Right-clicking a node type from the palette and dragging onto the graph will create a node where you wish. \"Undocking\" a node's view Sometimes it is useful to see several node views at the same time. Double-clicking on the tab, where the name of the node type appears, will make the view open in a separate window. This can be done for several views, and the windows can be rearranged as you wish. Closing the window will \"redock\" the view so it appears in the node area as before. Constant and comment nodes These two nodes are special - the boxes in the graph have text fields which can be edited. For constant nodes, the value in the box will be the numeric output of the node. This node has no view, and double-clicking has no effect. For comment nodes, the value in the box is a text comment that can help other users navigate the graph. Once edited, the box can be resized by clicking and dragging its bottom-right corner. The text will flow to fit the box. Double-clicking on a comment node opens a view which provides an alternative way of editing the text, as well as changing the font size and colours. It's also the only way of getting blank lines into the text, since hitting the \"enter\" key will stop editing when you are editing the node text directly in its box. Figure: Comment and constant nodes. Click on image to expand. Loading an image While PCOT can support several types of multispectral data file, we will load an RGB image into PCOT for now. Clicking on the Input 0 button at the top of the main window will open the first of the four input's editor window. This will show a number of buttons, one for each input method. Click on RGB, and the window will show that input's RGB method settings and select the RGB method as being active (see below). Using the directory tree widget, double-click any image file (PNG or RGB). The right-hand side is a common component in PCOT known as a \"canvas\", which will show the image selected. The canvas lets you modify how the RGB channels are mapped using the three combo boxes. Here, they will just hold \"R\", \"G\" and \"B\" as the source band names because this is an RGB image source. Figure: An open RGB input. Click on image to expand. At the bottom of the image are three source indicators : these show what bands within which inputs were used to generate the canvas image. They should show something like [0:R][0:G][0:B] meaning that the red channel was generated from the band labelled \"R\" in input 0, and so on. Canvases Most nodes use a canvas to display some bands of an image as RGB. This will take up a large proportion of their view - in some cases all of it - with no other controls. It is worth discussing in some detail. A canvas is shown in the previous section as the right-hand side of an input widget. Another is shown below as the entire control area for an actual input node, which brings one of the four inputs into the graph. Figure: An open *input 0* node. Click on image to expand. You can pan the canvas using the scroll bars at the edge, and zoom with the mouse wheel. The button at bottom right will reset to show the entire image. To the left of the canvas image itself are three combo boxes which determine the image mapping : how the bands within the (probably) multispectral image map onto RGB channels for viewing. Each band in the combo box shows the input number, a colon, and typically the name, position or wavelength of the band. Exactly what is shown depends on the image being loaded and the Caption global control . Below this are some assorted controls: Show ROIs will mark any regions of interest the image has - these are added using nodes like rect (for a rectangle) and are carried forward through subsequent nodes (where appropriate), delimiting the area upon which calculations are performed. They also control the regions used for calculating spectra. Normally an ROI is only shown in the node which adds it. Show spectrum opens a side pane, and dragging the cursor across the image will plot the spectrum of the pixel under the cursor in this pane. If no filter wavelengths are available, a list of the values for each band is shown. Save RGB saves the RGB-mapped image as a PNG. Guess RGB tries to guess appropriate channels for the RGB canvas image. The canvas is going to get a lot more complex in the next release, because it needs to show image quality and uncertainty data for each band. This information will be moved into its own page Manipulating an image Let's perform a simple manipulation on an RGB image. It's not what PCOT is for, but it will demonstrate some of the principles without requiring actual multispectral data. In this example, we'll generate a \"red/green slope\" image (which is pretty meaningless). Start PCOT and load an image into input 0 as before, by clicking on the Input 0 button, selecting RGB and double-clicking on an image file. Double-click on the input 0 node in the graph - instances of this node bring input 0 into the graph. Click on expr in the palette (on the right) to create an expression evaluation node. Drag a connection from the output of input 0 to the a input of expr Double-click on the expr node to open its view for editing We now have an input feeding into an expression evaluator, which we can edit. First, let's just see one band. Click in the expr view's expression box: this is the box which says \"Enter expression here...\". Enter the string a$R This says \"select band R from input a \" - \"R\" is the name given to the red channel in RGB images (in multispectral images we typically use wavelengths on the right-hand side of the $ operator, such as a$640 ). Press \"Run\" in the node's view. You should now see a monochrome image in the node's canvas: the result of the calculation, containing only the red channel. Now change the expression to a$R - a$G and press \"Run\". This will set each pixel to the result of subtracting the green value for that pixel from the red value, giving us our \"red/green slope.\" You will probably see quite a mess, because the canvas expects data between 0 and 1 and some of the pixels will probably be negative. We need to normalise the image to that range. Change the expression to norm(a$R - a$G) and press \"Run\" again to see the final result. Note that the source indicators in the bottom left of the image are now displaying something like [0:G&0:R][0:G&0:R][0:G&0:R] This indicates that all three RGB channels shown in the canvas are getting their data from both the R and G bands of input 0. The end result should look something like this: Figure: A \"red-green slope\" calculated from an RGB image. Click on image to expand. Getting help Nodes Inside the PCOT app there are three ways to get help on a node: Double-clicking on the little blue box in the top right corner of that node in the graph, Using the right-click context menu on a node, Using the right-click context menu on a button in the palette. The node help texts are also available in the automatically generated documentation . If this isn't enough, don't hesitate to contact the Aberystwyth team. Expressions Doing the above on an expr node will tell you what operations, functions and properties are available. This text is also available in the automatically generated documentation for this node . You can also get help on properties and functions by right clicking in the log box at the bottom and selecting the appropriate option. Inside the expression box in the expr node, you can right-click on most things and ask for help. Loading different image formats The examples above use RGB images, which aren't much use for real work. Other image formats are available: ENVI images \"multiband\" images (multiple monochrome PNG images, one per band) PDS4 products (work in progress) Loading an ENVI image ENVI images consists of a header (.hdr) file and the actual data (.dat) file. Currently PCOT can only load ENVI files which are 32-bit floating point, BSQ (band sequential) interleaved. To load ENVI, open an input and click the ENVI button. A dialog will appear which is very similar to the RGB file dialog above , but showing ENVI header files instead of image files. Double-click on such a file to load its associated data, which is assumed to be in the same directory with the same name. Filter name and wavelength information will be taken from the file. Loading a \"multiband\" image Sometimes data is provided as a set of monochrome PNG files, although this is clearly far from ideal. In this case we need to tell PCOT how to work out which filter is being used for each file. Again, we open the dialog by clicking on an input button and clicking the appropriate method - \"Multifile\" in this case. This will open the ENVI dialog, which is rather more complex than the RGB or ENVI dialogs: Figure: An open ENVI input. Click on image to expand. The procedure here is roughly this: Make sure the file names contain the the lens (left or right) as L or R, and the filter position number. Work out a regular expression which can find these in the file name. Hopefully you can just use the default, which assumes that the filename contains a string like LWAC01 for left lens, position 01; or RWAC10 for right lens, position 10. If your filenames don't have this format and it's too difficult to rename them, you'll have to write an RE yourself or find a handy IT person to help. Get the files into a single directory and open the input dialog as shown above. Determine which camera configuration - PANCAM or AUPE - produced the images and set the Camera option accordingly. These two setups use different filter sets, and will translate filter positions into different filter wavelengths and names. Click the \"Get Directory\" button. In the new dialog, select a directory containing the ENVI .hdr files and their accompanying .dat files, and click \"Select Folder\". A lot of files will appear in the Files box. Double-click images to preview them as a single channel. If they are dark, select an appropriate multiplier and double-click again. Click in image checkboxes to select them; selected images will be combined into a single multispectral mage. Close the dialog when you have the selected images you want. It might be a good idea to create an input node to examine the resulting multispectral image. PDS4 products This is very much work in progress - please contact the developers if you need this functionality soon.","title":"A first tutorial"},{"location":"gettingstarted/tutold/#a-first-tutorial","text":"This page is a gentle introduction to PCOT's user interface elements, and will walk you through loading image data and performing some simple manipulations. There is a video guide - it may be a little out of date, because development is progressing quickly but the basics will be the same (this also applies to this document). Be aware that this is very much an early version and there are no doubt a lot of serious problems!","title":"A first tutorial"},{"location":"gettingstarted/tutold/#preparation","text":"PCOT is primarily designed to work with multispectral images. For this tutorial we have prepared an image and packaged it with the rest of the system, but you will need to uncompress it: it's an ENVI image and these can be rather large. It can be found in the exampleData directory as obj4-sand-subset.zip . Uncompress it somewhere (perhaps into the same directory) producing two files: 1.hdr (the header file) and 1.dat (the data itself). This is a 256x256 image with taken using AUPE (Aberystwyth University Pancam Emulator), with 11 bands of data. It is a subset of a larger image used in testing, and is shown below as an RGB image: Figure: The example image (640nm, 550nm and 440nm bands). Click on image to expand. Now you have this image you can start PCOT.","title":"Preparation"},{"location":"gettingstarted/tutold/#introduction-to-the-user-interface","text":"First, a quick look at the UI with a document open. The image below shows the PCOT interface with text in red describing each part. Figure: The PCOT user interface. Click on image to expand. The window is divided into several areas: At the top, the input buttons each open one of PCOT's input windows. These describe how information is read into PCOT. Below this and to the left is the node area , which will be empty on startup. Double-clicking on a node in the graph will open a detailed view for that node. You can have several views open in this area looking at different nodes, and you can undock by double-clicking on the tab at the top of the view. To the right of the node area is the graph . This shows the nodes in the document and their connections. A new document always has an input 0 node, which brings input 0 into the graph. To the right of the graph is the palette . Clicking on a button in the palette will add a node of the given type to the graph. At the bottom is the log area , the status bar , and a set of global controls .","title":"Introduction to the user interface"},{"location":"gettingstarted/tutold/#working-with-graph-nodes","text":"","title":"Working with graph nodes"},{"location":"gettingstarted/tutold/#selecting-in-the-graph","text":"When you open the program the first thing you will see is a graph containing a single input node, with no nodes open. You can select a node in the graph by clicking on it, or by dragging a box which intersects the node. A selected node will be tinted blue.","title":"Selecting in the graph"},{"location":"gettingstarted/tutold/#opening-a-node-for-editing","text":"Double-clicking on a node will open that node for editing. If you double-click on the input node, it will turn a dark cyan colour and a view of the node will appear on the left, along with a tab to select that view when multiple node views are open: Figure: An open but unused input node. Click on image to expand. The node is cyan because the current view's node is tinted green, and the node is already tinted blue because it is selected in the graph. There are two selection models: Multiple nodes can be selected in the graph; these are tinted blue. A single node's view can be open and currently being edited, this node is tinted green in the graph. Nodes which are both being edited and are selected are tinted cyan. The view for this particular node - input 0 - shows what external input is currently being read into PCOT on the input numbered 0. In this case it shows \"None\" because there is currently no input.","title":"Opening a node for editing"},{"location":"gettingstarted/tutold/#creating-a-new-node","text":"This can be done in two ways: Clicking on a node type in the palette will create a new instance of that type in the graph, hopefully somewhere sensible. Right-clicking a node type from the palette and dragging onto the graph will create a node where you wish.","title":"Creating a new node"},{"location":"gettingstarted/tutold/#undocking-a-nodes-view","text":"Sometimes it is useful to see several node views at the same time. Double-clicking on the tab, where the name of the node type appears, will make the view open in a separate window. This can be done for several views, and the windows can be rearranged as you wish. Closing the window will \"redock\" the view so it appears in the node area as before.","title":"\"Undocking\" a node's view"},{"location":"gettingstarted/tutold/#constant-and-comment-nodes","text":"These two nodes are special - the boxes in the graph have text fields which can be edited. For constant nodes, the value in the box will be the numeric output of the node. This node has no view, and double-clicking has no effect. For comment nodes, the value in the box is a text comment that can help other users navigate the graph. Once edited, the box can be resized by clicking and dragging its bottom-right corner. The text will flow to fit the box. Double-clicking on a comment node opens a view which provides an alternative way of editing the text, as well as changing the font size and colours. It's also the only way of getting blank lines into the text, since hitting the \"enter\" key will stop editing when you are editing the node text directly in its box. Figure: Comment and constant nodes. Click on image to expand.","title":"Constant and comment nodes"},{"location":"gettingstarted/tutold/#loading-an-image","text":"While PCOT can support several types of multispectral data file, we will load an RGB image into PCOT for now. Clicking on the Input 0 button at the top of the main window will open the first of the four input's editor window. This will show a number of buttons, one for each input method. Click on RGB, and the window will show that input's RGB method settings and select the RGB method as being active (see below). Using the directory tree widget, double-click any image file (PNG or RGB). The right-hand side is a common component in PCOT known as a \"canvas\", which will show the image selected. The canvas lets you modify how the RGB channels are mapped using the three combo boxes. Here, they will just hold \"R\", \"G\" and \"B\" as the source band names because this is an RGB image source. Figure: An open RGB input. Click on image to expand. At the bottom of the image are three source indicators : these show what bands within which inputs were used to generate the canvas image. They should show something like [0:R][0:G][0:B] meaning that the red channel was generated from the band labelled \"R\" in input 0, and so on.","title":"Loading an image"},{"location":"gettingstarted/tutold/#canvases","text":"Most nodes use a canvas to display some bands of an image as RGB. This will take up a large proportion of their view - in some cases all of it - with no other controls. It is worth discussing in some detail. A canvas is shown in the previous section as the right-hand side of an input widget. Another is shown below as the entire control area for an actual input node, which brings one of the four inputs into the graph. Figure: An open *input 0* node. Click on image to expand. You can pan the canvas using the scroll bars at the edge, and zoom with the mouse wheel. The button at bottom right will reset to show the entire image. To the left of the canvas image itself are three combo boxes which determine the image mapping : how the bands within the (probably) multispectral image map onto RGB channels for viewing. Each band in the combo box shows the input number, a colon, and typically the name, position or wavelength of the band. Exactly what is shown depends on the image being loaded and the Caption global control . Below this are some assorted controls: Show ROIs will mark any regions of interest the image has - these are added using nodes like rect (for a rectangle) and are carried forward through subsequent nodes (where appropriate), delimiting the area upon which calculations are performed. They also control the regions used for calculating spectra. Normally an ROI is only shown in the node which adds it. Show spectrum opens a side pane, and dragging the cursor across the image will plot the spectrum of the pixel under the cursor in this pane. If no filter wavelengths are available, a list of the values for each band is shown. Save RGB saves the RGB-mapped image as a PNG. Guess RGB tries to guess appropriate channels for the RGB canvas image. The canvas is going to get a lot more complex in the next release, because it needs to show image quality and uncertainty data for each band. This information will be moved into its own page","title":"Canvases"},{"location":"gettingstarted/tutold/#manipulating-an-image","text":"Let's perform a simple manipulation on an RGB image. It's not what PCOT is for, but it will demonstrate some of the principles without requiring actual multispectral data. In this example, we'll generate a \"red/green slope\" image (which is pretty meaningless). Start PCOT and load an image into input 0 as before, by clicking on the Input 0 button, selecting RGB and double-clicking on an image file. Double-click on the input 0 node in the graph - instances of this node bring input 0 into the graph. Click on expr in the palette (on the right) to create an expression evaluation node. Drag a connection from the output of input 0 to the a input of expr Double-click on the expr node to open its view for editing We now have an input feeding into an expression evaluator, which we can edit. First, let's just see one band. Click in the expr view's expression box: this is the box which says \"Enter expression here...\". Enter the string a$R This says \"select band R from input a \" - \"R\" is the name given to the red channel in RGB images (in multispectral images we typically use wavelengths on the right-hand side of the $ operator, such as a$640 ). Press \"Run\" in the node's view. You should now see a monochrome image in the node's canvas: the result of the calculation, containing only the red channel. Now change the expression to a$R - a$G and press \"Run\". This will set each pixel to the result of subtracting the green value for that pixel from the red value, giving us our \"red/green slope.\" You will probably see quite a mess, because the canvas expects data between 0 and 1 and some of the pixels will probably be negative. We need to normalise the image to that range. Change the expression to norm(a$R - a$G) and press \"Run\" again to see the final result. Note that the source indicators in the bottom left of the image are now displaying something like [0:G&0:R][0:G&0:R][0:G&0:R] This indicates that all three RGB channels shown in the canvas are getting their data from both the R and G bands of input 0. The end result should look something like this: Figure: A \"red-green slope\" calculated from an RGB image. Click on image to expand.","title":"Manipulating an image"},{"location":"gettingstarted/tutold/#getting-help","text":"","title":"Getting help"},{"location":"gettingstarted/tutold/#nodes","text":"Inside the PCOT app there are three ways to get help on a node: Double-clicking on the little blue box in the top right corner of that node in the graph, Using the right-click context menu on a node, Using the right-click context menu on a button in the palette. The node help texts are also available in the automatically generated documentation . If this isn't enough, don't hesitate to contact the Aberystwyth team.","title":"Nodes"},{"location":"gettingstarted/tutold/#expressions","text":"Doing the above on an expr node will tell you what operations, functions and properties are available. This text is also available in the automatically generated documentation for this node . You can also get help on properties and functions by right clicking in the log box at the bottom and selecting the appropriate option. Inside the expression box in the expr node, you can right-click on most things and ask for help.","title":"Expressions"},{"location":"gettingstarted/tutold/#loading-different-image-formats","text":"The examples above use RGB images, which aren't much use for real work. Other image formats are available: ENVI images \"multiband\" images (multiple monochrome PNG images, one per band) PDS4 products (work in progress)","title":"Loading different image formats"},{"location":"gettingstarted/tutold/#loading-an-envi-image","text":"ENVI images consists of a header (.hdr) file and the actual data (.dat) file. Currently PCOT can only load ENVI files which are 32-bit floating point, BSQ (band sequential) interleaved. To load ENVI, open an input and click the ENVI button. A dialog will appear which is very similar to the RGB file dialog above , but showing ENVI header files instead of image files. Double-click on such a file to load its associated data, which is assumed to be in the same directory with the same name. Filter name and wavelength information will be taken from the file.","title":"Loading an ENVI image"},{"location":"gettingstarted/tutold/#loading-a-multiband-image","text":"Sometimes data is provided as a set of monochrome PNG files, although this is clearly far from ideal. In this case we need to tell PCOT how to work out which filter is being used for each file. Again, we open the dialog by clicking on an input button and clicking the appropriate method - \"Multifile\" in this case. This will open the ENVI dialog, which is rather more complex than the RGB or ENVI dialogs: Figure: An open ENVI input. Click on image to expand. The procedure here is roughly this: Make sure the file names contain the the lens (left or right) as L or R, and the filter position number. Work out a regular expression which can find these in the file name. Hopefully you can just use the default, which assumes that the filename contains a string like LWAC01 for left lens, position 01; or RWAC10 for right lens, position 10. If your filenames don't have this format and it's too difficult to rename them, you'll have to write an RE yourself or find a handy IT person to help. Get the files into a single directory and open the input dialog as shown above. Determine which camera configuration - PANCAM or AUPE - produced the images and set the Camera option accordingly. These two setups use different filter sets, and will translate filter positions into different filter wavelengths and names. Click the \"Get Directory\" button. In the new dialog, select a directory containing the ENVI .hdr files and their accompanying .dat files, and click \"Select Folder\". A lot of files will appear in the Files box. Double-click images to preview them as a single channel. If they are dark, select an appropriate multiplier and double-click again. Click in image checkboxes to select them; selected images will be combined into a single multispectral mage. Close the dialog when you have the selected images you want. It might be a good idea to create an input node to examine the resulting multispectral image.","title":"Loading a \"multiband\" image"},{"location":"gettingstarted/tutold/#pds4-products","text":"This is very much work in progress - please contact the developers if you need this functionality soon.","title":"PDS4 products"},{"location":"gettingstarted/tutorial/","text":"A first tutorial This page is a gentle introduction to PCOT's user interface elements, and will walk you through loading image data and performing some simple manipulations. There is also a video guide - it may be a little out of date, because development is progressing quickly but the basics will be the same. In this document, text written with a highlight like this is an instruction to follow. Be aware that this is an early version and there are no doubt a lot of serious problems! Also, the software is changing very quickly, so it may not look exactly as it does in these pages. Preparation PCOT is primarily designed to work with multispectral images. For this tutorial we have prepared an image and packaged it with the rest of the system, but you will need to uncompress it: it's an ENVI image and these can be rather large. It can be found in the exampleData directory as obj4-sand-subset.zip . Uncompress this image somewhere (perhaps into the same directory) producing two files: 1.hdr (the header file) and 1.dat (the data itself). This is a 256x256 image with taken using AUPE (Aberystwyth University Pancam Emulator), with 11 bands of data. It is a subset of a larger image used in testing, and is shown below as an RGB image: Figure: The example image (640nm, 550nm and 440nm bands). Click on image to expand. Now you have this image you can start PCOT . Introduction to the user interface The image below shows the PCOT interface when the program has just been started, with text in red describing each part. Figure: The PCOT user interface. Click on image to expand. The window is divided into several areas: At the top, the input buttons each open one of PCOT's input windows. These describe how information is read into PCOT. Below this and to the left is the node area , which will be empty on startup. Double-clicking on a node in the graph (see below) will open a detailed view for that node. You can have several views open in this area looking at different nodes, and you can undock by double-clicking on the tab at the top of the view. To the right of the node area is the graph . This shows the nodes in the document and their connections. A new document always has an input 0 node, which brings input 0 into the graph. To the right of the graph is the palette , which consists of a number of sections which can be expanded or hidden. Clicking on a button in the palette will add a node of the given type to the graph. At the bottom is the log area , the status bar , and a set of global controls . Working with graph nodes Each node is shown as a box with input connections on the top and output connections on the bottom. To illustrate this, consider the following graph you've seen before: Figure: R671_438 inset into source image RGB representation. Click on image to expand. Node boxes Looking at a pair of nodes in the graph: Figure: A pair of nodes. Click on image to expand. The inputs and outputs are coloured by type: blue is perhaps the most common and indicates an image. Red means any kind of data can be connected here, and generally only appears on inputs. Inputs and outputs can also have names (but don't always). Each node has a help box - double-clicking on this pops up help for the node. Each node has a name - usually this is the node's type, but in the case of the expr node it is the expression being calculated (see below). Nodes can have a \"display text\" in blue. These show extra data, such as the output of a calculation in an expr node or the annotation given to a region of interest. Currently nodes without a display text also show the number of times they have been calculated as a debugging aid. Selecting and opening graph nodes You can select a node in the graph by clicking on it, or by dragging a box which intersects the node. A selected node will be tinted blue. You can open a node for viewing or editing in the node area by double-clicking. If you double-click on the input 0 node, it will turn a dark cyan colour and a view of the node will appear on the left, along with a tab to select that view when multiple node views are open. You can close an open node by clicking on the cross in the node's tab. If you open the input 0 node in a new document you will see this: Figure: PCOT with an open but unused input node. Click on image to expand. The node is cyan because the current view's node is tinted green, and the node is already tinted blue because it is selected in the graph. There are two selection models: Multiple nodes can be selected in the graph; these are tinted blue. A single node's view can be open and currently being edited, this node is tinted green in the graph. Nodes which are both being edited and are selected are tinted cyan. The view for this particular node - input 0 - shows what external input is currently being read into PCOT on the input numbered 0. In this case it shows \"None\" because there is currently no input. Creating a new node This can be done in two ways: Clicking on a node type in the palette will create a new instance of that type in the graph, hopefully somewhere sensible. Right-clicking a node type from the palette and dragging onto the graph will create a node where you wish. \"Undocking\" a node's view Sometimes it is useful to see several node views at the same time. Double-clicking on the tab, where the name of the node type appears, will make the view open in a separate window. This can be done for several views, and the windows can be rearranged as you wish. Closing the window will \"redock\" the view so it appears in the node area as before. Constant and comment nodes These two nodes are special - the boxes in the graph have text fields which can be edited. For constant nodes, the value in the box will be the numeric output of the node. This node has no view, and double-clicking has no effect. For comment nodes, the value in the box is a text comment that can help other users navigate the graph. Once edited, the box can be resized by clicking and dragging its bottom-right corner. The text will flow to fit the box. Double-clicking on a comment node opens a view which provides an alternative way of editing the text, as well as changing the font size and colours. It's also the only way of getting blank lines into the text, since hitting the \"enter\" key will stop editing when you are editing the node text directly in its box. Figure: Comment and constant nodes. Click on image to expand. Loading an image The purpose of the input 0 node is to bring the an input into the graph for manipulation. As noted elsewhere , the graph is separate from the inputs. This makes it easier to run different graphs on the same inputs, or the same graph on different inputs. Clicking on the \"Input 0\" button at the top of the window will open a window to let us change the input, which looks like this: Figure: An input window, with the null input selected. Click on image to expand. Each input supports a number of input methods , only one of which can be active at a time. By default, the method is null , meaning that no data is loaded into that input. PCOT can support several types of multispectral data file: we will load a small ENVI image we uncompressed earlier. Click on ENVI , and the window will show that input's ENVI method settings and select the ENVI method as being active (see below). In the ENVI input window, Click on Select Directory to open a dialog Choose the directory where you stored the 1.hdr and 1.dat files you extracted earlier Double-click on the 1.hdr file You should see something like this: Figure: An open ENVI input. Click on image to expand. The Canvas The right-hand side is a common component in PCOT known as a canvas which will show the image selected. The canvas lets you modify how the RGB channels are mapped using the three combo boxes. Each box gives the band number, the input number and the wavelength. For example, the red channel is coming from band 2, which is from input 0, and has a wavelength of 640nm. If the RGB method is used to load an ordinary RGB image (e.g. from a PNG), the wavelength will not be shown. At the bottom of the image itself are three source indicators : these show what bands within which inputs were used to generate the canvas image. They should show something like [0:640][0:540][0:480] indicating that the red, green and blue channels come from the 640nm, 540nm and 480nm bands in input 0's image. The source indicator may get quite complex - it shows all inputs that have had an effect on each channel of the image. The indicators may be different because the source display is currently under development. The canvas is quite complicated because it does a lot of things, such as displaying spectra for each pixel, normalisation settings, and displaying data quality bits. More information can be found here , but for now: Click the spectrum button to open a spectrum view, and move the cursor over the image to show the spectrum at different pixels. You may need to resize the window, or drag the \"double bar\" which separates the image from the spectrum. Manipulating an image: obtaining a spectral parameter map Let's perform a simple manipulation on an image. Here we will generate a spectral parameter map from two of the image bands. This parameter is R671_438 , the ratio between the 671nm and 438nm bands, which indicates the presence of ferric minerals 1 . Start PCOT and load an image into input 0 as before , by clicking on the Input 0 button, selecting RGB and double-clicking on an image file. Double-click on the input 0 node in the graph - instances of this node bring input 0 into the graph. Open the maths section in the palette by clicking on it. Click on expr in the maths section to create an expression evaluation node. Drag a connection from the output of input 0 to the a input of expr . Double-click on the expr node to open its view for editing - this will show an empty output because our mathematical expression is empty. We now have an input feeding into an expression evaluator, which we can edit. First, let's just see one band. Click in the expr view's expression box : this is the box which says \"Enter expression here...\". Enter the string a$671 This says \"select the band with centre wavelength 671nm from input a \". Press \"Run\" in the node's view . You should now see a monochrome image in the node's canvas: the result of the calculation, containing only that single band. Now change the expression to a$671/a$438 and press \"Run\" to see the result, which should be this: Figure: R671_438 parameter from an multispectral image. Click on image to expand. Note that the source indicators in the bottom left of the image are now displaying [0:438&0:671] [0:438&0:671] [0:438&0:671] This indicates that all three RGB channels shown in the canvas are getting their data from the 438 and 671 bands of input 0. Disconnecting nodes and node error states Disconnect the input node by dragging the arrowhead from the a box and releasing it in empty space. This will cause an error: Figure: A node in an error state. Click on image to expand. The EXPR is the kind of error - EXPR is an error in expression parsing/execution. More details can be seen in the log window, which in this case reads Error in expression: variable's input is not connected This is because there is no longer an image feeding into the expr node's input for variable a . Now reconnect the input node to the expr node to fix the error. Adding a region of interest It's a little hard to see what's going on, so we will add a region of interest. This will make the expr node treat the operation differently - only the area inside the rectangle will be norm(a$671/a$438) . Everywhere else in the image, the output will come from the left-hand side of the expression (the 671nm channel). The rules for ROIs are explained more fully on this page . Open the regions section of the palette and add a rect node between the input and expr nodes (the latter of which will be labelled with its expression). Nothing will change, because the rectangle has not been set. Edit the rect node and draw a rectangle by clicking and dragging on the canvas. Set the scale to 5 pixels and type \"671_438\" in the Annotation box , something like this: Figure: R671_438 with a rectangle. Click on image to expand. Now the expr node shows this - only the rectangle has the 671/438 parameter, while the rest of the image shows the 671nm band. To see this, click on the expr tab : Figure: R671_438 with a rectangle. Click on image to expand. The image is dark, because the values of the parameter shown in the rectangle are much greater than those of the 671nm band. It can be hard to adjust the rectangle when you can't see what's happening in the final image - try undocking the rect node and dragging it to a different part of the screen by double-clicking on the tab, so you can edit the rectangle while the expr output is visible. Dock it again by closing the undocked window . To make things more visible still, open the data section and add a gradient node after the expr node and view it , clicking making sure Show ROIs is enabled on its canvas (ROIs are typically retained on derived images): Figure: R671_438 with a rectangle and gradient. Click on image to expand. There's not much to see because of the nature of the image, unfortunately! Note that there is a \"legend\" at top left, and this can be edited and placed by clicking on the Legend button : Figure: R671_438 with a better gradient legend. Click on image to expand. The colour palette for the gradient, incidentally, is the \"viridis\" palette which is helpful for people with certain kinds of colour blindness 2 . Other presets are available by clicking on the Load Preset button, or you can edit the gradient by hand: double-click to add or remove a point, drag to move a point. It is possible to \"inset\" the gradient into the RGB representation used in the input node by passing that to the insetinto input of the gradient: Figure: R671_438 inset into source image RGB representation. Click on image to expand. If you switch to the Legend part of the gradient node and select Left margin for the Legend item , when you export the image with the canvas' Export Image button the legend will be in the margin: Figure: Exported R671_438 (svg). Click on image to expand. Allender, E. J., C. R. Cousins, M. D. Gunn, and C. M. Caudill. \"Multiscale and multispectral characterization of mineralogy with the ExoMars 2022 rover remote sensing payload.\" Earth and Space Science 7, no. 4 (2020): e2019EA000692. \u21a9 Simon Garnier, Noam Ross, Robert Rudis, Ant\u00f4nio P. Camargo, Marco Sciaini, and C\u00e9dric Scherer (2021). viridis(Lite) - Colorblind-Friendly Color Maps for R. viridis package version 0.6.2. \u21a9","title":"Tutorial"},{"location":"gettingstarted/tutorial/#a-first-tutorial","text":"This page is a gentle introduction to PCOT's user interface elements, and will walk you through loading image data and performing some simple manipulations. There is also a video guide - it may be a little out of date, because development is progressing quickly but the basics will be the same. In this document, text written with a highlight like this is an instruction to follow. Be aware that this is an early version and there are no doubt a lot of serious problems! Also, the software is changing very quickly, so it may not look exactly as it does in these pages.","title":"A first tutorial"},{"location":"gettingstarted/tutorial/#preparation","text":"PCOT is primarily designed to work with multispectral images. For this tutorial we have prepared an image and packaged it with the rest of the system, but you will need to uncompress it: it's an ENVI image and these can be rather large. It can be found in the exampleData directory as obj4-sand-subset.zip . Uncompress this image somewhere (perhaps into the same directory) producing two files: 1.hdr (the header file) and 1.dat (the data itself). This is a 256x256 image with taken using AUPE (Aberystwyth University Pancam Emulator), with 11 bands of data. It is a subset of a larger image used in testing, and is shown below as an RGB image: Figure: The example image (640nm, 550nm and 440nm bands). Click on image to expand. Now you have this image you can start PCOT .","title":"Preparation"},{"location":"gettingstarted/tutorial/#introduction-to-the-user-interface","text":"The image below shows the PCOT interface when the program has just been started, with text in red describing each part. Figure: The PCOT user interface. Click on image to expand. The window is divided into several areas: At the top, the input buttons each open one of PCOT's input windows. These describe how information is read into PCOT. Below this and to the left is the node area , which will be empty on startup. Double-clicking on a node in the graph (see below) will open a detailed view for that node. You can have several views open in this area looking at different nodes, and you can undock by double-clicking on the tab at the top of the view. To the right of the node area is the graph . This shows the nodes in the document and their connections. A new document always has an input 0 node, which brings input 0 into the graph. To the right of the graph is the palette , which consists of a number of sections which can be expanded or hidden. Clicking on a button in the palette will add a node of the given type to the graph. At the bottom is the log area , the status bar , and a set of global controls .","title":"Introduction to the user interface"},{"location":"gettingstarted/tutorial/#working-with-graph-nodes","text":"Each node is shown as a box with input connections on the top and output connections on the bottom. To illustrate this, consider the following graph you've seen before: Figure: R671_438 inset into source image RGB representation. Click on image to expand.","title":"Working with graph nodes"},{"location":"gettingstarted/tutorial/#node-boxes","text":"Looking at a pair of nodes in the graph: Figure: A pair of nodes. Click on image to expand. The inputs and outputs are coloured by type: blue is perhaps the most common and indicates an image. Red means any kind of data can be connected here, and generally only appears on inputs. Inputs and outputs can also have names (but don't always). Each node has a help box - double-clicking on this pops up help for the node. Each node has a name - usually this is the node's type, but in the case of the expr node it is the expression being calculated (see below). Nodes can have a \"display text\" in blue. These show extra data, such as the output of a calculation in an expr node or the annotation given to a region of interest. Currently nodes without a display text also show the number of times they have been calculated as a debugging aid.","title":"Node boxes"},{"location":"gettingstarted/tutorial/#selecting-and-opening-graph-nodes","text":"You can select a node in the graph by clicking on it, or by dragging a box which intersects the node. A selected node will be tinted blue. You can open a node for viewing or editing in the node area by double-clicking. If you double-click on the input 0 node, it will turn a dark cyan colour and a view of the node will appear on the left, along with a tab to select that view when multiple node views are open. You can close an open node by clicking on the cross in the node's tab. If you open the input 0 node in a new document you will see this: Figure: PCOT with an open but unused input node. Click on image to expand. The node is cyan because the current view's node is tinted green, and the node is already tinted blue because it is selected in the graph. There are two selection models: Multiple nodes can be selected in the graph; these are tinted blue. A single node's view can be open and currently being edited, this node is tinted green in the graph. Nodes which are both being edited and are selected are tinted cyan. The view for this particular node - input 0 - shows what external input is currently being read into PCOT on the input numbered 0. In this case it shows \"None\" because there is currently no input.","title":"Selecting and opening graph nodes"},{"location":"gettingstarted/tutorial/#creating-a-new-node","text":"This can be done in two ways: Clicking on a node type in the palette will create a new instance of that type in the graph, hopefully somewhere sensible. Right-clicking a node type from the palette and dragging onto the graph will create a node where you wish.","title":"Creating a new node"},{"location":"gettingstarted/tutorial/#undocking-a-nodes-view","text":"Sometimes it is useful to see several node views at the same time. Double-clicking on the tab, where the name of the node type appears, will make the view open in a separate window. This can be done for several views, and the windows can be rearranged as you wish. Closing the window will \"redock\" the view so it appears in the node area as before.","title":"\"Undocking\" a node's view"},{"location":"gettingstarted/tutorial/#constant-and-comment-nodes","text":"These two nodes are special - the boxes in the graph have text fields which can be edited. For constant nodes, the value in the box will be the numeric output of the node. This node has no view, and double-clicking has no effect. For comment nodes, the value in the box is a text comment that can help other users navigate the graph. Once edited, the box can be resized by clicking and dragging its bottom-right corner. The text will flow to fit the box. Double-clicking on a comment node opens a view which provides an alternative way of editing the text, as well as changing the font size and colours. It's also the only way of getting blank lines into the text, since hitting the \"enter\" key will stop editing when you are editing the node text directly in its box. Figure: Comment and constant nodes. Click on image to expand.","title":"Constant and comment nodes"},{"location":"gettingstarted/tutorial/#loading-an-image","text":"The purpose of the input 0 node is to bring the an input into the graph for manipulation. As noted elsewhere , the graph is separate from the inputs. This makes it easier to run different graphs on the same inputs, or the same graph on different inputs. Clicking on the \"Input 0\" button at the top of the window will open a window to let us change the input, which looks like this: Figure: An input window, with the null input selected. Click on image to expand. Each input supports a number of input methods , only one of which can be active at a time. By default, the method is null , meaning that no data is loaded into that input. PCOT can support several types of multispectral data file: we will load a small ENVI image we uncompressed earlier. Click on ENVI , and the window will show that input's ENVI method settings and select the ENVI method as being active (see below). In the ENVI input window, Click on Select Directory to open a dialog Choose the directory where you stored the 1.hdr and 1.dat files you extracted earlier Double-click on the 1.hdr file You should see something like this: Figure: An open ENVI input. Click on image to expand.","title":"Loading an image"},{"location":"gettingstarted/tutorial/#the-canvas","text":"The right-hand side is a common component in PCOT known as a canvas which will show the image selected. The canvas lets you modify how the RGB channels are mapped using the three combo boxes. Each box gives the band number, the input number and the wavelength. For example, the red channel is coming from band 2, which is from input 0, and has a wavelength of 640nm. If the RGB method is used to load an ordinary RGB image (e.g. from a PNG), the wavelength will not be shown. At the bottom of the image itself are three source indicators : these show what bands within which inputs were used to generate the canvas image. They should show something like [0:640][0:540][0:480] indicating that the red, green and blue channels come from the 640nm, 540nm and 480nm bands in input 0's image. The source indicator may get quite complex - it shows all inputs that have had an effect on each channel of the image. The indicators may be different because the source display is currently under development. The canvas is quite complicated because it does a lot of things, such as displaying spectra for each pixel, normalisation settings, and displaying data quality bits. More information can be found here , but for now: Click the spectrum button to open a spectrum view, and move the cursor over the image to show the spectrum at different pixels. You may need to resize the window, or drag the \"double bar\" which separates the image from the spectrum.","title":"The Canvas"},{"location":"gettingstarted/tutorial/#manipulating-an-image-obtaining-a-spectral-parameter-map","text":"Let's perform a simple manipulation on an image. Here we will generate a spectral parameter map from two of the image bands. This parameter is R671_438 , the ratio between the 671nm and 438nm bands, which indicates the presence of ferric minerals 1 . Start PCOT and load an image into input 0 as before , by clicking on the Input 0 button, selecting RGB and double-clicking on an image file. Double-click on the input 0 node in the graph - instances of this node bring input 0 into the graph. Open the maths section in the palette by clicking on it. Click on expr in the maths section to create an expression evaluation node. Drag a connection from the output of input 0 to the a input of expr . Double-click on the expr node to open its view for editing - this will show an empty output because our mathematical expression is empty. We now have an input feeding into an expression evaluator, which we can edit. First, let's just see one band. Click in the expr view's expression box : this is the box which says \"Enter expression here...\". Enter the string a$671 This says \"select the band with centre wavelength 671nm from input a \". Press \"Run\" in the node's view . You should now see a monochrome image in the node's canvas: the result of the calculation, containing only that single band. Now change the expression to a$671/a$438 and press \"Run\" to see the result, which should be this: Figure: R671_438 parameter from an multispectral image. Click on image to expand. Note that the source indicators in the bottom left of the image are now displaying [0:438&0:671] [0:438&0:671] [0:438&0:671] This indicates that all three RGB channels shown in the canvas are getting their data from the 438 and 671 bands of input 0.","title":"Manipulating an image: obtaining a spectral parameter map"},{"location":"gettingstarted/tutorial/#disconnecting-nodes-and-node-error-states","text":"Disconnect the input node by dragging the arrowhead from the a box and releasing it in empty space. This will cause an error: Figure: A node in an error state. Click on image to expand. The EXPR is the kind of error - EXPR is an error in expression parsing/execution. More details can be seen in the log window, which in this case reads Error in expression: variable's input is not connected This is because there is no longer an image feeding into the expr node's input for variable a . Now reconnect the input node to the expr node to fix the error.","title":"Disconnecting nodes and node error states"},{"location":"gettingstarted/tutorial/#adding-a-region-of-interest","text":"It's a little hard to see what's going on, so we will add a region of interest. This will make the expr node treat the operation differently - only the area inside the rectangle will be norm(a$671/a$438) . Everywhere else in the image, the output will come from the left-hand side of the expression (the 671nm channel). The rules for ROIs are explained more fully on this page . Open the regions section of the palette and add a rect node between the input and expr nodes (the latter of which will be labelled with its expression). Nothing will change, because the rectangle has not been set. Edit the rect node and draw a rectangle by clicking and dragging on the canvas. Set the scale to 5 pixels and type \"671_438\" in the Annotation box , something like this: Figure: R671_438 with a rectangle. Click on image to expand. Now the expr node shows this - only the rectangle has the 671/438 parameter, while the rest of the image shows the 671nm band. To see this, click on the expr tab : Figure: R671_438 with a rectangle. Click on image to expand. The image is dark, because the values of the parameter shown in the rectangle are much greater than those of the 671nm band. It can be hard to adjust the rectangle when you can't see what's happening in the final image - try undocking the rect node and dragging it to a different part of the screen by double-clicking on the tab, so you can edit the rectangle while the expr output is visible. Dock it again by closing the undocked window . To make things more visible still, open the data section and add a gradient node after the expr node and view it , clicking making sure Show ROIs is enabled on its canvas (ROIs are typically retained on derived images): Figure: R671_438 with a rectangle and gradient. Click on image to expand. There's not much to see because of the nature of the image, unfortunately! Note that there is a \"legend\" at top left, and this can be edited and placed by clicking on the Legend button : Figure: R671_438 with a better gradient legend. Click on image to expand. The colour palette for the gradient, incidentally, is the \"viridis\" palette which is helpful for people with certain kinds of colour blindness 2 . Other presets are available by clicking on the Load Preset button, or you can edit the gradient by hand: double-click to add or remove a point, drag to move a point. It is possible to \"inset\" the gradient into the RGB representation used in the input node by passing that to the insetinto input of the gradient: Figure: R671_438 inset into source image RGB representation. Click on image to expand. If you switch to the Legend part of the gradient node and select Left margin for the Legend item , when you export the image with the canvas' Export Image button the legend will be in the margin: Figure: Exported R671_438 (svg). Click on image to expand. Allender, E. J., C. R. Cousins, M. D. Gunn, and C. M. Caudill. \"Multiscale and multispectral characterization of mineralogy with the ExoMars 2022 rover remote sensing payload.\" Earth and Space Science 7, no. 4 (2020): e2019EA000692. \u21a9 Simon Garnier, Noam Ross, Robert Rudis, Ant\u00f4nio P. Camargo, Marco Sciaini, and C\u00e9dric Scherer (2021). viridis(Lite) - Colorblind-Friendly Color Maps for R. viridis package version 0.6.2. \u21a9","title":"Adding a region of interest"},{"location":"gettingstarted/tutorial2/","text":"Tutorial part 2 Mathematical operations Performing operations on parts of an image Calculating a spectrum","title":"Tutorial part 2"},{"location":"gettingstarted/tutorial2/#tutorial-part-2","text":"","title":"Tutorial part 2"},{"location":"gettingstarted/tutorial2/#mathematical-operations","text":"","title":"Mathematical operations"},{"location":"gettingstarted/tutorial2/#performing-operations-on-parts-of-an-image","text":"","title":"Performing operations on parts of an image"},{"location":"gettingstarted/tutorial2/#calculating-a-spectrum","text":"","title":"Calculating a spectrum"},{"location":"userguide/","text":"Overview These pages are a reference guide to the PCOT application. If you want to read about using PCOT as a Python library, documentation is available in the Developer's Guide . At the moment, this consists of in-depth pages on particular aspects of PCOT. For introductory information see the Getting Started section, which will tell you how to install and run PCOT and give you a tutorial covering the basics. Contents Operating principles Global controls on the PCOT UI. The canvas and its optional views Special topics Using the multifile input to read multispectral data from BMP, PNG etc. Using the expr node to perform mathematical operations Autodocs Automatically generated documentation for nodes, expression functions and expression properties.","title":"Overview"},{"location":"userguide/#overview","text":"These pages are a reference guide to the PCOT application. If you want to read about using PCOT as a Python library, documentation is available in the Developer's Guide . At the moment, this consists of in-depth pages on particular aspects of PCOT. For introductory information see the Getting Started section, which will tell you how to install and run PCOT and give you a tutorial covering the basics.","title":"Overview"},{"location":"userguide/#contents","text":"Operating principles Global controls on the PCOT UI. The canvas and its optional views","title":"Contents"},{"location":"userguide/#special-topics","text":"Using the multifile input to read multispectral data from BMP, PNG etc. Using the expr node to perform mathematical operations","title":"Special topics"},{"location":"userguide/#autodocs","text":"Automatically generated documentation for nodes, expression functions and expression properties.","title":"Autodocs"},{"location":"userguide/canvas/","text":"The Canvas and its optional views Most nodes and inputs use a canvas to display some bands of an image as RGB. This will take up a large proportion of their view - in some cases all of it. It is worth discussing in some detail. Another is shown below as the entire control area for an actual input node, which brings one of the four inputs into the graph. Figure: An open *input 0* node. Click on image to expand. You can pan the canvas using the scroll bars at the edge, and zoom with the mouse wheel. The button at bottom right will reset to show the entire image. To the left of the canvas image itself is the canvas control area. This has quite a lot in it, so is scrollable with collapsible sections, rather like the palette. At the top - and not collapsible - are three combo boxes which determine the image mapping : how the bands within the (probably) multispectral image map onto RGB channels for viewing. Each band in the combo box shows the input number, a colon, and typically the name, position or wavelength of the band. Exactly what is shown depends on the image being loaded and the Caption global control . The Guess RGB button tries to guess appropriate channels for the RGB canvas image. Data section This is the first of the collapsible sections. Show ROIs will mark any regions of interest the image has - these are added using nodes like rect (for a rectangle) and are carried forward through subsequent nodes (where appropriate), delimiting the area upon which calculations are performed. They also control the regions used for calculating spectra. Normally an ROI is only shown in the node which adds it. Show spectrum opens a side pane, and dragging the cursor across the image will plot the spectrum of the pixel under the cursor in this pane. If no filter wavelengths are available, a list of the values for each band is shown. The show spectrum pane looks like this: The screenshot isn't showing the cursor, unfortunately, but the spectrum is for the pixel under the cursor. Figure: The \"show spectrum\" pane on an input node's canvas. Click on image to expand. When a spectrum view is opened the image pane can be tiny - to fix this you can resize the PCOT window (or undocked node window), or drag the separator between the image and the spectrum (the two vertical bars). Each dot is shown with an approximation of its wavelength colour (using Dan Bruton's algorithm ) or black if the wavelength is not visible. Export image saves the RGB-mapped image as a PDF, SVG or PNG. If a gradient has been plotted using the gradient node, it may add a legend to the image. Cursor coordinates are shown next, followed by number of pixels and ROIs in the image and the image dimensions (width x height x bands). The hide DQ disables the data quality overlays and hides their sections - data quality can be slow to draw. Normalisation section Nominally, the canvas channels are 0-1 with 0 being zero intensity and 1 being full intensity (so RGB 1,1,1 is white). Values outside the 0-1 range are clamped. However, this can be changed. The norm box selects the normalisation range: to all bands means that the normalisation range of each band for each pixel is the minimum and maximum of all the bands as a whole. to RGB means that the normalisation range is taken from the channels mapped to RGB in the canvas. independent means that each band is normalised independently, to its own range. none means no normalisation is done. If to cropped area is on, the range used for normalisation is taken from the portion of the image visible in the canvas. Data quality layers Each image has a set of data quality bits and an uncertainty value associated with each pixel of each band. Viewing this can be challenging. We make three \"layers\" of DQ data available: each works the same way: SRC specifies which band we are viewing the DQ for. If max is specified, the maximum uncertainty across all bands is used. If sum , then the sum of the uncertainties is used. If a DQ bit is being shown, the intersection of those bits across all bands is used for both max and sum . DATA specifies what data is being shown: NONE specifies that the DQ layer is inactive. BIT:name options specify a particular DQ bit. These are subject to change, but are likely to include nodata , nounc (no uncertainty data), sat (saturated). UNC specifies that the uncertainty data should be shown. UNC>THRESH specifies that the layer should be full intensity for pixels where the uncertainty is above a threshold (see below). UNCTHRESH specifies that the layer should be full intensity for pixels where the uncertainty is above a threshold (see below). UNCL|R)WAC(?P[0-9][0-9]).* This means: .* matches any number of any character, so there could be anything at the start of the filename L|R means \"either L or R \" - but we have specified a \"named match\" with brackets and the ?P<..> notation: (?PL|R) . This means \"either L or R and store the result under the name lens \". WAC means we must then have the sequence of letters WAC (?P[0-9][0-9]) means we must now match two digits ( [0-9] ) and store them as n The final .* means that there can now be any number of any character again - so there could be anything at the end of the filename. The idea is that a filename like /home/jim/files/DogBiscuitLWAC02Fish.jpg will be matched, and will result in L being stored as lens and 02 being stored as n . Named matches and how they are used Only one of the following should be true (e.g. you can't use name and n together): lens and n : if these are found, they are joined together to form a filter position which is looked up in the filter set (by the position column). The idea is that lens indicates either the left or right camera and n identifies a filter. They're separate because many early files used names like LWAC02 or LWideAngle02 , in which the two elements were separate. name : if this is found, it is used to match a filter using the name column cwl : if this is found, it is used to match a filter using the cwl (wavelength) column If you need assistance, or this isn't flexible enough, contact us. Reading raw binary files Data is often provided \"as is\" from the camera, in a raw binary format. Reading these files requires a little more information in advance: What the numeric format of the data is (e.g. 16-bit unsigned integer) How big the image is in pixels Whether there is a header at the start which should be skipped and how big it is (the \"offset\") Whether the image needs to be rotated and/or flipped Whether the data is \"big-endian\" or \"little-endian.\" These can be set by clicking the \"raw loader settings\" dialog. For images from AUPE, the settings are: 16-bit unsigned integer 1024x1024 48 byte offset Rotate 90 degrees Big-endian data Presets You can save and load these values - and most other settings for multifile input, such as the pattern and filter - using the \"Presets\" button. Presets are currently stored in your home user directory in a file called MFPresets.json . Users can easily copy this file from other users.","title":"Reading images from multiple files"},{"location":"userguide/multifile/#the-multifile-input-method","text":"Documentation for the other methods is forthcoming. The regex aspect of multifile is complicated, so documenting it was a priority. The multifile input method allows multiple monochrome files of \"standard\" types like PNG and BMP to be imported as multispectral images. It can also read raw binary files. Each file is flattened to greyscale in the process (if it is RGB) by finding the mean of the three channels for each pixel.","title":"The Multifile input method"},{"location":"userguide/multifile/#filter-csv-files","text":"In order to analyse the images, PCOT needs to be able to find out which filter was used for each image. This is done by storing a set of filters in a CSV file and using parts of the image files' names to work out which filter is used. Two files are available by default. These are PANCAM (for the actual ExoMars camera) and AUPE (Aberystwyth PANCAM Emulator). These can be found in the src/pcot/assets/ directory. Here's an example of part of such a file: cwl,fwhm,transmission,position,name 570,12,0.989,L01,G04 530,15,0.957,L02,G03 610,10,0.956,L03,G05 In each line: cwl is the centre wavelength fwhm is the bandwidth as as full width at half maximum transmission is how much energy the filter lets through at the CWL position is the position of the filter in the filter wheel, typically as a camera letter (e.g. R or L) and an index number name is the name of the filter. If you need to add a new filter set, edit the .pcot.ini file in your home directory and a [filters] block if one does not exist. To this block, add a line like this: [filters] myfilterset=filename For example [filters] JCFCAM=c:/users/jim/jimfilters.csv This new set, called JCFCAM, will be loaded when PCOT is started.","title":"Filter CSV files"},{"location":"userguide/multifile/#setting-a-file-pattern","text":"PCOT needs to be able to work out which filter was used to capture an image. This is done by extracting the filter name or position from the filename using a regular expression (or regex ). If you have some experience with regular expressions (or access to someone with this experience), it will help immensely . Regular expressions describe patterns which texts might match. For example, the regex c[a-z]t will match any three-letter string starting with c and ending with t : it's c , followed by any character between a and z , followed by t . (Beginner's guide)[ https://www.regular-expressions.info/index.html ] (Useful 'playground' to try out expressions)[ https://regex101.com/ ] The default pattern looks something like this: .*(?PL|R)WAC(?P[0-9][0-9]).* This means: .* matches any number of any character, so there could be anything at the start of the filename L|R means \"either L or R \" - but we have specified a \"named match\" with brackets and the ?P<..> notation: (?PL|R) . This means \"either L or R and store the result under the name lens \". WAC means we must then have the sequence of letters WAC (?P[0-9][0-9]) means we must now match two digits ( [0-9] ) and store them as n The final .* means that there can now be any number of any character again - so there could be anything at the end of the filename. The idea is that a filename like /home/jim/files/DogBiscuitLWAC02Fish.jpg will be matched, and will result in L being stored as lens and 02 being stored as n .","title":"Setting a file pattern"},{"location":"userguide/multifile/#named-matches-and-how-they-are-used","text":"Only one of the following should be true (e.g. you can't use name and n together): lens and n : if these are found, they are joined together to form a filter position which is looked up in the filter set (by the position column). The idea is that lens indicates either the left or right camera and n identifies a filter. They're separate because many early files used names like LWAC02 or LWideAngle02 , in which the two elements were separate. name : if this is found, it is used to match a filter using the name column cwl : if this is found, it is used to match a filter using the cwl (wavelength) column If you need assistance, or this isn't flexible enough, contact us.","title":"Named matches and how they are used"},{"location":"userguide/multifile/#reading-raw-binary-files","text":"Data is often provided \"as is\" from the camera, in a raw binary format. Reading these files requires a little more information in advance: What the numeric format of the data is (e.g. 16-bit unsigned integer) How big the image is in pixels Whether there is a header at the start which should be skipped and how big it is (the \"offset\") Whether the image needs to be rotated and/or flipped Whether the data is \"big-endian\" or \"little-endian.\" These can be set by clicking the \"raw loader settings\" dialog. For images from AUPE, the settings are: 16-bit unsigned integer 1024x1024 48 byte offset Rotate 90 degrees Big-endian data","title":"Reading raw binary files"},{"location":"userguide/multifile/#presets","text":"You can save and load these values - and most other settings for multifile input, such as the pattern and filter - using the \"Presets\" button. Presets are currently stored in your home user directory in a file called MFPresets.json . Users can easily copy this file from other users.","title":"Presets"},{"location":"userguide/principles/","text":"Operating principles PCOT nodes need to follow a set of rules to ensure that the information they process is handled consistently. This page describes these rules, most of which apply to image data. Source handling rules Each datum handled by PCOT has a \"source set\" describing where that datum ultimately comes from. Sources vary: in images, each band in an input image carries a source datum describing where it comes from. For a PDS4 source it could be a LIDVID, or it could simply be a filename (although ideally it should have some archive indexing data). Because data can be combined in various ways, each datum could have multiple sources. Image data in PCOT have a separate source set for each band. The rules for sources are simple: Every datum has a source set. This may be a null source if the datum is generated internally (consider the output from constant nodes). In the case of an image, this source set is actually a separate source set for each band, but may still be considered as a single source set for some operations (since the union of the band sets is available). If a datum, or band in a multi-band image, is constructed from data from more than once source set, the resulting datum or band consists of the union of the sets. As an example, consider the rather contrived example below. Here, each datum is marked as a black circle below which is a description of the the sources - there's more explanation of this below. image inputs are in yellow scalar inputs are in blue nodes are white rectangles expr nodes (which calculate mathematical expressions) are marked as such with the expression being the main text Figure: An example graph.. Click on image to expand. We have three inputs into the graph: Input 0 has an image with three bands centered on 640nm, 540nm and 440nm. Input 1 has a different image with four bands. Input 2 has a numeric value of 0.2 (perhaps a housekeeping datum of some kind). These data are then combined in various ways: Input 1 is converted to a single greyscale band and multiplied by input 2 (the scalar 0.2) The result of this operation is then added to the 540nm band of input 0. What do the sources look like for each datum? Datum A is an image, and as such it has one source set per band. Each source set consists of a single source, with details of input and filter wavelength. So here, the sources for A could be written as [ {0:640}, {0:540}, {0:440} ] That is, a list of three sets, each of which contains a single source which I've written in the form input:wavelength . Datum B is the extracted 540nm band of input A, so its sources are: [ {0:540} ] Datum C is another multiband image, this time from input 1: [ {1:780}, {1:640}, {1:540}, {1:440} ] Datum D is the greyscale conversion of Datum C above. This creates a single-band image, but that band combines all the bands of datum C. This results in a single source set containing all the bands: [ {1:780, 1:640, 1:540, 1:440} ] Datum E is the only non-image source. I will denote it here as just \"2\" indicating that it is input 2. It is worth noting here that sources may contain a lot of extra data describing exactly where they come from. For example, this input may come from PDS4, in which case at least the LIDVID will be included. But for now this is simply {2} Note that this is not shown as a list because it is not a multiband source. Datum F multiplies each band in datum D by the scalar E. This means that the source for E must be added to the source set for each band. There is only one band at this point because of the greyscale conversion: [ {1:780, 1:640, 1:540, 1:440, 2} ] Finally, we add the single-band images B and F together, resulting in another single-band image. This addition is done bandwise. There is only one band, so the result is: [ {1:780, 1:640, 1:540, 1:440, 2, 0:540} ] ROI rules Images may contain regions of interest. If this is the case, then any operation should only be performed on the region of interest if possible. However, the rest of the image should be passed through unchanged to provide context - it is always possible to use a croproi node before the operation if cropping is required. This rule has the following practical outcomes, in which images are denoted by capitals A, B, \\dots A, B, \\dots and scalars are denoted by lowercase x, y. \\dots x, y. \\dots So: In binary operations on an image and a scalar such as x+A x+A or A+x A+x , the operation is only performed on the region covered by the union of all ROIs on A A . Other parts of A A are left unchanged in the output, which carries the same ROIs. In binary operations on two images A+B A+B etc., there should be only one set of ROIs in operation. This means that either only one image has an ROI, or the sets of ROIs for both images are identical. Outside the ROI, the image pixels are taken from the left-hand side of the operator as shown in the figure below. Figure: Two images A and B, B has an ROI. In the result, the area covered by the ROI is A+B, the rest of the image is passed through from A (which has no ROI). Click on image to expand. Originally the rule was that the intersection of the unions of the ROIs would be processed, while the rest of the output would be from the left-hand side (in the case of image data). That was too complicated, and broke the \"principle of least astonishment.\" If the user has put different ROIs on both sides of their image they have probably made a mistake. However, it's quite possible for the same ROIs to be on both sides of an expression if they were derived from the same image. Quality data All values in PCOT - scalar and image - can have an uncertainty value and data quality (DQ) bits. In imagecubes, this applies to every pixel of every band. The uncertainty values are expressed as standard deviations. Operations need to combine these data in a sensible way. Note that the standard deviation used is the population standard deviation, not the sample standard deviation. This is currently implemented using the Value class. Consists of: image uncertainty map (float per band) image quality/info bits (uint16 per band) Uncertainty Uncertainty values are propagated through calculations in the expr node and elsewhere according to these rules: \\begin{align} \\sigma(a+b), \\quad \\sigma(a-b) &= \\sqrt{\\sigma^2 a + \\sigma^2 b}\\\\ \\sigma(ab) &= |ab| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}\\quad \\text{when all values positive}\\\\ \\sigma(a/b) &= \\left|\\frac{a}{b}\\right| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\frac{\\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}}{b^2}\\quad \\text{when all values positive}\\\\ \\sigma(a^b) &= \\sqrt{ (a^{b-1} b \\sigma a)^2 + (a^b \\log a \\sigma b)^2} \\\\ &= \\sqrt{a^{2b-2} ((a \\sigma b \\log a)^2 + (b \\sigma a)^2)}\\\\ \\sigma (a \\& b) &= \\min (\\sigma a, \\sigma b)\\\\ \\sigma (a | b) &= \\max (\\sigma a, \\sigma b) \\end{align} \\begin{align} \\sigma(a+b), \\quad \\sigma(a-b) &= \\sqrt{\\sigma^2 a + \\sigma^2 b}\\\\ \\sigma(ab) &= |ab| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}\\quad \\text{when all values positive}\\\\ \\sigma(a/b) &= \\left|\\frac{a}{b}\\right| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\frac{\\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}}{b^2}\\quad \\text{when all values positive}\\\\ \\sigma(a^b) &= \\sqrt{ (a^{b-1} b \\sigma a)^2 + (a^b \\log a \\sigma b)^2} \\\\ &= \\sqrt{a^{2b-2} ((a \\sigma b \\log a)^2 + (b \\sigma a)^2)}\\\\ \\sigma (a \\& b) &= \\min (\\sigma a, \\sigma b)\\\\ \\sigma (a | b) &= \\max (\\sigma a, \\sigma b) \\end{align} Remember that generally these rules only apply if the bands are independent. In reality there is always a covariance between them, but we have to ignore that. Not all images contain uncertainty data - it may be that the input image doesn't have this data, or that a function has been performed on the image which does not permit uncertainty data to be carried through (consider a decorrelation stretch, for example). This can be viewed by selecting the nounc (no uncertainty) bit for viewing in the canvas . DQ bits Each scalar, or band value for each pixel, has an associated set of bits which indicate error states, etc. Bits are currently: Bit name Meaning Effect on calculations (see notes below) NODATA There is no data here BAD SAT Pixel is saturated high in this band BAD DIVZERO The data is the result of a division by zero BAD UNDEF The data is the result of an undefined operation BAD COMPLEX The data is a complex number likely to be undefined (or just the real part) BAD ERROR There is an unspecified error in this data BAD NOUNCERTAINTY There is no uncertainty data All bits are propagated into the data generated from the data they are attached to. BAD means that the data in these pixels should not be considered. While calculations will still be done on BAD data, the BAD bits will be propagated. In the case of nodes which generate a scalar from images, such as finding the mean or SD of a set of pixels (or similar operations in the spectrum node) the pixels marked BAD should be ignored. It is possible to set bits based on per-pixel conditions with the dqmod node. For example, convert all uncertainties greater than a given value into errors. It is possible to convert DQ bits into regions of interest using the roidq node, with the region being made up of pixels for which certain bits are absent or present. This can be done looking at all bands or just one. In general, when multiple image bands are combined (either from the same image or from different images) these are OR-ed together. This typically happens in a band-wise fashion because images are combined band-wise. Thus, when two images a a and b b are added, and the bits for channel i i of image a a are B_i(a) B_i(a) , \\[ B_i(a+b) = B_i(a) \\vee B_i(b)\\quad \\text{for all channels } i \\] However, some operations have a more complex flow of information. For example, a decorrelation stretch results in information from all bands being used in each band. In cases like this, the resulting bands are all ORed toether: \\[ B_i(\\text{decorr}(a)) = \\bigvee_i B_i(a) \\] UNIMPLEMENTED BIT OPERATIONS Nodes which perform a convolution operation or similar should propagate the error pixel to all affected pixels, leading to a blob of pixels in the output. I realise This isn't ideal ; another possibility could be to just zero the mask? But then we lose the error data. At the moment I don't believe we have any \"non-local\" behaviour where pixels affect regions of pixels in the output, so the point could be moot. Filter aberration UNIMPLEMENTED The filter wavelengths are only accurate for pixels in the centre of the image, due to the difference in the light path through the filter at different angles of incidence. Therefore: There will be a system in place to calculate the actual filter wavelength for a given pixel and use this in spectral plots (using the centre of the ROI for the spectrum node) A function should be available to generate the filter aberration value in expr - this would allow an \"image\" to be made of the aberration value which could be used in calculations It should be possible to set the ERROR bit for excessive aberration values Canvas information UNIMPLEMENTED The following should be visible in the canvas as optional overlays: Filter aberration as a heat map (default OFF)","title":"Operating principles"},{"location":"userguide/principles/#operating-principles","text":"PCOT nodes need to follow a set of rules to ensure that the information they process is handled consistently. This page describes these rules, most of which apply to image data.","title":"Operating principles"},{"location":"userguide/principles/#source-handling-rules","text":"Each datum handled by PCOT has a \"source set\" describing where that datum ultimately comes from. Sources vary: in images, each band in an input image carries a source datum describing where it comes from. For a PDS4 source it could be a LIDVID, or it could simply be a filename (although ideally it should have some archive indexing data). Because data can be combined in various ways, each datum could have multiple sources. Image data in PCOT have a separate source set for each band. The rules for sources are simple: Every datum has a source set. This may be a null source if the datum is generated internally (consider the output from constant nodes). In the case of an image, this source set is actually a separate source set for each band, but may still be considered as a single source set for some operations (since the union of the band sets is available). If a datum, or band in a multi-band image, is constructed from data from more than once source set, the resulting datum or band consists of the union of the sets. As an example, consider the rather contrived example below. Here, each datum is marked as a black circle below which is a description of the the sources - there's more explanation of this below. image inputs are in yellow scalar inputs are in blue nodes are white rectangles expr nodes (which calculate mathematical expressions) are marked as such with the expression being the main text Figure: An example graph.. Click on image to expand. We have three inputs into the graph: Input 0 has an image with three bands centered on 640nm, 540nm and 440nm. Input 1 has a different image with four bands. Input 2 has a numeric value of 0.2 (perhaps a housekeeping datum of some kind). These data are then combined in various ways: Input 1 is converted to a single greyscale band and multiplied by input 2 (the scalar 0.2) The result of this operation is then added to the 540nm band of input 0. What do the sources look like for each datum? Datum A is an image, and as such it has one source set per band. Each source set consists of a single source, with details of input and filter wavelength. So here, the sources for A could be written as [ {0:640}, {0:540}, {0:440} ] That is, a list of three sets, each of which contains a single source which I've written in the form input:wavelength . Datum B is the extracted 540nm band of input A, so its sources are: [ {0:540} ] Datum C is another multiband image, this time from input 1: [ {1:780}, {1:640}, {1:540}, {1:440} ] Datum D is the greyscale conversion of Datum C above. This creates a single-band image, but that band combines all the bands of datum C. This results in a single source set containing all the bands: [ {1:780, 1:640, 1:540, 1:440} ] Datum E is the only non-image source. I will denote it here as just \"2\" indicating that it is input 2. It is worth noting here that sources may contain a lot of extra data describing exactly where they come from. For example, this input may come from PDS4, in which case at least the LIDVID will be included. But for now this is simply {2} Note that this is not shown as a list because it is not a multiband source. Datum F multiplies each band in datum D by the scalar E. This means that the source for E must be added to the source set for each band. There is only one band at this point because of the greyscale conversion: [ {1:780, 1:640, 1:540, 1:440, 2} ] Finally, we add the single-band images B and F together, resulting in another single-band image. This addition is done bandwise. There is only one band, so the result is: [ {1:780, 1:640, 1:540, 1:440, 2, 0:540} ]","title":"Source handling rules"},{"location":"userguide/principles/#roi-rules","text":"Images may contain regions of interest. If this is the case, then any operation should only be performed on the region of interest if possible. However, the rest of the image should be passed through unchanged to provide context - it is always possible to use a croproi node before the operation if cropping is required. This rule has the following practical outcomes, in which images are denoted by capitals A, B, \\dots A, B, \\dots and scalars are denoted by lowercase x, y. \\dots x, y. \\dots So: In binary operations on an image and a scalar such as x+A x+A or A+x A+x , the operation is only performed on the region covered by the union of all ROIs on A A . Other parts of A A are left unchanged in the output, which carries the same ROIs. In binary operations on two images A+B A+B etc., there should be only one set of ROIs in operation. This means that either only one image has an ROI, or the sets of ROIs for both images are identical. Outside the ROI, the image pixels are taken from the left-hand side of the operator as shown in the figure below. Figure: Two images A and B, B has an ROI. In the result, the area covered by the ROI is A+B, the rest of the image is passed through from A (which has no ROI). Click on image to expand. Originally the rule was that the intersection of the unions of the ROIs would be processed, while the rest of the output would be from the left-hand side (in the case of image data). That was too complicated, and broke the \"principle of least astonishment.\" If the user has put different ROIs on both sides of their image they have probably made a mistake. However, it's quite possible for the same ROIs to be on both sides of an expression if they were derived from the same image.","title":"ROI rules"},{"location":"userguide/principles/#quality-data","text":"All values in PCOT - scalar and image - can have an uncertainty value and data quality (DQ) bits. In imagecubes, this applies to every pixel of every band. The uncertainty values are expressed as standard deviations. Operations need to combine these data in a sensible way. Note that the standard deviation used is the population standard deviation, not the sample standard deviation. This is currently implemented using the Value class. Consists of: image uncertainty map (float per band) image quality/info bits (uint16 per band)","title":"Quality data"},{"location":"userguide/principles/#uncertainty","text":"Uncertainty values are propagated through calculations in the expr node and elsewhere according to these rules: \\begin{align} \\sigma(a+b), \\quad \\sigma(a-b) &= \\sqrt{\\sigma^2 a + \\sigma^2 b}\\\\ \\sigma(ab) &= |ab| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}\\quad \\text{when all values positive}\\\\ \\sigma(a/b) &= \\left|\\frac{a}{b}\\right| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\frac{\\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}}{b^2}\\quad \\text{when all values positive}\\\\ \\sigma(a^b) &= \\sqrt{ (a^{b-1} b \\sigma a)^2 + (a^b \\log a \\sigma b)^2} \\\\ &= \\sqrt{a^{2b-2} ((a \\sigma b \\log a)^2 + (b \\sigma a)^2)}\\\\ \\sigma (a \\& b) &= \\min (\\sigma a, \\sigma b)\\\\ \\sigma (a | b) &= \\max (\\sigma a, \\sigma b) \\end{align} \\begin{align} \\sigma(a+b), \\quad \\sigma(a-b) &= \\sqrt{\\sigma^2 a + \\sigma^2 b}\\\\ \\sigma(ab) &= |ab| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}\\quad \\text{when all values positive}\\\\ \\sigma(a/b) &= \\left|\\frac{a}{b}\\right| \\sqrt{\\left(\\frac{\\sigma a}{a}\\right)^2+\\left(\\frac{\\sigma b}{b}\\right)^2} \\\\ &= \\frac{\\sqrt{a^2 \\sigma^2 b + b^2 \\sigma^2 a}}{b^2}\\quad \\text{when all values positive}\\\\ \\sigma(a^b) &= \\sqrt{ (a^{b-1} b \\sigma a)^2 + (a^b \\log a \\sigma b)^2} \\\\ &= \\sqrt{a^{2b-2} ((a \\sigma b \\log a)^2 + (b \\sigma a)^2)}\\\\ \\sigma (a \\& b) &= \\min (\\sigma a, \\sigma b)\\\\ \\sigma (a | b) &= \\max (\\sigma a, \\sigma b) \\end{align} Remember that generally these rules only apply if the bands are independent. In reality there is always a covariance between them, but we have to ignore that. Not all images contain uncertainty data - it may be that the input image doesn't have this data, or that a function has been performed on the image which does not permit uncertainty data to be carried through (consider a decorrelation stretch, for example). This can be viewed by selecting the nounc (no uncertainty) bit for viewing in the canvas .","title":"Uncertainty"},{"location":"userguide/principles/#dq-bits","text":"Each scalar, or band value for each pixel, has an associated set of bits which indicate error states, etc. Bits are currently: Bit name Meaning Effect on calculations (see notes below) NODATA There is no data here BAD SAT Pixel is saturated high in this band BAD DIVZERO The data is the result of a division by zero BAD UNDEF The data is the result of an undefined operation BAD COMPLEX The data is a complex number likely to be undefined (or just the real part) BAD ERROR There is an unspecified error in this data BAD NOUNCERTAINTY There is no uncertainty data All bits are propagated into the data generated from the data they are attached to. BAD means that the data in these pixels should not be considered. While calculations will still be done on BAD data, the BAD bits will be propagated. In the case of nodes which generate a scalar from images, such as finding the mean or SD of a set of pixels (or similar operations in the spectrum node) the pixels marked BAD should be ignored. It is possible to set bits based on per-pixel conditions with the dqmod node. For example, convert all uncertainties greater than a given value into errors. It is possible to convert DQ bits into regions of interest using the roidq node, with the region being made up of pixels for which certain bits are absent or present. This can be done looking at all bands or just one. In general, when multiple image bands are combined (either from the same image or from different images) these are OR-ed together. This typically happens in a band-wise fashion because images are combined band-wise. Thus, when two images a a and b b are added, and the bits for channel i i of image a a are B_i(a) B_i(a) , \\[ B_i(a+b) = B_i(a) \\vee B_i(b)\\quad \\text{for all channels } i \\] However, some operations have a more complex flow of information. For example, a decorrelation stretch results in information from all bands being used in each band. In cases like this, the resulting bands are all ORed toether: \\[ B_i(\\text{decorr}(a)) = \\bigvee_i B_i(a) \\]","title":"DQ bits"},{"location":"userguide/principles/#unimplemented-bit-operations","text":"Nodes which perform a convolution operation or similar should propagate the error pixel to all affected pixels, leading to a blob of pixels in the output. I realise This isn't ideal ; another possibility could be to just zero the mask? But then we lose the error data. At the moment I don't believe we have any \"non-local\" behaviour where pixels affect regions of pixels in the output, so the point could be moot.","title":"UNIMPLEMENTED BIT OPERATIONS"},{"location":"userguide/principles/#filter-aberration-unimplemented","text":"The filter wavelengths are only accurate for pixels in the centre of the image, due to the difference in the light path through the filter at different angles of incidence. Therefore: There will be a system in place to calculate the actual filter wavelength for a given pixel and use this in spectral plots (using the centre of the ROI for the spectrum node) A function should be available to generate the filter aberration value in expr - this would allow an \"image\" to be made of the aberration value which could be used in calculations It should be possible to set the ERROR bit for excessive aberration values","title":"Filter aberration UNIMPLEMENTED"},{"location":"userguide/principles/#canvas-information-unimplemented","text":"The following should be visible in the canvas as optional overlays: Filter aberration as a heat map (default OFF)","title":"Canvas information UNIMPLEMENTED"},{"location":"userguide/uncs/","text":"Uncertainties \\begin{align} \\sigma(a+b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(a-b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(ab) &= \\sqrt{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}\\\\ \\sigma(a/b) &= \\sqrt{\\frac{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}{b^4}}\\\\ \\sigma(a^b) &= \\sqrt{a^{2b-2} (a^2 \\sigma_b^2 (\\ln{a})^2 + b^2 \\sigma_a^2)} \\end{align} \\begin{align} \\sigma(a+b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(a-b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(ab) &= \\sqrt{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}\\\\ \\sigma(a/b) &= \\sqrt{\\frac{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}{b^4}}\\\\ \\sigma(a^b) &= \\sqrt{a^{2b-2} (a^2 \\sigma_b^2 (\\ln{a})^2 + b^2 \\sigma_a^2)} \\end{align}","title":"Uncertainties"},{"location":"userguide/uncs/#uncertainties","text":"\\begin{align} \\sigma(a+b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(a-b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(ab) &= \\sqrt{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}\\\\ \\sigma(a/b) &= \\sqrt{\\frac{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}{b^4}}\\\\ \\sigma(a^b) &= \\sqrt{a^{2b-2} (a^2 \\sigma_b^2 (\\ln{a})^2 + b^2 \\sigma_a^2)} \\end{align} \\begin{align} \\sigma(a+b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(a-b) &= \\sqrt{\\sigma^2_a + \\sigma^2_b}\\\\ \\sigma(ab) &= \\sqrt{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}\\\\ \\sigma(a/b) &= \\sqrt{\\frac{a^2 \\sigma_b^2 + b^2 \\sigma_a^2}{b^4}}\\\\ \\sigma(a^b) &= \\sqrt{a^{2b-2} (a^2 \\sigma_b^2 (\\ln{a})^2 + b^2 \\sigma_a^2)} \\end{align}","title":"Uncertainties"}]}
\ No newline at end of file
diff --git a/sitemap.xml.gz b/sitemap.xml.gz
index 6b6c8bbf..214e788e 100644
Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ
diff --git a/userguide/expr/index.html b/userguide/expr/index.html
index de8fcb16..e24050fb 100644
--- a/userguide/expr/index.html
+++ b/userguide/expr/index.html
@@ -231,7 +231,7 @@
Description
set the node to the new expression and perform it. The input can accept any type of data
and the output type is determined when the node is run.
The four inputs are assigned to the variables a, b, c, and d. They are typically (but not necessarily) images
-or scalar values.
+or numeric values.
The standard operators +,/,*,- and ^ all have their usual meanings. When applied to images they work in
a pixel-wise fashion, so if a is an image, 2*a will double the brightness. If b is also an image,
a+b will add the two images, pixel by pixel. There are two non-standard operators: . for properties
@@ -308,7 +308,7 @@
Image/numeric operators:
-
All operators can act on images and scalars (numeric values),
+
All operators can act on images, 1D vectors and scalars
with the exception of . and $ which have images on the left-hand side and identifiers
or integers on the right-hand side.
Those operators marked with (can act on ROIs) can also act on pairs of ROIs (regions of interest, see below).
@@ -350,11 +350,18 @@
Binary operations on image pairs
This probably isn't what you wanted. Note that this is obviously not an issue when an operation is being performed
on bands in a single image.
-
binary operators on images with regions of interest
+
Binary operators on images with regions of interest
If one of the two images has an ROI, the operation is only performed on that ROI; the remaining area of output is
taken from the image without an ROI. If both images have an ROI an error will result - it is likely that this
is a mistake on the user's part, and doing something more "intelligent" might conceal this. The desired result
can be achieved using expr nodes on ROIs and an importroi node.
+
Operations with vectors
+
Some functions can generate vectors, such as mean for getting the means of the bands, and vec for generating
+vectors by hand.
+
If an image is used in a binary operation with a vector on the other side, the vector must have the same number of
+elements as there are bands in the image. The operation will be performed on each band. Consider a 3-band image
+and the vector [2,3,4]. If we multiply them, the result will an image with the first band multiplied by 2,
+the second band multiplied by 3, and the third band multiplied by 4.
Operators on ROIs themselves (as opposed to images with ROIs)
@@ -430,6 +437,8 @@
Band extraction
+
Band extraction can also be performed with vectors provided the vector elements are numeric (i.e. wavelengths):
+a $ vec(640,550,440) is valid.
Properties
Properties are indicated by the . operator, e.g. a.w to find an
image's width.
diff --git a/userguide/multifile.md~ b/userguide/multifile.md~
new file mode 100644
index 00000000..6badf11b
--- /dev/null
+++ b/userguide/multifile.md~
@@ -0,0 +1,148 @@
+# The Multifile input method
+
+@@@ primary
+Documentation for the other methods is forthcoming.
+The regex aspect of multifile is complicated, so documenting it was
+a priority.
+@@@
+
+The multifile input method allows multiple monochrome files of "standard"
+types like PNG and BMP to be imported as multispectral images. It can
+also read raw binary files.
+
+Each file is flattened to greyscale in the process (if it is RGB) by finding
+the mean of the three channels for each pixel.
+
+
+## Filter CSV files
+
+In order to analyse the images,
+PCOT needs to be able to find out which filter was used for each image. This
+is done by storing a set of filters in a CSV file and using parts of the image
+files' names to work out which filter is used.
+
+Two files are available by default. These are PANCAM (for the actual
+ExoMars camera) and AUPE (Aberystwyth PANCAM Emulator). These can be
+found in the `src/pcot/assets/` directory.
+
+Here's an example of part of such a file:
+```csv
+cwl,fwhm,transmission,position,name
+570,12,0.989,L01,G04
+530,15,0.957,L02,G03
+610,10,0.956,L03,G05
+```
+In each line:
+
+* **cwl** is the centre wavelength
+* **fwhm** is the bandwidth as as full width at half maximum
+* **transmission** is how much energy the filter lets through at the CWL
+* **position** is the position of the filter in the filter wheel, typically as a camera letter
+(e.g. R or L) and an index number
+* **name** is the name of the filter.
+
+If you need to add a new filter set, edit the `.pcot.ini` file in your
+home directory and a `[filters]` block if one does not exist. To this block,
+add a line like this:
+```ini
+[filters]
+myfilterset=filename
+```
+For example
+```ini
+
+[filters]
+JCFCAM=c:/users/jim/jimfilters.csv
+```
+This new set, called JCFCAM, will be loaded when PCOT is started.
+
+## Setting a file pattern
+
+PCOT needs to be able to work out which filter was used to capture an
+image. This is done by extracting the filter name or position from the
+filename using a regular expression (or *regex*).
+If you have some experience with regular expressions (or access to someone
+with this experience), it will [help immensely](https://xkcd.com/208/).
+
+Regular expressions describe patterns which texts might match. For example,
+the regex `c[a-z]t` will match any three-letter string starting with `c` and
+ending with `t`: it's `c`, followed by any character between `a` and `z`,
+followed by `t`.
+
+* (Beginner's guide)[https://www.regular-expressions.info/index.html]
+* (Useful 'playground' to try out expressions)[https://regex101.com/]
+
+The default pattern looks something like this:
+```txt
+.*(?PL|R)WAC(?P[0-9][0-9]).*
+```
+
+This means:
+
+* `.*` matches any number of any character, so there could be anything
+at the start of the filename
+* `L|R` means "either `L` or `R`" - but we have specified a "named match"
+with brackets and the `?P<..>` notation: `(?PL|R)`. This means
+"either `L` or `R` and store the result under the name `lens`".
+* `WAC` means we must then have the sequence of letters `WAC`
+* `(?P[0-9][0-9])` means we must now match two digits (`[0-9]`) and
+store them as `n`
+* The final `.*` means that there can now be any number of any character again -
+so there could be anything at the end of the filename.
+
+The idea is that a filename like `/home/jim/files/DogBiscuitLWAC02Fish.jpg`
+will be matched, and will result in `L` being stored as `lens` and
+`02` being stored as `n`.
+
+### Named matches and how they are used
+
+Only one of the following should be true (e.g. you can't use
+`name` and `n` together):
+
+* `lens` and `n`: if these are found, they are joined together
+to form a filter position which is looked up in the filter set (by
+the `position` column).
+The idea is that `lens` indicates either the left or right camera
+and `n` identifies a filter. They're separate because many early
+files used names like `LWAC02` or `LWideAngle02`, in which the
+two elements were separate.
+* `name`: if this is found, it is used to match a filter using the `name`
+column
+* `cwl`: if this is found, it is used to match a filter using the `cwl`
+(wavelength) column
+
+
+If you need assistance, or this isn't flexible enough, contact us.
+
+
+## Reading raw binary files
+
+Data is often provided "as is" from the camera, in a raw binary format.
+Reading these files requires a little more information in advance:
+
+* What the numeric format of the data is (e.g. 16-bit unsigned integer)
+* How big the image is in pixels
+* Whether there is a header at the start which should be skipped and
+how big it is (the "offset")
+* Whether the image needs to be rotated and/or flipped
+* Whether the data is "big-endian" or "little-endian."
+
+These can be set by clicking the "raw loader settings" dialog.
+
+@@@ primary
+For images from AUPE, the settings are:
+
+* 16-bit unsigned integer
+* 1024x1024
+* 48 byte offset
+* Rotate 90 degrees
+* Big-endian data
+@@@
+
+## Presets
+You can save
+and load these values - and most other settings for multifile input, such
+as the pattern - using the "Presets" button. Presets are currently stored
+in your home user directory in a file called `MFPresets.json`. Users can
+easily copy this file from other users.
+
diff --git a/userguide/multifile/index.html b/userguide/multifile/index.html
index 7f8e7b47..f97b0ade 100644
--- a/userguide/multifile/index.html
+++ b/userguide/multifile/index.html
@@ -170,6 +170,14 @@
The multifile input method allows multiple monochrome files of "standard"
-types like PNG and BMP to be imported as multispectral images. Each file
-is flattened to greyscale in the process if it is RGB by finding the mean
-of the three channels for each pixel.
-
For this to work, PCOT needs to be able to find out which filter was used
-for each image. This is done by storing a set of filters in a CSV file
-and using parts of the image files' names to work out which filter is used.
+types like PNG and BMP to be imported as multispectral images. It can
+also read raw binary files.
+
Each file is flattened to greyscale in the process (if it is RGB) by finding
+the mean of the three channels for each pixel.
Filter CSV files
+
In order to analyse the images,
+PCOT needs to be able to find out which filter was used for each image. This
+is done by storing a set of filters in a CSV file and using parts of the image
+files' names to work out which filter is used.
Two files are available by default. These are PANCAM (for the actual
ExoMars camera) and AUPE (Aberystwyth PANCAM Emulator). These can be
found in the src/pcot/assets/ directory.
@@ -201,7 +211,15 @@
Filter CSV files
530,15,0.957,L02,G03
610,10,0.956,L03,G05
-
It should be fairly obvious.
+
In each line:
+
+
cwl is the centre wavelength
+
fwhm is the bandwidth as as full width at half maximum
+
transmission is how much energy the filter lets through at the CWL
+
position is the position of the filter in the filter wheel, typically as a camera letter
+(e.g. R or L) and an index number
+
name is the name of the filter.
+
If you need to add a new filter set, edit the .pcot.ini file in your
home directory and a [filters] block if one does not exist. To this block,
add a line like this:
@@ -215,18 +233,19 @@
Filter CSV files
This new set, called JCFCAM, will be loaded when PCOT is started.
Setting a file pattern
-
The file pattern is a regular expression
-used to work out which filter is used for which image.
+
PCOT needs to be able to work out which filter was used to capture an
+image. This is done by extracting the filter name or position from the
+filename using a regular expression (or regex).
If you have some experience with regular expressions (or access to someone
with this experience), it will help immensely.
Regular expressions describe patterns which texts might match. For example,
the regex c[a-z]t will match any three-letter string starting with c and
-ending with t.
+ending with t: it's c, followed by any character between a and z,
+followed by t.
We can also use a regex to extract parts of the string.
The default pattern looks something like this:
.*(?P<lens>L|R)WAC(?P<n>[0-9][0-9]).*
@@ -254,13 +273,43 @@
Named matches and how they are used
to form a filter position which is looked up in the filter set (by
the position column).
The idea is that lens indicates either the left or right camera
-and n identifies a filter.
+and n identifies a filter. They're separate because many early
+files used names like LWAC02 or LWideAngle02, in which the
+two elements were separate.
name: if this is found, it is used to match a filter using the name
column
cwl: if this is found, it is used to match a filter using the cwl
(wavelength) column
-
If you need assistance, or this isn't flexible enough, contact us.
+
If you need assistance, or this isn't flexible enough, contact us.
+
Reading raw binary files
+
Data is often provided "as is" from the camera, in a raw binary format.
+Reading these files requires a little more information in advance:
+
+
What the numeric format of the data is (e.g. 16-bit unsigned integer)
+
How big the image is in pixels
+
Whether there is a header at the start which should be skipped and
+how big it is (the "offset")
+
Whether the image needs to be rotated and/or flipped
+
Whether the data is "big-endian" or "little-endian."
+
+
These can be set by clicking the "raw loader settings" dialog.
+
+
For images from AUPE, the settings are:
+
+
16-bit unsigned integer
+
1024x1024
+
48 byte offset
+
Rotate 90 degrees
+
Big-endian data
+
+
+
Presets
+
You can save
+and load these values - and most other settings for multifile input, such
+as the pattern and filter - using the "Presets" button. Presets are currently stored
+in your home user directory in a file called MFPresets.json. Users can
+easily copy this file from other users.
diff --git a/userguide/principles.md~ b/userguide/principles.md~
new file mode 100644
index 00000000..43508bbd
--- /dev/null
+++ b/userguide/principles.md~
@@ -0,0 +1,237 @@
+# Operating principles
+
+PCOT nodes need to follow a set of rules to ensure that the information
+they process is handled consistently. This page describes these rules,
+most of which apply to image data.
+
+## Source handling rules
+Each datum handled by PCOT has a "source set" describing where that
+datum ultimately comes from. Sources vary: in images, each band in an input image carries
+a source datum describing where it comes from. For a PDS4 source it could be a LIDVID, or it
+could simply be a filename (although ideally it should have some archive indexing data).
+
+Because data can be combined in various ways, each datum could have multiple
+sources. Image data in PCOT have a separate source set for each band.
+
+The rules for sources are simple:
+
+* Every datum has a source set.
+* This may be a null source if the datum is generated internally (consider the output from *constant* nodes).
+* In the case of an image, this source set is actually a separate source set for each band, but may still be considered as a single
+source set for some operations (since the union of the band sets is available).
+* If a datum, or band in a multi-band image, is constructed from data from more than once source set, the resulting datum or band
+consists of the union of the sets.
+
+
+As an example, consider the rather contrived example below. Here,
+
+* each datum is marked as a black circle below which is a description of the the sources - there's more explanation of this below.
+* image inputs are in yellow
+* scalar inputs are in blue
+* nodes are white rectangles
+* *expr* nodes (which calculate mathematical expressions) are marked as
+such with the expression being the main text
+
+![!An example graph.](SourcesExample.svg)
+
+We have three inputs into the graph:
+
+* Input 0 has an image with three bands centered on 640nm, 540nm and 440nm.
+* Input 1 has a different image with four bands.
+* Input 2 has a numeric value of 0.2 (perhaps a housekeeping datum of some kind).
+
+These data are then combined in various ways:
+
+* Input 1 is converted to a single greyscale band and multiplied by input 2 (the scalar 0.2)
+* The result of this operation is then added to the 540nm band of input 0.
+
+What do the sources look like for each datum?
+
+* Datum A is an image, and as such it has one source set per band. Each source
+set consists of a single source, with details of input and filter
+wavelength. So here, the sources for A could be written as
+
+ [ {0:640}, {0:540}, {0:440} ]
+
+ That is, a list of three sets, each of which contains a single source which I've written
+ in the form ```input:wavelength```.
+
+* Datum B is the extracted 540nm band of input A, so its sources are:
+
+ [ {0:540} ]
+
+* Datum C is another multiband image, this time from input 1:
+
+ [ {1:780}, {1:640}, {1:540}, {1:440} ]
+
+* Datum D is the greyscale conversion of Datum C above. This creates a single-band image,
+but that band combines all the bands of datum C. This results in a single source set containing
+all the bands:
+
+ [ {1:780, 1:640, 1:540, 1:440} ]
+
+* Datum E is the only non-image source. I will denote it here as just "2" indicating that it
+is input 2. It is worth noting here that sources may contain a lot of extra data describing
+exactly where they come from. For example, this input may come from PDS4, in which case at least
+the LIDVID will be included. But for now this is simply
+
+ {2}
+
+ Note that this is not shown as a list because it is not a multiband source.
+
+
+* Datum F multiplies each band in datum D by the scalar E. This means that the source for E must
+be added to the source set for each band. There is only one band at this point because of the
+greyscale conversion:
+
+ [ {1:780, 1:640, 1:540, 1:440, 2} ]
+
+* Finally, we add the single-band images B and F together, resulting in another single-band image.
+This addition is done bandwise. There is only one band, so the result is:
+
+ [ {1:780, 1:640, 1:540, 1:440, 2, 0:540} ]
+
+## ROI rules
+
+Images may contain regions of interest. If this is the case, then
+any operation should only be performed on the region of interest if possible.
+However, the rest of the image should be passed through unchanged to
+provide context - it is always possible to use a *croproi* node before
+the operation if cropping is required.
+
+This rule has the following practical outcomes, in which images are denoted
+by capitals $A, B, \dots$ and scalars are denoted by lowercase $x, y. \dots$
+
+So:
+
+* In binary operations on an image and a scalar such as $x+A$ or $A+x$, the
+operation is only performed on the region covered by the union of all
+ROIs on $A$. Other parts of $A$ are left unchanged in the output,
+which carries the same ROIs.
+
+* In binary operations on two images $A+B$ etc., there should be only one set of ROIs
+in operation. This means that either only one image has an ROI, or the sets of ROIs for
+both images are identical.
+
+Outside the ROI, the image pixels are taken from the left-hand side of the operator
+as shown in the figure below.
+
+![!Two images A and B, B has an ROI. In the result, the area covered by the ROI is A+B, the rest of the image is passed through from A (which has no ROI)](ROIs.svg)
+
+
+@@@info
+Originally the rule was that the intersection of the unions of the ROIs would
+be processed, while the rest of the output would be from the left-hand side
+(in the case of image data). That was too complicated, and broke the
+"principle of least astonishment." If the user has put different ROIs on both sides of
+their image they have probably made a mistake. However, it's quite possible
+for the same ROIs to be on both sides of an expression if they were derived from the same
+image.
+@@@
+
+## Quality data
+All values in PCOT - scalar and image - can have an uncertainty value and data quality (DQ) bits.
+In imagecubes, this applies to every pixel of every band. The uncertainty values are expressed
+as standard deviations. Operations need to combine these data in a sensible way.
+
+This is currently implemented using the **Value** class.
+
+Consists of:
+
+* image uncertainty map (float per band)
+* image quality/info bits (uint16 per band)
+
+### Uncertainty
+
+Uncertainty values are propagated through calculations in the *expr* node and elsewhere according to
+these rules:
+
+\begin{align}
+\sigma(a+b), \quad \sigma(a-b) &= \sqrt{\sigma^2 a + \sigma^2 b}\\
+\sigma(ab) &= |ab| \sqrt{\left(\frac{\sigma a}{a}\right)^2+\left(\frac{\sigma b}{b}\right)^2} \\
+&= \sqrt{a^2 \sigma^2 b + b^2 \sigma^2 a}\quad \text{when all values positive}\\
+\sigma(a/b) &= \left|\frac{a}{b}\right| \sqrt{\left(\frac{\sigma a}{a}\right)^2+\left(\frac{\sigma b}{b}\right)^2} \\
+&= \frac{\sqrt{a^2 \sigma^2 b + b^2 \sigma^2 a}}{b^2}\quad \text{when all values positive}\\
+\sigma(a^b) &= \sqrt{ (a^{b-1} b \sigma a)^2 + (a^b \log a \sigma b)^2} \\
+&= \sqrt{a^{2b-2} ((a \sigma b \log a)^2 + (b \sigma a)^2)}\\
+\sigma (a \& b) &= \min (\sigma a, \sigma b)\\
+\sigma (a | b) &= \max (\sigma a, \sigma b)
+\end{align}
+
+@@@ warning
+Remember that generally these rules only apply if the bands are independent. In reality there
+is always a covariance between them, but we have to ignore that.
+@@@
+
+
+**Not all images contain uncertainty data** - it may be that the input
+image doesn't have this data, or that a function has been performed on the
+image which does not permit uncertainty data to be carried through
+(consider a decorrelation stretch, for example). **This
+can be viewed** by selecting the **nounc** (no uncertainty) bit for viewing in the [canvas](canvas.md).
+
+### DQ bits
+
+Each scalar, or band value for each pixel, has an associated set of bits which indicate error states, etc.
+
+Bits are currently:
+
+| Bit name | Meaning | Effect on calculations (see notes below)|
+|-----------|------|----|
+|**NODATA**|There is no data here|BAD|
+|**SAT**|Pixel is saturated high in this band|BAD|
+|**DIVZERO**|The data is the result of a division by zero|BAD|
+|**UNDEF**|The data is the result of an undefined operation|BAD|
+|**COMPLEX**|The data is a complex number likely to be undefined (or just the real part)|BAD|
+|**ERROR**|There is an unspecified error in this data|BAD|
+|NOUNCERTAINTY|There is no uncertainty data||
+
+All bits are propagated into the data generated from the data they are attached to.
+**BAD** means that the data in these pixels should not be
+considered. While calculations will still be done on BAD data, the BAD bits will be propagated. In the case
+of nodes which generate a scalar from images, such as finding the mean or SD of a set of pixels (or similar operations in the
+*spectrum* node) the pixels marked BAD should be ignored.
+
+* It is possible to set bits based on per-pixel conditions with the *dqmod* node. For example, convert all uncertainties
+greater than a given value into errors.
+
+* It is possible to convert DQ bits into regions of interest using the *roidq* node, with the region being made up of
+pixels for which certain bits are absent or present. This can be done looking at all bands or just one.
+
+* In general, when multiple image bands
+are combined (either from the same image or from different images) these are OR-ed together. This typically happens in a band-wise fashion
+because images are combined band-wise. Thus, when two images $a$ and $b$ are
+added, and the bits for channel $i$ of image $a$ are $B_i(a)$,
+
+\\[
+B_i(a+b) = B_i(a) \vee B_i(b)\quad \text{for all channels } i
+\\]
+
+* However, some operations have a more complex flow of information. For example, a decorrelation stretch results in information from all
+bands being used in each band. In cases like this, the resulting bands are all ORed toether:
+\\[
+B_i(\text{decorr}(a)) = \bigvee_i B_i(a)
+\\]
+
+### UNIMPLEMENTED BIT OPERATIONS
+* Nodes which perform a convolution operation or similar should propagate the error pixel to all affected pixels, leading to a blob of pixels in the output.
+I realise **This isn't ideal**; another possibility could be to just zero the mask? But then we lose the error data. At the moment I don't believe we have any
+"non-local" behaviour where pixels affect regions of pixels in the output, so the point could be moot.
+
+## Filter aberration UNIMPLEMENTED
+
+The filter wavelengths are only accurate for pixels in the centre of the image, due to the difference in the light path through the filter
+at different angles of incidence. Therefore:
+
+* There will be a system in place to calculate the actual filter wavelength for a given pixel and use this in spectral plots (using the centre
+of the ROI for the *spectrum* node)
+* A function should be available to generate the filter aberration value in *expr* - this would allow an "image" to be made of
+the aberration value which could be used in calculations
+* It should be possible to set the ERROR bit for excessive aberration values
+
+
+## Canvas information UNIMPLEMENTED
+
+The following should be visible in the canvas as optional overlays:
+
+* Filter aberration as a heat map (default OFF)
diff --git a/userguide/principles/index.html b/userguide/principles/index.html
index 94290a44..f2eca694 100644
--- a/userguide/principles/index.html
+++ b/userguide/principles/index.html
@@ -320,6 +320,10 @@
Quality data
All values in PCOT - scalar and image - can have an uncertainty value and data quality (DQ) bits.
In imagecubes, this applies to every pixel of every band. The uncertainty values are expressed
as standard deviations. Operations need to combine these data in a sensible way.
+
+
Note that the standard deviation used is the population standard
+deviation, not the sample standard deviation.
+
This is currently implemented using the Value class.