Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
calculate_python_expression [2026/08/03 17:39]
hermann
calculate_python_expression [2026/08/31 17:10] (current)
hermann
Line 3: Line 3:
 ===== Description ===== ===== Description =====
  
-This is a **[[ego_script#​container_functors|container functor]]** that runs a Python instance with the user-defined expression. Like the other calculator functors, data is connected through hook functors placed inside its ''<​nowiki>​{{ … }}</​nowiki>'' ​block. See also [[calculate_r_expression|Calculate R Expression]] for the equivalent functor using R.+This is a container functor that runs a Python instance with user-defined expression. Like the other calculator functors, such as [[Calculate Map]] or [[Calculate Lookup Table]], data is connected through hook functors placed inside its block, rather than through regular input ports. See also [[Calculate R Expression]] for the equivalent functor using R.
  
 ===== Inputs ===== ===== Inputs =====
  
-^ Name ^ Type ^ Description ^ +^ Name  ^ Type  ^ Description ​ 
-expression ​| [[ego_script#​constants|Code]] | The expression ​that will run on Python. ​''​Code''​ values cannot be written as plain text constants in EGO Script — see [[#​writing_the_expression_in_ego_script|Writing the expression in EGO Script]] below. |+Expression  ​| [[Code ​Type]]  | The expression ​to run on Python. ​Written directly as a Code constant using its own raw string syntax -- see [[#​writing_the_expression_in_ego_script|Writing the expression in EGO Script]] below. ​ |
  
 ===== Optional Inputs ===== ===== Optional Inputs =====
  
-^ Name ^ Type ^ Description ^ +^ Name  ^ Type  ^ Description ​ ^ Default Value  ​
-packages //​(advanced)// ​| String | Required packages ​to be installed by PIP (one per line). Each package can be identified ​either ​by its name or by specifying ​a filename or URL pointing to the corresponding wheel file. Packages that are already installed ​will be ignored. |+Packages  ​[[String ​Type]]  ​Packages ​to install with pip before the expression runs, one per line. Each package can be identified by name or by a filename or URL pointing to the corresponding wheel; a package ​already installed ​is skippedThis is an advanced port.  | None  ​|
  
 ===== Outputs ===== ===== Outputs =====
  
-^ Name ^ Type ^ Description ^ +^ Name  ^ Type  ^ Description ​ 
-result ​| [[struct_type|Struct]] | A struct ​containing the output values generated by the expression. |+Result  ​| [[Struct ​Type]]  Struct ​containing the output values generated by the expression, one entry per key assigned into dinamica.outputs.  ​| 
 + 
 +===== Group ===== 
 + 
 +[[Functor List#​Integration | Integration]]
  
 ===== Notes ===== ===== Notes =====
Line 24: Line 28:
 ==== Expression inputs ==== ==== Expression inputs ====
  
-Data is passed into the expression through ​**hook** functors placed inside the container'​s ​''<​nowiki>​{{ … }}</​nowiki>'' ​block — the same [[ego_script#​verbose_form|verbose form]] hook mechanism used by the calculator functors:+Data is passed into the expression through hook functors placed inside the container'​s block -- the same verbose-form hook mechanism used by the calculator functors:
  
-  * Tables and lookup tables → [[Number Table]] → available in the expression as ''​dinamica.inputs["​t1"​]''​''​dinamica.inputs["​t2"​]''​, …, ''​dinamica.inputs["​t100"​]''​ +  * Tables and lookup tables → [[Number Table]] → available in the expression as dinamica.inputs["​t1"​],​ dinamica.inputs["​t2"​],​ …, dinamica.inputs["​t100"​] 
-  * Scalar values → [[Number Value]] → available as ''​dinamica.inputs["​v1"​]''​''​dinamica.inputs["​v2"​]''​, …, ''​dinamica.inputs["​v100"​]''​ +  * Scalar values → [[Number Value]] → available as dinamica.inputs["​v1"​],​ dinamica.inputs["​v2"​],​ …, dinamica.inputs["​v100"​] 
-  * Strings → [[Number String]] → available as ''​dinamica.inputs["​s1"​]''​''​dinamica.inputs["​s2"​]''​, …, ''​dinamica.inputs["​s100"​]''​+  * Strings → [[Number String]] → available as dinamica.inputs["​s1"​],​ dinamica.inputs["​s2"​],​ …, dinamica.inputs["​s100"​]
  
-Maps cannot be connected ​— this functor has no cell context. There is no shorthand notation. See [[calculate_functors|Calculate Functors ​— Complete Operator Documentation]] for the general hook mechanism and syntax.+Maps cannot be connected ​-- this functor has no cell context. There is no shorthand notation. See [[Calculate Functors|Calculate Functors ​-- Complete Operator Documentation]] for the general hook mechanism and syntax.
  
-Every table or lookup table arriving through ​''​dinamica.inputs'' ​is represented in Python as a **list of lists**: the first inner list contains the column names (the header row) and every subsequent inner list is a row of data. The header carries plain column names only — no ''​*'' ​marking key columns, no ''#​type'' ​annotation ​— so which columns were keys in the original table is information Python doesn'​t receive and can't recover from the input alone. Code that needs that distinction has to be told it separately, for instance by also connecting the key count as its own [[Number Value]] hook.+Every table or lookup table arriving through dinamica.inputs is represented in Python as a list of lists: the first inner list contains the column names (the header row) and every subsequent inner list is a row of data. The header carries plain column names only -- no asterisk ​marking key columns, no type annotation ​-- so which columns were keys in the original table is information Python doesn'​t receive and can't recover from the input alone. Code that needs that distinction has to be told it separately, for instance by also connecting the key count as its own [[Number Value]] hook.
  
 ==== Expression outputs ==== ==== Expression outputs ====
  
-Values are returned to Dinamica by assigning them into ''​dinamica.outputs''​, keyed by the desired output name. Every assigned value becomes an entry in the output ​''​result'' ​struct:+Values are returned to Dinamica by assigning them into dinamica.outputs,​ keyed by the desired output name. Every assigned value becomes an entry in the output ​Result ​struct:
  
 <code python> <code python>
-// Scalar values are assigned directly+Scalar values are assigned directly
 dinamica.outputs["​patchCount"​] = 42 dinamica.outputs["​patchCount"​] = 42
 dinamica.outputs["​totalArea"​] = 1530.5 dinamica.outputs["​totalArea"​] = 1530.5
 </​code>​ </​code>​
  
-Tables and lookup tables cannot be assigned directly ​— they must first be converted using the [[#utilities|utilities]] ​described below. If a table arriving from an input already carries ​''​*'' ​markers on its key column names, it can be assigned directly to an output without conversion.+Tables and lookup tables cannot be assigned directly ​-- they must first be converted using the utilities described below. If a table arriving from an input already carries ​asterisk ​markers on its key column names, it can be assigned directly to an output without conversion.
  
 ==== Retrieving outputs ==== ==== Retrieving outputs ====
  
-''​CalculatePythonExpression'' ​returns a single [[struct_type|Struct]] value (via its ''​result'' ​output port) containing every entry assigned to ''​dinamica.outputs''​. To retrieve individual values from that struct, use the following functors from the //Integration// group:+Calculate Python Expression ​returns a single [[Struct ​Type]] value (via its Result ​output port) containing every entry assigned to dinamica.outputs. To retrieve individual values from that struct, use the following functors from the Integration group:
  
-^ Functor ^ Retrieves ^ +^ Functor ​ ^ Retrieves ​ 
-| [[Extract Struct Number]] | A numeric value (''​int'' ​or ''​float'' ​assigned to ''​dinamica.outputs''​) | +| [[Extract Struct Number]] ​ | A numeric value (int or float assigned to dinamica.outputs) ​ 
-| [[Extract Struct String]] | A string value | +| [[Extract Struct String]] ​ | A string value  
-| [[Extract Struct Table]] | A table produced by ''​dinamica.prepareTable()'' ​+| [[Extract Struct Table]] ​ | A table produced by dinamica.prepareTable() ​ 
-| [[Extract Struct Lookup Table]] | A lookup table produced by ''​dinamica.prepareLookupTable()'' ​+| [[Extract Struct Lookup Table]] ​ | A lookup table produced by dinamica.prepareLookupTable() ​ 
-| [[Extract Struct Tuple]] | A tuple value |+| [[Extract Struct Tuple]] ​ | A tuple value  |
  
-Each functor takes two inputs: the ''​Struct'' ​returned by ''​CalculatePythonExpression''​, and the name of the entry to extract as a string constant. For example, to retrieve a numeric output named ''​patchCount'' ​and a table output named ''​filteredPatches''​ — the ''​String $"​(...)"''​ wrapper around the Python code is explained in [[#​writing_the_expression_in_ego_script|Writing the expression in EGO Script]] below:+Each functor takes two inputs: the Struct returned by Calculate Python Expression, and the name of the entry to extract as a string constant. For example, to retrieve a numeric output named patchCount and a table output named filteredPatches:​
  
 <​code>​ <​code>​
-result := CalculatePythonExpression ​(String ​$"(+result := CalculatePythonExpression $"(
 myTable = [["​PatchId*",​ "​Area"​],​ [1, 12.4], [2, 8.7]] myTable = [["​PatchId*",​ "​Area"​],​ [1, 12.4], [2, 8.7]]
 dinamica.outputs['​patchCount'​] = 42 dinamica.outputs['​patchCount'​] = 42
 dinamica.outputs['​filteredPatches'​] = dinamica.prepareTable(myTable,​ 1) dinamica.outputs['​filteredPatches'​] = dinamica.prepareTable(myTable,​ 1)
-)"{{ }};+)" {{ }};
 patchCount ​     := ExtractStructNumber result "​patchCount";​ patchCount ​     := ExtractStructNumber result "​patchCount";​
 filteredPatches := ExtractStructTable ​ result "​filteredPatches";​ filteredPatches := ExtractStructTable ​ result "​filteredPatches";​
Line 73: Line 77:
 === Installing packages === === Installing packages ===
  
-Packages can be installed in two ways: calling ​''​dinamica.package(...)'' ​from within the expression, or listing package names on the //​packages// ​input port, one per line. The two differ in timing: ​''​dinamica.package(...)'' ​runs **during** the expression, so it can be called conditionally;​ the //​packages// ​port always runs **before** the expression executes. Because ''​dinamica.package(...)''​ runs together with the script, it may fail if a package is already loaded in an incompatible version.+Packages can be installed in two ways: calling dinamica.package(...) from within the expression, or listing package names on the Packages ​input port. The two differ in timing: dinamica.package(...) runs during the expression, so it can be called conditionally;​ the Packages ​port always runs before the expression executes.
  
-''​dinamica.package(packageName,​ installPath=None,​ loadPath=None)''​ installs (if needed via pip) and imports the requested module.+**dinamica.package()**
  
-^ Parameter ^ Type ^ Default ^ Description ^ +dinamica.package(packageName,​ installPath=None,​ loadPath=None) installs (if needed, via pip) and imports the requested module. Because it runs together with the script, it may fail if a package is already loaded in an incompatible version. 
-''​packageName'' ​| str | — | Identifies the package. Used as the default for both ''​installPath'' ​and ''​loadPath'' ​when those are omitted. | + 
-''​installPath'' ​| str | ''​packageName'' ​| What is passed to ''​pip install''​. Can be a plain name, a version-pinned requirement,​ a wheel filename or URL, a ''​git+'' ​URL, or a name followed by extra pip flags. | +^ Parameter ​ ^ Type  ^ Default ​ ^ Description ​ 
-''​loadPath'' ​| str | ''​packageName'' ​| The name used to ''​import'' ​the module in Python. |+| packageName ​ | str  --  ​| Identifies the package. Used as the default for both installPath and loadPath when those are omitted. ​ 
 +| installPath ​ | str  | packageName ​ | What is passed to pip install. Can be a plain name, a version-pinned requirement,​ a wheel filename or URL, a git+ URL, or a name followed by extra pip flags. ​ 
 +| loadPath ​ | str  | packageName ​ | The name used to import the module in Python. ​ | 
 + 
 +Simply install and load numpy:
  
-Simply install and load ''​numpy'':​ 
 <code python> <code python>
 dinamica.package("​numpy"​) dinamica.package("​numpy"​)
Line 88: Line 95:
  
 Specify a version: Specify a version:
 +
 <code python> <code python>
 dinamica.package("​numpy",​ "​numpy==1.19.5"​) dinamica.package("​numpy",​ "​numpy==1.19.5"​)
Line 93: Line 101:
  
 Install a package whose importable name differs from its pip name: Install a package whose importable name differs from its pip name:
 +
 <code python> <code python>
 dinamica.package("​segment_anything_py",​ "​segment_anything_py",​ "​segment_anything"​) dinamica.package("​segment_anything_py",​ "​segment_anything_py",​ "​segment_anything"​)
Line 98: Line 107:
  
 Use arbitrary pip parameters, such as installing from a remote wheel with a custom index: Use arbitrary pip parameters, such as installing from a remote wheel with a custom index:
 +
 <code python> <code python>
 dinamica.package("​segment_anything_py",​ "​https://​files.pythonhosted.org/​packages/​43/​2f/​dabe75d90a7eb54a0a609a0fc5c36d1933256319beaea5d6b2f176e213a2/​segment_anything_py-1.0-py3-none-any.whl --index-url https://​download.pytorch.org/​whl/​cu118",​ "​segment_anything"​) dinamica.package("​segment_anything_py",​ "​https://​files.pythonhosted.org/​packages/​43/​2f/​dabe75d90a7eb54a0a609a0fc5c36d1933256319beaea5d6b2f176e213a2/​segment_anything_py-1.0-py3-none-any.whl --index-url https://​download.pytorch.org/​whl/​cu118",​ "​segment_anything"​)
Line 103: Line 113:
  
 Chain several installs: Chain several installs:
 +
 <code python> <code python>
 dinamica.package("​cython"​) dinamica.package("​cython"​)
Line 109: Line 120:
 </​code>​ </​code>​
  
-The //​packages// ​port takes one package identifier per line, in any form accepted by ''​pip''​It only installs — unlike ''​dinamica.package()''​it does not also import ​the module, and it does not support specifying a separate install name and import ​nameEvery listed package ​is installed before ​the expression ​runsbut the expression must still import each one itself, using its actual importable name (which may differ from the name given to ''​pip''​):+**The Packages port** 
 + 
 +The Packages input port takes one package identifier per line, in any form accepted by pip. Every listed package is installed before the expression runs. Unlike ​dinamica.package(),​ the port only installs -- it does not import ​anythingImporting ​is left entirely to the expression, ​using standard Python ​import ​syntax and each package'​s ​actual importable namewhich may differ from the name given to pip
 + 
 +The Packages port:
  
-The //​packages//​ port: 
 <​code>​ <​code>​
 numpy numpy
Line 119: Line 133:
  
 The expression: The expression:
 +
 <code python> <code python>
-import numpy +import numpy as np 
-import ​requests +from requests import ​Session 
-import torchvision+from torchvision ​import transforms
 </​code>​ </​code>​
  
 === dinamica.prepareTable() === === dinamica.prepareTable() ===
  
-Converts a list of lists into a table ready to be assigned to an output. The first inner list must be the header row. As with any table (see [[ego_script#​constants|Constants]]), each column'​s type is inferred from its own values: a column of ''​int''​/''​float'' ​values produces a Real column, a column of ''​str'' ​values produces a String column.+Converts a list of lists into a table ready to be assigned to an output. The first inner list must be the header row. As with any table, each column'​s type is inferred from its own values: a column of int/float values produces a Real column, a column of str values produces a String column.
  
-^ Parameter ^ Type ^ Default ^ Description ^ +^ Parameter ​ ^ Type  ^ Default ​ ^ Description ​ 
-''​inputTable'' ​| list of lists | — | The table data. First inner list is the header row; subsequent inner lists are data rows. | +| inputTable ​ | list of lists  --  ​| The table data. First inner list is the header row; subsequent inner lists are data rows.  
-''​numKeys'' ​| int | — | Number of key columns. Key column names will have ''​*'' ​appended in the output. |+| numKeys ​ | int  --  ​| Number of key columns. Key column names will have an asterisk ​appended in the output. ​ |
  
 === dinamica.prepareLookupTable() === === dinamica.prepareLookupTable() ===
  
-Converts a list of lists into a lookup table ready to be assigned to an output. The first inner list must be the header row. Lookup tables are always Real-typed on both key and value sides — there is no String option ​— so every value in ''​lut'' ​must be numeric.+Converts a list of lists into a lookup table ready to be assigned to an output. The first inner list must be the header row. Lookup tables are always Real-typed on both key and value sides -- there is no String option ​-- so every value in lut must be numeric.
  
-^ Parameter ^ Type ^ Default ^ Description ^ +^ Parameter ​ ^ Type  ^ Default ​ ^ Description ​ 
-''​lut'' ​| list of lists | — | The lookup table data. First inner list is the header row; subsequent inner lists are data rows. |+| lut  | list of lists  --  ​| The lookup table data. First inner list is the header row; subsequent inner lists are data rows.  |
  
 === dinamica.toTable() === === dinamica.toTable() ===
  
-Converts several Python data shapes into a valid Dinamica table for output. Column types are inferred the same way as ''​dinamica.prepareTable()''​ — except for a flat list, which always produces a Real-typed lookup table with sequential keys, matching the Real-only rule for lookup tables.+Converts several Python data shapes into a valid Dinamica table for output. Column types are inferred the same way as dinamica.prepareTable() ​-- except for a flat list, which always produces a Real-typed lookup table with sequential keys, matching the Real-only rule for lookup tables.
  
-^ Parameter ^ Type ^ Default ^ Description ^ +^ Parameter ​ ^ Type  ^ Default ​ ^ Description ​ 
-''​inputTable'' ​| list of lists; dict of lists; list of tuples; flat list; ''​pandas.DataFrame''​''​numpy.array'' ​— | The data to convert. A flat list produces a lookup table with sequential keys. A ''​numpy.array'' ​must have its header as the first row. | +| inputTable ​ | list of lists; dict of lists; list of tuples; flat list; pandas.DataFrame;​ numpy.array ​ --  ​| The data to convert. A flat list produces a lookup table with sequential keys. A numpy.array must have its header as the first row.  
-''​numKeys'' ​| int | — | Number of key columns. Key column names will have ''​*'' ​appended in the output. Ignored for a flat list, which always produces a lookup table with sequential keys. |+| numKeys ​ | int  --  ​| Number of key columns. Key column names will have an asterisk ​appended in the output. Ignored for a flat list, which always produces a lookup table with sequential keys.  |
  
 ==== Examples ==== ==== Examples ====
  
-The following examples use a consistent set of inputs:+The following examples use a consistent set of inputs: ​t1 is a table of land cover patches with columns PatchId*, Area, and CategoryId; t2 is a lookup table mapping category identifiers to category names; and v1 is a scalar minimum area threshold.
  
-  * ''​t1''​ — a table of land cover patches, with columns ''​PatchId*'',​ ''​Area'',​ and ''​CategoryId''​ +Install and import numpy, then inspect all inputs passed in by Dinamica:
-  * ''​t2''​ — a lookup table mapping category identifiers to category names +
-  * ''​v1''​ — a scalar minimum area threshold +
- +
-Install and import ​''​numpy''​, then inspect all inputs passed in by Dinamica:+
  
 <code python> <code python>
Line 163: Line 174:
 </​code>​ </​code>​
  
-> **Note:​** ​The ''​print()'' ​call is visible in Dinamica EGO's Message Log, shown as a Result-level message. Log levels are ordered Unconditional,​ Error, Warning, Result, Info, Info2, Debug, Debug2 ​— messages printed from Python are only shown when the Message Log level is set to Result or a more verbose level; at Unconditional,​ Error, or Warning they are suppressed.+The print() call is visible in Dinamica EGO's Message Log, shown as a Result-level message. Log levels are ordered Unconditional,​ Error, Warning, Result, Info, Info2, Debug, Debug2 ​-- messages printed from Python are only shown when the Message Log level is set to Result or a more verbose level; at Unconditional,​ Error, or Warning they are suppressed.
  
 Print the rows of both connected tables to verify their contents: Print the rows of both connected tables to verify their contents:
Line 217: Line 228:
 ==== Writing the expression in EGO Script ==== ==== Writing the expression in EGO Script ====
  
-When writing a script by hand, the ''​expression''​ input cannot ​be filled in directly as a text constant: the [[ego_script#​constants|Code]] ​type is represented in the underlying script using base64 encoding, which is impractical to write or edit directly. Instead, connect a ''​String''​ carrier functor containing the expression text to the ''​expression'' ​port — its output is accepted wherever a ''​Code''​ value is expectedSince only this one output ​is needed, ​the carrier can be [[ego_script#​inline_syntax|inlined]] directly into the call. +Expression can be filled in directly as a text constant, using [[Code ​Type]]'s own raw string syntax -- the same ''​$"<​delimiter>​( raw_characters )<​delimiter>"​'' ​form used by String constantsThis is also the form the Dinamica EGO GUI'dedicated code editor ​generates when it writes ​the Expression port'value, so a hand-written script and one produced by the GUI take the same shapeSee [[Code Type]] for the full grammar, including the base64 alternative form.
- +
-This limitation is specific to hand-written EGO Script. In the Dinamica EGO GUI, the ''​expression''​ port has a dedicated code editor ​that edits the ''​Code'' ​value directly — the ''​String''​ carrier workaround is only necessary when writing or editing ​the ''​.ego''​ file as text.+
  
 The following counts the patches in a land cover areas table whose area meets a minimum threshold: The following counts the patches in a land cover areas table whose area meets a minimum threshold:
  
 <​code>​ <​code>​
-result := CalculatePythonExpression ​(String ​$"(+result := CalculatePythonExpression $"(
 total = 0 total = 0
 for row in dinamica.inputs['​t1'​][1:​]:​ for row in dinamica.inputs['​t1'​][1:​]:​
Line 230: Line 239:
         total += 1         total += 1
 dinamica.outputs['​patchCount'​] = total dinamica.outputs['​patchCount'​] = total
-)"{{+)" {{
     NumberTable landCoverAreas 1;     NumberTable landCoverAreas 1;
     NumberValue minimumArea ​   1;     NumberValue minimumArea ​   1;
Line 237: Line 246:
 </​code>​ </​code>​
  
-''​CalculatePythonExpression'' ​returns a ''​Struct'' ​containing all values assigned to ''​dinamica.outputs''​. The ''​ExtractStructNumber'' ​functor then pulls the ''​patchCount'' ​entry out of that struct by name. See [[#​retrieving_outputs|Retrieving outputs]] for the full list of extraction functors.+Here landCoverAreas is bound to t1 and minimumArea to v1, each through a [[Number Table]] or [[Number Value]] hook. Calculate Python Expression ​returns a Struct containing all values assigned to dinamica.outputs; the ExtractStructNumber functor then pulls the patchCount entry out of that struct by name. See "Retrieving outputs" above for the full list of extraction functors.
  
-A more involved example: installing ​''​numpy'' ​to compute area statistics for a land cover patches table and flag outlier patches, then retrieving both the scalar statistics and the resulting table:+A more involved example: installing numpy to compute area statistics for a land cover patches table and flag outlier patches, then retrieving both the scalar statistics and the resulting table:
  
 <​code>​ <​code>​
-result := CalculatePythonExpression ​(String ​$"(+result := CalculatePythonExpression $"(
 dinamica.package('​numpy'​) dinamica.package('​numpy'​)
  
Line 260: Line 269:
  
 dinamica.outputs['​outlierPatches'​] = dinamica.prepareTable(outlierTable,​ 1) dinamica.outputs['​outlierPatches'​] = dinamica.prepareTable(outlierTable,​ 1)
-)"{{+)" {{
     NumberTable landCoverPatches 1;     NumberTable landCoverPatches 1;
 }}; }};
Line 268: Line 277:
 </​code>​ </​code>​
  
-Here ''​landCoverPatches'' ​is bound to ''​t1'' ​through a single [[Number Table]] hook. +Here landCoverPatches is bound to t1 through a single [[Number Table]] hook.
- +
-===== Group ===== +
- +
-[[Functor List#​Integration|Integration]]+
  
 ===== Internal Name ===== ===== Internal Name =====
  
 CalculatePythonExpression CalculatePythonExpression
-