Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 6 additions & 15 deletions openhcs/interop/cellprofiler/analyst_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
SourceImageProvenance,
)
from openhcs.core.source_bindings import CompiledSourceBindingPlan
from openhcs.core.source_metadata import SourceMetadataFields, SourceMetadataScalar
from openhcs.core.source_metadata import SourceMetadataScalar
from openhcs.core.source_workspace_projection import (
VirtualWorkspacePathLookup,
VirtualWorkspaceSourceProjectionAuthority,
Expand All @@ -87,7 +87,6 @@
CELLPROFILER_MEASUREMENT_DIALECT,
)
from .module_declarations import CellProfilerModule
from .source_metadata import CellProfilerSourceMetadataField


class CellProfilerObjectTableMode(str, Enum):
Expand Down Expand Up @@ -920,11 +919,8 @@ def collect_image_provenance(
values: dict[str, Any] = {}
source_path = self._resolved_source_path(plane_provenance.source_path)
if source_path is not None:
values[self.dialect.source_image_path_field(source_image_name).name] = (
str(source_path.parent)
)
values[self.dialect.source_image_file_field(source_image_name).name] = (
source_path.name
values.update(
self.dialect.source_image_file_values(source_path, source_image_name)
)
if source_path.is_file():
values.update(
Expand All @@ -948,14 +944,9 @@ def collect_image_provenance(
field_name: value
for field_name, value in self.dialect.source_metadata_defaults().items()
}
if source_path is not None:
metadata_items[
CellProfilerSourceMetadataField.FILE_LOCATION.field_name
] = source_path.as_uri()
if component_metadata is not None:
metadata_items.update(
SourceMetadataFields.original_items(component_metadata)
)
metadata_items.update(
self.dialect.source_metadata_values(component_metadata, source_path)
)
image_number = self.image_set_numbering.for_source_slice(
scope=scope,
provenance=provenance,
Expand Down
50 changes: 50 additions & 0 deletions openhcs/interop/cellprofiler/database_column_dialect.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from dataclasses import dataclass, field, replace
from enum import Enum
from hashlib import md5
from pathlib import Path
import re
from types import MappingProxyType
from typing import Any, ClassVar
Expand All @@ -15,6 +16,7 @@
ObjectCoreMeasurementFeature,
)
from openhcs.core.runtime_tabular_values import FieldSpec
from openhcs.core.source_metadata import SourceMetadataFields, SourceMetadataMapping
from openhcs.interop.cellprofiler.source_metadata import (
CellProfilerSourceMetadataField,
)
Expand Down Expand Up @@ -417,6 +419,54 @@ def source_metadata_defaults() -> dict[str, object]:

return CellProfilerSourceMetadataField.static_defaults()

@staticmethod
def source_metadata_values(
metadata: SourceMetadataMapping | None,
source_path: Path | None,
) -> dict[str, object]:
"""Project original extraction fields and the declared source path."""
values: dict[str, object] = {}
if metadata is not None:
values.update(SourceMetadataFields.original_items(metadata))
if source_path is not None:
values.setdefault(
CellProfilerSourceMetadataField.FILE_LOCATION.field_name,
source_path.as_uri() if source_path.is_absolute() else str(source_path),
)
return values

@staticmethod
def source_acquisition_values(
metadata: SourceMetadataMapping | None,
) -> dict[str, object]:
"""Project declared acquisition components, never absent axes.

A represented multi-value domain is not a single acquisition coordinate.
This is distinct from the original extraction-field namespace.
"""
if metadata is None:
return {}
return {
component.value: (
SourceMetadataFields.canonical_component_value(component, domain[0])
if len(domain) == 1 else None
)
for component, domain in SourceMetadataFields.component_domains(metadata).items()
}

def source_image_file_values(
self,
source_path: Path,
source_image_name: str,
) -> dict[str, str]:
"""Project a named source identity without reading its image pixels."""
return {
self.source_image_path_field(source_image_name).name: str(
source_path.parent
),
self.source_image_file_field(source_image_name).name: source_path.name,
}

def image_table_name(self) -> str:
return f"{self.table_prefix}Per_Image"

Expand Down
29 changes: 22 additions & 7 deletions openhcs/interop/cellprofiler/image_set_numbering.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def project_measurement_rows(
image_numbers_by_slice = self.for_source_slices(
scope=scope,
provenance=table.source_provenance,
slice_indices=projection.present_axis_values(slice_axis.value),
slice_indices=self.source_slices_for_measurement_table(table),
owner=table.name,
)
axisless_image_number = None
Expand All @@ -116,12 +116,7 @@ def project_measurement_rows(
) or (0,)
source_image_numbers = tuple(
dict.fromkeys(
self.for_source_slices(
scope=scope,
provenance=table.source_provenance,
slice_indices=source_plane_indices,
owner=table.name,
).values()
image_numbers_by_slice[index] for index in source_plane_indices
)
)
if (
Expand All @@ -145,6 +140,26 @@ def project_measurement_rows(
axisless_value=axisless_image_number,
)

@staticmethod
def source_slices_for_measurement_table(
table: MeasurementTable,
) -> tuple[int, ...]:
"""Return exactly the slices represented by this producer's row scope.

Axisless rows consume their declared source stack, not a guessed plate
grid. Preserve explicit row order before additional source planes.
"""
projection = MeasurementRowsAxisProjection.from_rows(table.rows)
indices = projection.present_axis_values(
MeasurementRowAxisField.SLICE_INDEX.value
)
if not projection.has_axisless_rows(MeasurementRowAxisField.SLICE_INDEX):
return indices
source_indices = (
tuple(range(table.source_provenance.source_plane_count)) or (0,)
)
return tuple(dict.fromkeys((*indices, *source_indices)))

def _source_identity(
self,
provenance: SourceImageProvenance,
Expand Down
Loading
Loading