Skill Tracer
Skills › Knowledge Work Plugins › instrument-data-to-allotrope › flattening_guide.md

flattening_guide.md

254 lines · bio-research/skills/instrument-data-to-allotrope/references/flattening_guide.md at 2574e29

Open on GitHub
1# Flattening ASM to 2D CSV
2
3Converting hierarchical ASM JSON to flat 2D tables for LIMS import, spreadsheet analysis, or data engineering pipelines.
4

Flattening ASM to 2D CSV

Converting hierarchical ASM JSON to flat 2D tables for LIMS import, spreadsheet analysis, or data engineering pipelines.

Raw lines 1 to 4
5## Why Flatten?
6
7ASM is semantically rich but hierarchical. Many systems need flat tables:
8- LIMS import (Benchling, STARLIMS, LabWare)
9- Excel/CSV analysis
10- Database loading
11- Quick visual inspection

Why Flatten?

ASM is semantically rich but hierarchical. Many systems need flat tables:

  • LIMS import (Benchling, STARLIMS, LabWare)
  • Excel/CSV analysis
  • Database loading
  • Quick visual inspection
Raw lines 5 to 12
13## Flattening Strategy

Flattening Strategy

Raw lines 13 to 14
15### Core Principle
16Each **measurement** becomes one **row**. Metadata is repeated per row.

Core Principle

Each measurement becomes one row. Metadata is repeated per row.

Raw lines 15 to 17
18### What's Excluded
19The flattening intentionally **omits top-level ASM metadata** such as:
20- `$asm.manifest` (model version, schema URIs)
21- Root-level fields outside the technique aggregate document
23This keeps the output focused on experimental data. If you need schema version tracking for compliance or audit purposes, consider storing the original ASM JSON alongside the flattened CSV, or modify the flattening script to include these fields.

What's Excluded

The flattening intentionally omits top-level ASM metadata such as:

  • $asm.manifest (model version, schema URIs)
  • Root-level fields outside the technique aggregate document

This keeps the output focused on experimental data. If you need schema version tracking for compliance or audit purposes, consider storing the original ASM JSON alongside the flattened CSV, or modify the flattening script to include these fields.

Raw lines 18 to 24
25### Hierarchy to Columns
26```
27ASM Hierarchy → Flat Column
28─────────────────────────────────────────────────
29device-system-document.
30 device-identifier → instrument_serial_number
31 model-number → instrument_model
33measurement-aggregate-document.
34 analyst → analyst
35 measurement-time → measurement_datetime
37measurement-document[].
38 sample-identifier → sample_id
39 viable-cell-density.value → viable_cell_density
40 viable-cell-density.unit → viable_cell_density_unit
41 viability.value → viability_percent
42```

Hierarchy to Columns

ASM Hierarchy                    → Flat Column
─────────────────────────────────────────────────
device-system-document.
  device-identifier              → instrument_serial_number
  model-number                   → instrument_model
  
measurement-aggregate-document.
  analyst                        → analyst
  measurement-time               → measurement_datetime
  
measurement-document[].
  sample-identifier              → sample_id
  viable-cell-density.value      → viable_cell_density
  viable-cell-density.unit       → viable_cell_density_unit
  viability.value                → viability_percent
Raw lines 25 to 43
44## Column Naming Convention
46Use snake_case with descriptive suffixes:
48| ASM Field | Flat Column |
49|-----------|-------------|
50| `viable-cell-density` | `viable_cell_density` |
51| `.value` | `_value` (or omit if obvious) |
52| `.unit` | `_unit` |
53| `measurement-time` | `measurement_datetime` |

Column Naming Convention

Use snake_case with descriptive suffixes:

ASM FieldFlat Column
viable-cell-densityviable_cell_density
.value_value (or omit if obvious)
.unit_unit
measurement-timemeasurement_datetime
Raw lines 44 to 54
55## Example: Cell Counting

Example: Cell Counting

