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/19 22:19]
hermann
calculate_python_expression [2026/08/18 20:55] (current)
hermann
Line 8: Line 8:
  
 ^ 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|Code]] | The expression that will 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 =====
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"​]''​
Line 32: Line 32:
 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 ​name of each key column ​has an asterisk (''​*''​) appended ​to it.+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.
  
 ==== Expression outputs ==== ==== Expression outputs ====
Line 38: Line 38:
 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>​+<​code ​python>
 // Scalar values are assigned directly // Scalar values are assigned directly
 dinamica.outputs["​patchCount"​] = 42 dinamica.outputs["​patchCount"​] = 42
Line 57: 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 ''​$"​(...)"''​ raw string syntax wrapping the Python code is explained in [[#​writing_the_expression_in_ego_script|Writing the expression in EGO Script]] below:
  
 <​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 71: 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. 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()** 
 + 
 +''​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.
  
 ^ Parameter ^ Type ^ Default ^ Description ^ ^ Parameter ^ Type ^ Default ^ Description ^
Line 79: Line 83:
 | ''​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'':​
-<​code>​+<​code ​python>
 dinamica.package("​numpy"​) dinamica.package("​numpy"​)
 </​code>​ </​code>​
  
 Specify a version: Specify a version:
-<​code>​+<​code ​python>
 dinamica.package("​numpy",​ "​numpy==1.19.5"​) dinamica.package("​numpy",​ "​numpy==1.19.5"​)
 </​code>​ </​code>​
  
 Install a package whose importable name differs from its pip name: Install a package whose importable name differs from its pip name:
-<​code>​+<​code ​python>
 dinamica.package("​segment_anything_py",​ "​segment_anything_py",​ "​segment_anything"​) dinamica.package("​segment_anything_py",​ "​segment_anything_py",​ "​segment_anything"​)
 </​code>​ </​code>​
  
 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>​+<​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"​)
 </​code>​ </​code>​
  
 Chain several installs: Chain several installs:
-<​code>​+<​code ​python>
 dinamica.package("​cython"​) dinamica.package("​cython"​)
 dinamica.package("​numpy"​) dinamica.package("​numpy"​)
Line 144: Line 111:
 </​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** 
 + 
 +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 also import the module, and it does not support specifying ​separate install ​name and import ​name. The expression must still import each package ​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 121:
 torchvision==0.19.1 --index-url https://​download.pytorch.org/​whl/​cu121 torchvision==0.19.1 --index-url https://​download.pytorch.org/​whl/​cu121
 </​code>​ </​code>​
 +
 +The expression:
 +<code python>
 +import numpy
 +import requests
 +import torchvision
 +</​code>​
 +
 +=== 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.
 +
 +^ 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. 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 columns. Key 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 161: Line 162:
 Install and import ''​numpy'',​ then inspect all inputs passed in by Dinamica: Install and import ''​numpy'',​ then inspect all inputs passed in by Dinamica:
  
-<​code>​+<​code ​python>
 dinamica.package("​numpy"​) dinamica.package("​numpy"​)
 print(dinamica.inputs) print(dinamica.inputs)
Line 170: Line 171:
 Print the rows of both connected tables to verify their contents: Print the rows of both connected tables to verify their contents:
  
-<​code>​+<​code ​python>
 for row in dinamica.inputs["​t1"​]:​ for row in dinamica.inputs["​t1"​]:​
     print(row)     print(row)
Line 180: Line 181:
 Count the patches whose area meets the minimum threshold and sum their total area: Count the patches whose area meets the minimum threshold and sum their total area:
  
-<​code>​+<​code ​python>
 patchCount = 0 patchCount = 0
 totalArea = 0.0 totalArea = 0.0
Line 194: Line 195:
 Return a filtered table containing only the patches above the threshold: Return a filtered table containing only the patches above the threshold:
  
-<​code>​+<​code ​python>
 filtered = [dinamica.inputs["​t1"​][0]] filtered = [dinamica.inputs["​t1"​][0]]
 for row in dinamica.inputs["​t1"​][1:​]:​ for row in dinamica.inputs["​t1"​][1:​]:​
Line 205: Line 206:
 Compute the total area per category and return it as a lookup table: Compute the total area per category and return it as a lookup table:
  
-<​code>​+<​code ​python>
 areaSums = {} areaSums = {}
 for row in dinamica.inputs["​t1"​][1:​]:​ for row in dinamica.inputs["​t1"​][1:​]:​
Line 220: Line 221:
 ==== 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. +The ''​expression''​ input can be filled in directly as a text constant, using [[code_type|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'​s ​value, so a hand-written script and one produced by the GUI take the same shapeSee [[code_type|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 233: Line 232:
         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 245: Line 244:
  
 <​code>​ <​code>​
-result := CalculatePythonExpression ​(String ​$"(+result := CalculatePythonExpression $"(
 dinamica.package('​numpy'​) dinamica.package('​numpy'​)
  
Line 263: Line 262:
  
 dinamica.outputs['​outlierPatches'​] = dinamica.prepareTable(outlierTable,​ 1) dinamica.outputs['​outlierPatches'​] = dinamica.prepareTable(outlierTable,​ 1)
-)"{{+)" {{
     NumberTable landCoverPatches 1;     NumberTable landCoverPatches 1;
 }}; }};
Line 280: Line 279:
  
 CalculatePythonExpression CalculatePythonExpression
- 
-===== See Also ===== 
- 
-  * [[ego_script#​container_functors|EGO Script — Container Functors]] 
-  * [[ego_script#​verbose_form|EGO Script — Verbose Form (Hooks)]] 
-  * [[calculate_functors|Calculate Functors — Complete Operator Documentation]] 
- 
-