Differences
This shows you the differences between two versions of the page.
| 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 a 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 expected. Since 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 constants. This is also the form the Dinamica EGO GUI's 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 shape. See [[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]] | ||
| - | |||
| - | |||