Raw lines 55 to 56
57### ASM Input (simplified)
58```json
60 "cell-counting-aggregate-document": {
61 "device-system-document": {
62 "device-identifier": "VCB001",
63 "model-number": "Vi-CELL BLU"
64 },
65 "cell-counting-document": [{
66 "measurement-aggregate-document": {
67 "analyst": "jsmith",
68 "measurement-time": "2024-01-15T10:30:00Z",
69 "measurement-document": [
70 {
71 "sample-identifier": "Sample_A",
72 "viable-cell-density": {"value": 2500000, "unit": "(cell/mL)"},
73 "viability": {"value": 95.2, "unit": "%"}
74 },
75 {
76 "sample-identifier": "Sample_B",
77 "viable-cell-density": {"value": 1800000, "unit": "(cell/mL)"},
78 "viability": {"value": 88.7, "unit": "%"}
79 }
80 ]
81 }
82 }]
83 }
85```

ASM Input (simplified)

{
  "cell-counting-aggregate-document": {
    "device-system-document": {
      "device-identifier": "VCB001",
      "model-number": "Vi-CELL BLU"
    },
    "cell-counting-document": [{
      "measurement-aggregate-document": {
        "analyst": "jsmith",
        "measurement-time": "2024-01-15T10:30:00Z",
        "measurement-document": [
          {
            "sample-identifier": "Sample_A",
            "viable-cell-density": {"value": 2500000, "unit": "(cell/mL)"},
            "viability": {"value": 95.2, "unit": "%"}
          },
          {
            "sample-identifier": "Sample_B",
            "viable-cell-density": {"value": 1800000, "unit": "(cell/mL)"},
            "viability": {"value": 88.7, "unit": "%"}
          }
        ]
      }
    }]
  }
}
Raw lines 57 to 86
87### Flattened Output
88```csv
89sample_id,viable_cell_density,viable_cell_density_unit,viability_percent,analyst,measurement_datetime,instrument_serial_number,instrument_model
90Sample_A,2500000,(cell/mL),95.2,jsmith,2024-01-15T10:30:00Z,VCB001,Vi-CELL BLU
91Sample_B,1800000,(cell/mL),88.7,jsmith,2024-01-15T10:30:00Z,VCB001,Vi-CELL BLU
92```

Flattened Output

sample_id,viable_cell_density,viable_cell_density_unit,viability_percent,analyst,measurement_datetime,instrument_serial_number,instrument_model
Sample_A,2500000,(cell/mL),95.2,jsmith,2024-01-15T10:30:00Z,VCB001,Vi-CELL BLU
Sample_B,1800000,(cell/mL),88.7,jsmith,2024-01-15T10:30:00Z,VCB001,Vi-CELL BLU
Raw lines 87 to 93
94## Example: Plate Reader

Example: Plate Reader

Raw lines 94 to 95
96### ASM Input (simplified)
97```json
99 "plate-reader-aggregate-document": {
100 "plate-reader-document": [{
101 "measurement-aggregate-document": {
102 "plate-identifier": "ELISA_001",
103 "measurement-document": [
104 {"well-location": "A1", "absorbance": {"value": 0.125, "unit": "mAU"}},
105 {"well-location": "A2", "absorbance": {"value": 0.892, "unit": "mAU"}},
106 {"well-location": "A3", "absorbance": {"value": 1.456, "unit": "mAU"}}
107 ]
108 }
109 }]
110 }
112```

ASM Input (simplified)

{
  "plate-reader-aggregate-document": {
    "plate-reader-document": [{
      "measurement-aggregate-document": {
        "plate-identifier": "ELISA_001",
        "measurement-document": [
          {"well-location": "A1", "absorbance": {"value": 0.125, "unit": "mAU"}},
          {"well-location": "A2", "absorbance": {"value": 0.892, "unit": "mAU"}},
          {"well-location": "A3", "absorbance": {"value": 1.456, "unit": "mAU"}}
        ]
      }
    }]
  }
}
Raw lines 96 to 113
114### Flattened Output
115```csv
116plate_id,well_position,absorbance,absorbance_unit
117ELISA_001,A1,0.125,mAU
118ELISA_001,A2,0.892,mAU
119ELISA_001,A3,1.456,mAU
120```

