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/07/18 02:03]
hermann
calculate_python_expression [2026/07/22 18:15] (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. ​For an overview of how Dinamica EGO and Python can be linked together, see [[python_coupling|Dinamica EGO and Python Coupling]].+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.
  
 ===== Inputs ===== ===== Inputs =====
Line 24: Line 24:
 ==== Expression inputs ==== ==== Expression inputs ====
  
-Data is passed into the expression through **hook** functors placed inside the container'​s ''<​nowiki>​{{ … }}</​nowiki>''​ block:+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:
  
   * 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"​]''​
  
 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.
Line 56: Line 57:
 | [[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'':​+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:
  
 <​code>​ <​code>​
Line 70: Line 71:
 ==== Utilities ==== ==== Utilities ====
  
-=== dinamica.package() ​===+=== Installing packages ​===
  
-Installs ​(if needed via pip) and imports the requested module.+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. 
 + 
 +''​dinamica.package(packageName,​ installPath=None,​ loadPath=None)''​ installs ​(if needed via pip) and imports the requested module.
  
 ^ Parameter ^ Type ^ Default ^ Description ^ ^ Parameter ^ Type ^ Default ^ Description ^
Line 78: Line 81:
 | ''​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. | | ''​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. | | ''​loadPath''​ | str | ''​packageName''​ | The name used to ''​import''​ the module in Python. |
- 
-=== 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. 
- 
-^ Parameter ^ Type ^ Default ^ Description ^ 
-| ''​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. | 
- 
-=== 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. 
- 
-^ 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. | 
- 
-=== dinamica.toTable() === 
- 
-Converts several Python data shapes into a valid Dinamica table for output. 
- 
-^ 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. | 
- 
-=== Installing packages === 
- 
-Packages can be installed in two ways: 
- 
-  * Calling ''​dinamica.package(...)''​ from within the expression. 
-  * Listing package names on the //​packages//​ input port, one per line. 
- 
-These two approaches 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. 
- 
-''​dinamica.package(packageName,​ installPath=None,​ loadPath=None)'':​ 
- 
-  * ''​packageName''​ identifies the package and is the default for both following parameters when omitted. 
-  * ''​installPath''​ is passed to ''​pip install''​. It can be a version-pinned requirement,​ a wheel filename or URL, a ''​git+''​ URL, or a name plus extra pip flags. 
-  * ''​loadPath''​ is the name used to ''​import''​ the module. It defaults to ''​packageName''​. 
  
 Simply install and load ''​numpy'':​ Simply install and load ''​numpy'':​
Line 143: Line 109:
 </​code>​ </​code>​
  
-The //​packages//​ port takes one package identifier per line, in any form accepted by ''​pip''​. ​Unlike ​''​dinamica.package()'',​ it does not support specifying separate install and import ​names, and every listed package is installed before the expression runs:+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 ​separate install ​name and import ​name. Every listed package is installed before the expression runs, but 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:
 <​code>​ <​code>​
 numpy numpy
Line 150: Line 118:
 </​code>​ </​code>​
  
-==== Writing the expression ​in EGO Script ====+The expression
 +<​code>​ 
 +import numpy 
 +import requests 
 +import torchvision 
 +</​code>​
  
-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 expected. Since only this one output is needed, the carrier can be [[ego_script#​inline_syntax|inlined]] directly into the call.+=== dinamica.prepareTable() ===
  
-The following counts ​the patches in a land cover areas table whose area meets a minimum threshold:+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 valuesa column of ''​int''/''​float''​ values produces a Real column, a column of ''​str''​ values produces a String column.
  
-<​code>​ +^ Parameter ^ Type ^ Default ^ Description ^ 
-result := CalculatePythonExpression (String $"( +| ''inputTable'' | list of lists | — | The table data. First inner list is the header ​row; subsequent inner lists are data rows
-total = 0 +''​numKeys''​ | int | — | Number of key columnsKey column names will have ''​*''​ appended in the output. |
-for row in dinamica.inputs['t1'][1:]: +
-    if row[1] >= dinamica.inputs['v1']: +
-        total += 1 +
-dinamica.outputs['patchCount'] = total +
-)") {{ +
-    NumberTable landCoverAreas 1; +
-    NumberValue minimumArea ​   1; +
-}}; +
-patchCount := ExtractStructNumber result "​patchCount";​ +
-</​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 nameSee [[#​retrieving_outputs|Retrieving outputs]] for the full list of extraction functors.+=== 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. 
 + 
 +^ 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. | 
 + 
 +=== 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. 
 + 
 +^ 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. | 
 +''​numKeys'' ​| int | — | Number ​of key columnsKey column names will have ''​*''​ appended in the output. Ignored for a flat list, which always produces a lookup table with sequential keys|
  
 ==== Examples ==== ==== Examples ====
Line 186: Line 162:
 print(dinamica.inputs) print(dinamica.inputs)
 </​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.
  
 Print the rows of both connected tables to verify their contents: Print the rows of both connected tables to verify their contents:
Line 237: Line 215:
 </​code>​ </​code>​
  
-===== Group =====+==== Writing the expression in EGO Script ​====
  
-[[Functor List#Integration|Integration]]+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 expected. Since only this one output is needed, the carrier can be [[ego_script#​inline_syntax|inlined]] directly into the call.
  
-===== Internal Name =====+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.
  
-CalculatePythonExpression+The following counts the patches in a land cover areas table whose area meets a minimum threshold:
  
-===== See Also =====+<​code>​ 
 +result :CalculatePythonExpression (String $"( 
 +total 
 +for row in dinamica.inputs['​t1'​][1:​]:​ 
 +    if row[1] >dinamica.inputs['​v1'​]:​ 
 +        total +
 +dinamica.outputs['​patchCount'​] ​total 
 +)") {{ 
 +    NumberTable landCoverAreas 1; 
 +    NumberValue minimumArea ​   1; 
 +}}; 
 +patchCount :ExtractStructNumber result "​patchCount";​ 
 +</​code>​
  
-  * [[python_coupling|Dinamica EGO and Python Coupling]] +''​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.
-  * [[ego_script#container_functors|EGO Script — Container Functors]] +
-  * [[ego_script#​verbose_form|EGO Script — Verbose Form (Hooks)]] +
-  * [[calculate_functors|Calculate Functors — Complete Operator Documentation]]+
  
 +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>​
 +result := CalculatePythonExpression (String $"(
 +dinamica.package('​numpy'​)
 +
 +areas = numpy.array([row[1] for row in dinamica.inputs['​t1'​][1:​]])
 +
 +meanArea = float(numpy.mean(areas))
 +stdArea = float(numpy.std(areas))
 +
 +isOutlier = numpy.abs(areas - meanArea) > 2 * stdArea
 +
 +dinamica.outputs['​meanArea'​] = meanArea
 +dinamica.outputs['​stdArea'​] = stdArea
 +
 +header = dinamica.inputs['​t1'​][0]
 +outlierRows = [row for row, flagged in zip(dinamica.inputs['​t1'​][1:​],​ isOutlier) if flagged]
 +outlierTable = [header] + outlierRows
 +
 +dinamica.outputs['​outlierPatches'​] = dinamica.prepareTable(outlierTable,​ 1)
 +)") {{
 +    NumberTable landCoverPatches 1;
 +}};
 +meanArea ​      := ExtractStructNumber result "​meanArea";​
 +stdArea ​       := ExtractStructNumber result "​stdArea";​
 +outlierPatches := ExtractStructTable ​ result "​outlierPatches";​
 +</​code>​
 +
 +Here ''​landCoverPatches''​ is bound to ''​t1''​ through a single [[Number Table]] hook.
 +
 +===== Group =====
 +
 +[[Functor List#​Integration|Integration]]
 +
 +===== Internal Name =====
 +
 +CalculatePythonExpression