Flattened Output

plate_id,well_position,absorbance,absorbance_unit
ELISA_001,A1,0.125,mAU
ELISA_001,A2,0.892,mAU
ELISA_001,A3,1.456,mAU
Raw lines 114 to 121
122## Handling Data Cubes
124Data cubes (time series, spectra) need special handling:

Handling Data Cubes

Data cubes (time series, spectra) need special handling:

Raw lines 122 to 125
126### Option 1: Expand to rows
127Each point becomes a row:
128```csv
129sample_id,time_seconds,absorbance
130Sample_A,0,0.100
131Sample_A,60,0.125
132Sample_A,120,0.150
133```

Option 1: Expand to rows

Each point becomes a row:

sample_id,time_seconds,absorbance
Sample_A,0,0.100
Sample_A,60,0.125
Sample_A,120,0.150
Raw lines 126 to 134
135### Option 2: Wide format
136Measurements as columns:
137```csv
138sample_id,abs_0s,abs_60s,abs_120s
139Sample_A,0.100,0.125,0.150
140```

Option 2: Wide format

Measurements as columns:

sample_id,abs_0s,abs_60s,abs_120s
Sample_A,0.100,0.125,0.150
Raw lines 135 to 141
142### Option 3: JSON array in cell
143Keep as array (some systems support this):
144```csv
145sample_id,absorbance_timeseries
146Sample_A,"[0.100,0.125,0.150]"
147```

Option 3: JSON array in cell

Keep as array (some systems support this):

sample_id,absorbance_timeseries
Sample_A,"[0.100,0.125,0.150]"
Raw lines 142 to 148
149## Standard Column Sets by Technique

Standard Column Sets by Technique

Raw lines 149 to 150
151### Cell Counting
152```
153sample_id, viable_cell_density, viable_cell_density_unit, total_cell_count,
154viability_percent, average_cell_diameter, average_cell_diameter_unit,
155analyst, measurement_datetime, instrument_serial_number
156```

Cell Counting

sample_id, viable_cell_density, viable_cell_density_unit, total_cell_count,
viability_percent, average_cell_diameter, average_cell_diameter_unit,
analyst, measurement_datetime, instrument_serial_number
Raw lines 151 to 157
158### Spectrophotometry
159```
160sample_id, wavelength_nm, absorbance, pathlength_cm, concentration,
161concentration_unit, a260_a280_ratio, a260_a230_ratio,
162analyst, measurement_datetime, instrument_serial_number
163```

Spectrophotometry

sample_id, wavelength_nm, absorbance, pathlength_cm, concentration,
concentration_unit, a260_a280_ratio, a260_a230_ratio,
analyst, measurement_datetime, instrument_serial_number
Raw lines 158 to 164
165### Plate Reader / ELISA
166```
167plate_id, well_position, sample_type, sample_id, absorbance, absorbance_unit,
168concentration, concentration_unit, dilution_factor, cv_percent,
169analyst, measurement_datetime, instrument_serial_number
170```

Plate Reader / ELISA

plate_id, well_position, sample_type, sample_id, absorbance, absorbance_unit,
concentration, concentration_unit, dilution_factor, cv_percent,
analyst, measurement_datetime, instrument_serial_number
Raw lines 165 to 171
172### qPCR
173```
174sample_id, target_name, well_position, ct_value, ct_mean, ct_sd,
175quantity, quantity_unit, amplification_efficiency,
176analyst, measurement_datetime, instrument_serial_number
177```

qPCR

sample_id, target_name, well_position, ct_value, ct_mean, ct_sd,
quantity, quantity_unit, amplification_efficiency,
analyst, measurement_datetime, instrument_serial_number
Raw lines 172 to 178
179## Python Implementation
181```python
182import json
183import pandas as pd
185def flatten_asm(asm_dict, technique="cell-counting"):
186 """
187 Flatten ASM JSON to pandas DataFrame.
189 Args:
190 asm_dict: Parsed ASM JSON
191 technique: ASM technique type
193 Returns:
194 pandas DataFrame with one row per measurement
195 """
196 rows = []
198 # Get aggregate document
199 agg_key = f"{technique}-aggregate-document"
200 agg_doc = asm_dict.get(agg_key, {})
202 # Extract device info
203 device = agg_doc.get("device-system-document", {})
204 device_info = {
205 "instrument_serial_number": device.get("device-identifier"),
206 "instrument_model": device.get("model-number")
207 }
209 # Get technique documents
210 doc_key = f"{technique}-document"
211 for doc in agg_doc.get(doc_key, []):
212 meas_agg = doc.get("measurement-aggregate-document", {})
214 # Extract common metadata
215 common = {
216 "analyst": meas_agg.get("analyst"),
217 "measurement_datetime": meas_agg.get("measurement-time"),
218 **device_info
219 }
221 # Extract each measurement
222 for meas in meas_agg.get("measurement-document", []):
223 row = {**common}
225 # Flatten measurement fields
226 for key, value in meas.items():
227 if isinstance(value, dict) and "value" in value:
228 # Value datum pattern
229 col = key.replace("-", "_")
230 row[col] = value["value"]
231 if "unit" in value:
232 row[f"{col}_unit"] = value["unit"]
233 else:
234 row[key.replace("-", "_")] = value
236 rows.append(row)
238 return pd.DataFrame(rows)
240# Usage
241with open("asm_output.json") as f:
242 asm = json.load(f)
244df = flatten_asm(asm, "cell-counting")
245df.to_csv("flattened_output.csv", index=False)
246```

Python Implementation

import json
import pandas as pd

def flatten_asm(asm_dict, technique="cell-counting"):
    """
    Flatten ASM JSON to pandas DataFrame.
    
    Args:
        asm_dict: Parsed ASM JSON
        technique: ASM technique type
        
    Returns:
        pandas DataFrame with one row per measurement
    """
    rows = []
    
    # Get aggregate document
    agg_key = f"{technique}-aggregate-document"
    agg_doc = asm_dict.get(agg_key, {})
    
    # Extract device info
    device = agg_doc.get("device-system-document", {})
    device_info = {
        "instrument_serial_number": device.get("device-identifier"),
        "instrument_model": device.get("model-number")
    }
    
    # Get technique documents
    doc_key = f"{technique}-document"
    for doc in agg_doc.get(doc_key, []):
        meas_agg = doc.get("measurement-aggregate-document", {})
        
        # Extract common metadata
        common = {
            "analyst": meas_agg.get("analyst"),
            "measurement_datetime": meas_agg.get("measurement-time"),
            **device_info
        }
        
        # Extract each measurement
        for meas in meas_agg.get("measurement-document", []):
            row = {**common}
            
            # Flatten measurement fields
            for key, value in meas.items():
                if isinstance(value, dict) and "value" in value:
                    # Value datum pattern
                    col = key.replace("-", "_")
                    row[col] = value["value"]
                    if "unit" in value:
                        row[f"{col}_unit"] = value["unit"]
                else:
                    row[key.replace("-", "_")] = value
            
            rows.append(row)
    
    return pd.DataFrame(rows)

# Usage
with open("asm_output.json") as f:
    asm = json.load(f)

df = flatten_asm(asm, "cell-counting")
df.to_csv("flattened_output.csv", index=False)
Raw lines 179 to 247
248## LIMS Import Considerations
250When importing flattened data into a LIMS:
251- Match column names to your LIMS schema field names
252- Use ISO 8601 date format for timestamps
253- Ensure sample IDs match existing LIMS sample identifiers
254- Check if your LIMS expects units in separate columns or embedded in values

LIMS Import Considerations

When importing flattened data into a LIMS:

  • Match column names to your LIMS schema field names
  • Use ISO 8601 date format for timestamps
  • Ensure sample IDs match existing LIMS sample identifiers
  • Check if your LIMS expects units in separate columns or embedded in values
Raw lines 248 to 254