diff --git a/cellmap_flow/blockwise/blockwise_processor.py b/cellmap_flow/blockwise/blockwise_processor.py
index 32e10b9..92f72e1 100644
--- a/cellmap_flow/blockwise/blockwise_processor.py
+++ b/cellmap_flow/blockwise/blockwise_processor.py
@@ -1,4 +1,10 @@
import logging
+logging.getLogger().setLevel(logging.INFO)
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+logging.getLogger().setLevel(logging.INFO)
+logging.basicConfig(level=logging.INFO)
+
import subprocess
from pathlib import Path
@@ -16,8 +22,9 @@
from cellmap_flow.utils.config_utils import build_models, load_config
from cellmap_flow.utils.serilization_utils import get_process_dataset
from cellmap_flow.utils.ds import generate_singlescale_metadata
+from cellmap_flow.models.model_merger import get_model_merger
+
-logger = logging.getLogger(__name__)
class CellMapFlowBlockwiseProcessor:
@@ -98,15 +105,33 @@ def __init__(self, yaml_config: str, create=False):
# Support multiple models with model_mode
self.models = models
- self.model_mode = self.config.get("model_mode", "AND").upper()
- if self.model_mode not in ["AND", "OR", "SUM"]:
- raise Exception(
- f"Invalid model_mode: {self.model_mode}. Must be one of: AND, OR, SUM"
- )
+ self.model_mode_str = self.config.get("model_mode", "AND").upper()
+ try:
+ self.model_merger = get_model_merger(self.model_mode_str)
+ except ValueError as e:
+ raise Exception(str(e))
if len(models) > 1:
logger.info(
- f"Using {len(models)} models with merge mode: {self.model_mode}"
+ f"Using {len(models)} models with merge mode: {self.model_mode_str}"
+ )
+
+ # Support cross-channel processing
+ self.process_only = self.config.get("process_only", None)
+ self.cross_channels_mode = self.config.get("cross_channels", None)
+
+ if self.cross_channels_mode:
+ self.cross_channels_mode = self.cross_channels_mode.upper()
+ try:
+ self.cross_channels_merger = get_model_merger(self.cross_channels_mode)
+ except ValueError as e:
+ raise Exception(f"Invalid cross_channels setting: {e}")
+ else:
+ self.cross_channels_merger = None
+
+ if self.process_only:
+ logger.info(
+ f"Processing only channels: {self.process_only} with merge mode: {self.cross_channels_mode}"
)
self.model_config = models[0]
@@ -114,7 +139,7 @@ def __init__(self, yaml_config: str, create=False):
# this is zyx
block_shape = [int(x) for x in self.model_config.config.block_shape][:3]
- self.block_shape = self.config.get("block_size", block_shape)
+ self.block_shape = tuple(self.config.get("block_size", block_shape))
self.input_voxel_size = Coordinate(self.model_config.config.input_voxel_size)
self.output_voxel_size = Coordinate(self.model_config.config.output_voxel_size)
@@ -133,6 +158,9 @@ def __init__(self, yaml_config: str, create=False):
self.output_channel_names = self.channels
self.output_channel_indices = None
+ if not isinstance(self.output_channels, list):
+ self.output_channels = [self.output_channels]
+
if json_data:
g.input_norms, g.postprocess = get_process_dataset(json_data)
@@ -161,6 +189,12 @@ def __init__(self, yaml_config: str, create=False):
# Ensure we have output channels to iterate over
channels_to_create = self.output_channels if self.output_channels else []
+ if not isinstance(channels_to_create, list):
+ channels_to_create = [channels_to_create]
+
+ # check if there is two channels_to_create with same name
+ if len(channels_to_create) != len(set(channels_to_create)):
+ raise Exception(f"output_channels has duplicated channel names. channels: {channels_to_create}")
for channel in channels_to_create:
if create:
@@ -190,7 +224,7 @@ def __init__(self, yaml_config: str, create=False):
chunk_shape=(
self.block_shape
if len(final_output_shape) == 3
- else (len(channel_indices),) + tuple(self.block_shape)
+ else (len(channel_indices),) + self.block_shape
),
voxel_size=(
self.output_voxel_size
@@ -254,6 +288,8 @@ def __init__(self, yaml_config: str, create=False):
if "multiscales" in list(zg.attrs):
old_multiscales = zg.attrs["multiscales"]
if old_multiscales != zattrs["multiscales"]:
+ logger.info(f"Old multiscales: {old_multiscales}")
+ logger.info(f"New multiscales: {zattrs['multiscales']}")
raise ValueError(
f"multiscales attribute already exists in {z_store.path} and is different from the new one"
)
@@ -302,10 +338,15 @@ def process_fn(self, block):
model_outputs = []
for inferencer in self.inferencers:
output = inferencer.process_chunk(self.idi_raw, block.write_roi)
+ if self.process_only and self.cross_channels_merger:
+ # Extract only the specified channels
+ channel_outputs = [output[ch_idx] for ch_idx in self.process_only]
+ # Merge the extracted channels based on cross_channels mode
+ output = self.cross_channels_merger.merge(channel_outputs)
model_outputs.append(output)
# Merge outputs based on model_mode
- chunk_data = self._merge_model_outputs(model_outputs)
+ chunk_data = self.model_merger.merge(model_outputs)
chunk_data = chunk_data.astype(self.dtype)
@@ -372,38 +413,6 @@ def process_fn(self, block):
continue
array[array_write_roi] = predictions.to_ndarray(array_write_roi)
- def _merge_model_outputs(self, model_outputs):
- """
- Merge outputs from multiple models based on the configured model_mode.
-
- Args:
- model_outputs: List of numpy arrays from different models
-
- Returns:
- Merged numpy array
- """
- if self.model_mode == "AND":
- # Element-wise minimum (logical AND for binary, minimum for continuous)
- merged = model_outputs[0]
- for output in model_outputs[1:]:
- merged = np.minimum(merged, output)
- return merged
-
- elif self.model_mode == "OR":
- # Element-wise maximum (logical OR for binary, maximum for continuous)
- merged = model_outputs[0]
- for output in model_outputs[1:]:
- merged = np.maximum(merged, output)
- return merged
-
- elif self.model_mode == "SUM":
- # Sum all outputs and normalize by number of models
- merged = np.sum(model_outputs, axis=0) / len(model_outputs)
- return merged
-
- else:
- raise ValueError(f"Unknown model_mode: {self.model_mode}")
-
def client(self):
client = daisy.Client()
while True:
@@ -484,7 +493,7 @@ def run_worker():
f"prediction_logs/out.out",
"-e",
f"prediction_logs/out.err",
- "cellmap_flow_blockwise_processor",
+ "cellmap_flow_blockwise",
f"{yaml_config}",
"--client",
]
diff --git a/cellmap_flow/blockwise/cli.py b/cellmap_flow/blockwise/cli.py
index 28a3dc0..6aee0fc 100644
--- a/cellmap_flow/blockwise/cli.py
+++ b/cellmap_flow/blockwise/cli.py
@@ -1,6 +1,8 @@
import click
import logging
-
+import logging
+logging.getLogger().setLevel(logging.INFO)
+logging.basicConfig(level=logging.INFO)
from cellmap_flow.blockwise import CellMapFlowBlockwiseProcessor
@@ -32,3 +34,7 @@ def cli(yaml_config, client, log_level):
logger = logging.getLogger(__name__)
+
+
+if __name__ == "__main__":
+ cli()
diff --git a/cellmap_flow/cli/yaml_cli.py b/cellmap_flow/cli/yaml_cli.py
index 3d34b5b..929af72 100644
--- a/cellmap_flow/cli/yaml_cli.py
+++ b/cellmap_flow/cli/yaml_cli.py
@@ -172,23 +172,23 @@ def main(config_path: str, log_level: str, list_types: bool, validate_only: bool
# Build model configuration objects dynamically
logger.info("Building model configurations...")
- models = build_models(config["models"])
+ g.models_config = build_models(config["models"])
- logger.info(f"Configured {len(models)} model(s):")
- for i, model in enumerate(models, 1):
+ logger.info(f"Configured {len(g.models_config)} model(s):")
+ for i, model in enumerate(g.models_config, 1):
model_name = getattr(model, "name", None) or type(model).__name__
logger.info(f" {i}. {model_name} ({type(model).__name__})")
# Validation mode - exit without running
if validate_only:
click.echo("\n✓ Configuration is valid!")
- click.echo(f" - Models: {len(models)}")
+ click.echo(f" - Models: {len(g.models_config)}")
click.echo(f" - Data path: {data_path}")
click.echo(f" - Queue: {queue}")
return
# Run the models
- run_multiple(models, data_path, charge_group, queue)
+ run_multiple(g.models_config, data_path, charge_group, queue)
if __name__ == "__main__":
diff --git a/cellmap_flow/dashboard/PIPELINE_BUILDER.md b/cellmap_flow/dashboard/PIPELINE_BUILDER.md
new file mode 100644
index 0000000..553d193
--- /dev/null
+++ b/cellmap_flow/dashboard/PIPELINE_BUILDER.md
@@ -0,0 +1,222 @@
+# CellMapFlow Pipeline Builder
+
+A drag-and-drop visual pipeline builder for creating and managing CellMapFlow inference workflows.
+
+## Features
+
+- **Visual Pipeline Editor**: Drag-and-drop interface to connect normalization models and postprocessors
+- **Node-based Workflow**: Connect input → normalizers → postprocessors → output
+- **Parameter Configuration**: Set parameters for each processing step
+- **Export/Import**: Save pipelines as YAML or JSON and reload them later
+- **Real-time Validation**: Validate pipelines before applying them
+
+## Architecture
+
+### Frontend (React Flow)
+
+The pipeline builder uses **React Flow** for the visual node editor:
+
+**Components:**
+- [pipeline-builder.js](static/js/pipeline-builder.js) - Main React component
+- [pipeline-nodes.js](static/js/pipeline-nodes.js) - Node styling and layout
+- [pipeline-toolbar.js](static/js/pipeline-toolbar.js) - Toolbar for adding nodes
+- [pipeline-exporter.js](static/js/pipeline-exporter.js) - Export/Import functionality
+- [pipeline_builder.html](templates/pipeline_builder.html) - HTML template
+
+### Backend (Flask)
+
+Flask routes handle validation and application of pipelines:
+
+**Endpoints:**
+- `GET /pipeline-builder` - Render the pipeline builder UI
+- `POST /api/pipeline/validate` - Validate pipeline configuration
+- `POST /api/pipeline/apply` - Apply pipeline to inference
+
+## File Structure
+
+```
+cellmap_flow/dashboard/
+├── app.py # Flask app with pipeline routes
+├── package.json # npm dependencies
+├── static/
+│ └── js/
+│ ├── pipeline-builder.js # Main React component
+│ ├── pipeline-nodes.js # Node components
+│ ├── pipeline-toolbar.js # Toolbar component
+│ ├── pipeline-exporter.js # Export/import logic
+│ └── index-pipeline.js # Entry point
+└── templates/
+ └── pipeline_builder.html # HTML template
+```
+
+## Usage
+
+### Access the Pipeline Builder
+
+Navigate to: `http://localhost:PORT/pipeline-builder`
+
+### Creating a Pipeline
+
+1. **Add Normalizers**: Select from dropdown in toolbar to add normalization steps
+2. **Add Postprocessors**: Select from dropdown to add post-processing steps
+3. **Connect Nodes**: Drag from output handle to input handle to connect steps
+4. **Configure Parameters**: Click a node and enter JSON parameters in the panel
+5. **Apply**: Use the "Apply Pipeline" button to use the configuration
+
+### Exporting a Pipeline
+
+**As YAML:**
+```yaml
+input_normalizers:
+ - name: StandardNormalizer
+ params:
+ mean: 0.5
+ std: 0.1
+postprocessors:
+ - name: InstanceSegmentation
+ params:
+ threshold: 0.5
+```
+
+**As JSON:**
+```json
+{
+ "nodes": [...],
+ "edges": [...],
+ "timestamp": "2024-01-28T..."
+}
+```
+
+### Importing a Pipeline
+
+Click "Import Pipeline" and select a previously saved `.yaml` or `.json` file.
+
+## Configuration
+
+### Node Types
+
+- **Input** (green): Data entry point
+- **Normalizer** (blue): Input normalization steps
+- **Postprocessor** (pink): Post-processing operations
+- **Output** (gold): Final output
+
+### Parameter Format
+
+Parameters are specified as JSON. Examples:
+
+```json
+{
+ "clip_min": -1.0,
+ "clip_max": 1.0,
+ "bias": 1.0,
+ "multiplier": 127.5
+}
+```
+
+## Integration with CellMapFlow
+
+The pipeline builder integrates with your existing CellMapFlow infrastructure:
+
+1. **Normalizers**: Uses `get_input_normalizers()` from [input_normalize.py](../norm/input_normalize.py)
+2. **Postprocessors**: Uses `get_postprocessors_list()` from [postprocessors.py](../post/postprocessors.py)
+3. **Validation**: Validates against available models before applying
+4. **Application**: Applies to global state (`g.input_norms`, `g.postprocess`)
+
+## API Reference
+
+### GET /pipeline-builder
+
+Returns the pipeline builder UI with available normalizers and postprocessors.
+
+**Response:**
+- HTML page with React Flow editor
+
+### POST /api/pipeline/validate
+
+Validates a pipeline configuration.
+
+**Request:**
+```json
+{
+ "input_normalizers": [
+ { "name": "StandardNormalizer", "params": {...} }
+ ],
+ "postprocessors": [
+ { "name": "InstanceSegmentation", "params": {...} }
+ ]
+}
+```
+
+**Response:**
+```json
+{
+ "valid": true,
+ "message": "Pipeline is valid"
+}
+```
+
+### POST /api/pipeline/apply
+
+Applies a validated pipeline to the current inference.
+
+**Request:** Same as validate
+
+**Response:**
+```json
+{
+ "message": "Pipeline applied successfully",
+ "normalizers_applied": 2,
+ "postprocessors_applied": 1
+}
+```
+
+## Development
+
+### Setup
+
+```bash
+cd cellmap_flow/dashboard
+npm install
+npm run build # Build for production
+npm run dev # Watch mode for development
+```
+
+### Building the Frontend
+
+React Flow components are bundled using webpack. Run `npm run build` to generate the bundled JavaScript.
+
+## Dependencies
+
+### Frontend
+- **React** 18.2+ - UI library
+- **React DOM** 18.2+ - DOM rendering
+- **React Flow** 11.10+ - Node-based visual editor
+
+### Backend
+- **Flask** - Web framework (already in CellMapFlow)
+- **Pydantic** - Configuration validation (already in CellMapFlow)
+
+## Future Enhancements
+
+- [ ] Real-time pipeline preview/simulation
+- [ ] Custom node templates for complex operations
+- [ ] Pipeline library/templates for common workflows
+- [ ] Performance profiling for pipeline execution
+- [ ] Undo/Redo functionality
+- [ ] Keyboard shortcuts for faster node creation
+- [ ] Search/filter for large model lists
+
+## Troubleshooting
+
+### Pipeline not applying
+- Check browser console for errors
+- Verify all node names match available normalizers/postprocessors
+- Ensure JSON parameter format is valid
+
+### Import fails
+- Ensure file is valid YAML or JSON
+- Check that all referenced models exist in your installation
+
+### Styling issues
+- Clear browser cache
+- Rebuild with `npm run build`
diff --git a/cellmap_flow/dashboard/app.py b/cellmap_flow/dashboard/app.py
index 901a1e9..154b406 100644
--- a/cellmap_flow/dashboard/app.py
+++ b/cellmap_flow/dashboard/app.py
@@ -6,12 +6,17 @@
from flask import Flask, request, jsonify, render_template
from flask_cors import CORS
import logging
+import subprocess
+import yaml
+import tempfile
+import re
from cellmap_flow.utils.web_utils import get_free_port
from cellmap_flow.norm.input_normalize import (
get_input_normalizers,
get_normalizations,
)
from cellmap_flow.post.postprocessors import get_postprocessors_list, get_postprocessors
+from cellmap_flow.models.model_merger import get_model_mergers_list
from cellmap_flow.utils.load_py import load_safe_config
from cellmap_flow.utils.scale_pyramid import get_raw_layer
from cellmap_flow.utils.web_utils import (
@@ -34,6 +39,12 @@
CORS(app)
NEUROGLANCER_URL = None
INFERENCE_SERVER = None
+
+# Blockwise task directory will be set from globals or use default
+def get_blockwise_tasks_dir():
+ tasks_dir = getattr(g, 'blockwise_tasks_dir', None) or os.path.expanduser("~/.cellmap_flow/blockwise_tasks")
+ os.makedirs(tasks_dir, exist_ok=True)
+ return tasks_dir
CUSTOM_CODE_FOLDER = os.path.expanduser(
os.environ.get(
"CUSTOM_CODE_FOLDER",
@@ -47,6 +58,7 @@ def index():
# Render the main page with tabs
input_norms = get_input_normalizers()
output_postprocessors = get_postprocessors_list()
+ model_mergers = get_model_mergers_list()
model_catalog = g.model_catalog
model_catalog["User"] = {j.model_name: "" for j in g.jobs}
default_post_process = {d.to_dict()["name"]: d.to_dict() for d in g.postprocess}
@@ -61,6 +73,7 @@ def index():
inference_servers=INFERENCE_SERVER,
input_normalizers=input_norms,
output_postprocessors=output_postprocessors,
+ model_mergers=model_mergers,
default_post_process=default_post_process,
default_input_norm=default_input_norm,
model_catalog=model_catalog,
@@ -68,6 +81,192 @@ def index():
)
+@app.route("/pipeline-builder")
+def pipeline_builder():
+ """Render the drag-and-drop pipeline builder interface with current state from globals"""
+ input_norms = get_input_normalizers()
+ output_postprocessors = get_postprocessors_list()
+
+ # Get available models from models_config (not from catalog)
+ available_models = {}
+ # if hasattr(g, 'models_config') and g.models_config:
+ # for model_config in g.models_config:
+ # model_dict = model_config.to_dict()
+ # available_models[model_config.name] = model_dict
+
+ logger.warning(f"\n{'='*80}")
+ logger.warning(f"AVAILABLE MODELS DEBUG:")
+ logger.warning(f" Initial available_models keys: {list(available_models.keys())}")
+ logger.warning(f" g.models_config: {g.models_config if hasattr(g, 'models_config') else 'NOT SET'}")
+ logger.warning(f" Sample model with config:")
+ for model_name, model_data in list(available_models.items())[:1]:
+ logger.warning(f" {model_name}: {model_data}")
+ models_with_config = {}
+ for model_name in available_models.keys():
+ # Find matching config (strip _server suffix for matching)
+ model_name_stripped = model_name.replace('_server', '')
+ for model_config in g.models_config:
+ config_name = getattr(model_config, 'name', '').replace('_server', '')
+ if config_name == model_name_stripped:
+ if hasattr(model_config, 'to_dict'):
+ models_with_config[model_name] = {
+ 'name': model_name,
+ 'config': model_config.to_dict()
+ }
+ break
+ # If no config found, just use the name
+ if model_name not in models_with_config:
+ models_with_config[model_name] = {'name': model_name}
+ available_models = models_with_config
+
+ # Check if we have stored pipeline state from previous apply
+ if hasattr(g, 'pipeline_normalizers') and len(g.pipeline_normalizers) > 0:
+ # Use stored pipeline state (includes IDs, positions, params)
+ current_normalizers = g.pipeline_normalizers
+ current_postprocessors = g.pipeline_postprocessors
+ current_models = g.pipeline_models
+ # Enrich current_models with config from g.models_config if available
+ if hasattr(g, 'models_config') and g.models_config:
+ for model_dict in current_models:
+ if 'config' not in model_dict:
+ # Strip _server suffix for matching
+ model_name = model_dict['name'].replace('_server', '')
+ for model_config in g.models_config:
+ config_name = getattr(model_config, 'name', '').replace('_server', '')
+ if config_name == model_name:
+ if hasattr(model_config, 'to_dict'):
+ model_dict['config'] = model_config.to_dict()
+ break
+ current_inputs = g.pipeline_inputs
+ current_outputs = g.pipeline_outputs
+ current_edges = g.pipeline_edges
+ else:
+ # Fall back to converting from globals.input_norms and globals.postprocess
+ current_normalizers = []
+ for idx, norm in enumerate(g.input_norms):
+ norm_dict = norm.to_dict() if hasattr(norm, 'to_dict') else {'name': str(norm)}
+ norm_name = norm_dict.get('name', str(norm))
+ # Extract params: all dict items except 'name'
+ params = {k: v for k, v in norm_dict.items() if k != 'name'}
+ current_normalizers.append({
+ 'id': f'norm-{idx}-{int(time.time()*1000)}',
+ 'name': norm_name,
+ 'params': params
+ })
+
+ # Current models (from jobs and models_config)
+ current_models = []
+ logger.warning(f"\n{'='*80}")
+ logger.warning(f"Building current_models from g.jobs:")
+ logger.warning(f" g.jobs count: {len(g.jobs)}")
+ logger.warning(f" g.models_config exists: {hasattr(g, 'models_config')}")
+ if hasattr(g, 'models_config'):
+ logger.warning(f" g.models_config count: {len(g.models_config) if g.models_config else 0}")
+ logger.warning(f" g.models_config type: {type(g.models_config)}")
+ logger.warning(f" g.models_config value: {g.models_config}")
+ if g.models_config:
+ logger.warning(f" g.models_config names: {[getattr(mc, 'name', 'NO_NAME') for mc in g.models_config]}")
+ for mc in g.models_config:
+ logger.warning(f" Config object: {mc}, has to_dict: {hasattr(mc, 'to_dict')}")
+
+ # If models_config is empty but we have jobs, try to get configs from model_catalog
+ if (not hasattr(g, 'models_config') or not g.models_config) and hasattr(g, 'model_catalog'):
+ logger.warning(f" models_config is empty, checking model_catalog for configs...")
+ # Check if available_models dict has configs
+ if available_models:
+ logger.warning(f" available_models has {len(available_models)} entries with potential configs")
+
+ for idx, job in enumerate(g.jobs):
+ if hasattr(job, 'model_name'):
+ logger.warning(f"\n Processing job {idx}: model_name={job.model_name}")
+ model_dict = {'id': f'model-{idx}-{int(time.time()*1000)}', 'name': job.model_name, 'params': {}}
+ # Try to find the corresponding ModelConfig to get full configuration
+ config_found = False
+
+ # First try g.models_config
+ if hasattr(g, 'models_config') and g.models_config:
+ # Strip _server suffix for matching
+ job_model_name = job.model_name.replace('_server', '')
+ for model_config in g.models_config:
+ model_config_name = getattr(model_config, 'name', None)
+ config_name_stripped = model_config_name.replace('_server', '') if model_config_name else None
+ logger.warning(f" Checking model_config: {model_config_name} (stripped: {config_name_stripped}) vs job: {job.model_name} (stripped: {job_model_name})")
+ if config_name_stripped and config_name_stripped == job_model_name:
+ # Export the full model config using to_dict()
+ if hasattr(model_config, 'to_dict'):
+ model_dict['config'] = model_config.to_dict()
+ logger.warning(f" ✓ Config attached from models_config: {model_dict['config']}")
+ config_found = True
+ break
+
+ # Fallback: check available_models dict (which was enriched earlier)
+ if not config_found and available_models:
+ job_model_name = job.model_name.replace('_server', '')
+ for model_name, model_data in available_models.items():
+ model_name_stripped = model_name.replace('_server', '')
+ logger.warning(f" Checking available_models: {model_name} (stripped: {model_name_stripped}) vs job: {job.model_name} (stripped: {job_model_name})")
+ if model_name_stripped == job_model_name and isinstance(model_data, dict) and 'config' in model_data:
+ model_dict['config'] = model_data['config']
+ logger.warning(f" ✓ Config attached from available_models: {model_dict['config']}")
+ config_found = True
+ break
+
+ # Second fallback: check previously saved pipeline_model_configs
+ if not config_found and hasattr(g, 'pipeline_model_configs'):
+ job_model_name = job.model_name.replace('_server', '')
+ for saved_name, saved_config in g.pipeline_model_configs.items():
+ saved_name_stripped = saved_name.replace('_server', '')
+ logger.warning(f" Checking pipeline_model_configs: {saved_name} (stripped: {saved_name_stripped}) vs job: {job.model_name} (stripped: {job_model_name})")
+ if saved_name_stripped == job_model_name:
+ model_dict['config'] = saved_config
+ logger.warning(f" ✓ Config attached from pipeline_model_configs: {model_dict['config']}")
+ config_found = True
+ break
+
+ if not config_found:
+ logger.warning(f" ✗ No matching config found for {job.model_name}")
+ logger.warning(f" TIP: Import a YAML with full model configs to populate g.pipeline_model_configs")
+ current_models.append(model_dict)
+ logger.warning(f"{'='*80}\n")
+
+ current_postprocessors = []
+ for idx, post in enumerate(g.postprocess):
+ post_dict = post.to_dict() if hasattr(post, 'to_dict') else {'name': str(post)}
+ post_name = post_dict.get('name', str(post))
+ # Extract params: all dict items except 'name'
+ params = {k: v for k, v in post_dict.items() if k != 'name'}
+ current_postprocessors.append({
+ 'id': f'post-{idx}-{int(time.time()*1000)}',
+ 'name': post_name,
+ 'params': params
+ })
+
+ current_inputs = []
+ current_outputs = []
+ current_edges = []
+
+ # Get current dataset_path from globals
+ dataset_path = getattr(g, 'dataset_path', None) or ''
+
+ # Get available model mergers
+ model_mergers = get_model_mergers_list()
+
+ return render_template(
+ "pipeline_builder_v2.html",
+ input_normalizers=input_norms or {},
+ available_models=available_models or {},
+ output_postprocessors=output_postprocessors or {},
+ model_mergers=model_mergers or {},
+ current_normalizers=current_normalizers,
+ current_models=current_models,
+ current_postprocessors=current_postprocessors,
+ current_inputs=current_inputs,
+ current_outputs=current_outputs,
+ current_edges=current_edges,
+ dataset_path=dataset_path,
+ )
+
+
def is_output_segmentation():
if len(g.postprocess) == 0:
return False
@@ -177,6 +376,464 @@ def process():
)
+@app.route("/api/pipeline/validate", methods=["POST"])
+def validate_pipeline():
+ """Validate a pipeline configuration"""
+ try:
+ data = request.get_json()
+
+ # Validate normalizers
+ normalizer_names = [n.get("name") for n in data.get("input_normalizers", [])]
+ available_norms = get_input_normalizers()
+ # Extract just the normalizer names from the list of dicts
+ available_norm_names = [norm["name"] for norm in available_norms]
+ for norm_name in normalizer_names:
+ if norm_name not in available_norm_names:
+ return jsonify(
+ {"valid": False, "error": f"Unknown normalizer: {norm_name}"}
+ ), 400
+
+ # Validate postprocessors
+ processor_names = [p.get("name") for p in data.get("postprocessors", [])]
+ available_procs = get_postprocessors_list()
+ # Extract just the postprocessor names from the list of dicts
+ available_proc_names = [proc["name"] for proc in available_procs]
+ for proc_name in processor_names:
+ if proc_name not in available_proc_names:
+ return jsonify(
+ {"valid": False, "error": f"Unknown postprocessor: {proc_name}"}
+ ), 400
+
+ return jsonify({"valid": True, "message": "Pipeline is valid"})
+
+ except Exception as e:
+ logger.error(f"Error validating pipeline: {e}")
+ return jsonify({"valid": False, "error": str(e)}), 500
+
+
+@app.route("/api/dataset-path", methods=["GET", "POST"])
+def dataset_path_api():
+ """Get or set the dataset path in globals"""
+ if request.method == "GET":
+ dataset_path = getattr(g, 'dataset_path', None) or ''
+ return jsonify({'dataset_path': dataset_path})
+ elif request.method == "POST":
+ data = request.get_json()
+ dataset_path = data.get('dataset_path', '')
+ g.dataset_path = dataset_path
+ logger.warning(f"Dataset path updated to: {dataset_path}")
+ return jsonify({'success': True, 'dataset_path': g.dataset_path})
+
+
+@app.route("/api/blockwise-config", methods=["GET", "POST"])
+def blockwise_config_api():
+ """Get or set blockwise configuration in globals"""
+ if request.method == "GET":
+ return jsonify({
+ 'queue': g.queue,
+ 'charge_group': g.charge_group,
+ 'nb_cores_master': g.nb_cores_master,
+ 'nb_cores_worker': g.nb_cores_worker,
+ 'nb_workers': g.nb_workers,
+ 'tmp_dir': g.tmp_dir,
+ 'blockwise_tasks_dir': g.blockwise_tasks_dir
+ })
+ elif request.method == "POST":
+ data = request.get_json()
+ g.queue = data.get('queue')
+ g.charge_group = data.get('charge_group')
+ g.nb_cores_master = int(data.get('nb_cores_master'))
+ g.nb_cores_worker = int(data.get('nb_cores_worker'))
+ g.nb_workers = int(data.get('nb_workers'))
+ g.tmp_dir = data.get('tmp_dir')
+ g.blockwise_tasks_dir = data.get('blockwise_tasks_dir')
+ logger.warning(f"Blockwise config updated: queue={g.queue}, charge_group={g.charge_group}, cores_master={g.nb_cores_master}, cores_worker={g.nb_cores_worker}, workers={g.nb_workers}, tmp_dir={g.tmp_dir}, blockwise_tasks_dir={g.blockwise_tasks_dir}")
+ return jsonify({'success': True, 'config': {
+ 'queue': g.queue,
+ 'charge_group': g.charge_group,
+ 'nb_cores_master': g.nb_cores_master,
+ 'nb_cores_worker': g.nb_cores_worker,
+ 'nb_workers': g.nb_workers,
+ 'tmp_dir': g.tmp_dir,
+ 'blockwise_tasks_dir': g.blockwise_tasks_dir
+ }})
+
+
+@app.route("/api/pipeline/apply", methods=["POST"])
+def apply_pipeline():
+ """Apply a pipeline configuration to the current inference"""
+ try:
+ data = request.get_json()
+ logger.warning(f"\n{'='*80}")
+ logger.warning(f"APPLY PIPELINE - Received data:")
+ logger.warning(f" Input normalizers: {data.get('input_normalizers', [])}")
+ logger.warning(f" Postprocessors: {data.get('postprocessors', [])}")
+
+ # Validate first
+ validation = validate_pipeline_config(data)
+ if not validation["valid"]:
+ return jsonify(validation), 400
+
+ # Apply normalizers
+ input_norms_config = {
+ n["name"]: n.get("params", {}) for n in data.get("input_normalizers", [])
+ }
+ logger.warning(f"\nNormalizers config dict: {input_norms_config}")
+ g.input_norms = get_normalizations(input_norms_config)
+
+ # Apply postprocessors
+ postprocs_config = {
+ p["name"]: p.get("params", {}) for p in data.get("postprocessors", [])
+ }
+ logger.warning(f"Postprocessors config dict: {postprocs_config}")
+ g.postprocess = get_postprocessors(postprocs_config)
+
+ # Save complete pipeline visual state to globals
+ g.pipeline_inputs = data.get("inputs", [])
+ g.pipeline_outputs = data.get("outputs", [])
+ g.pipeline_edges = data.get("edges", [])
+ g.pipeline_normalizers = data.get("input_normalizers", [])
+ g.pipeline_models = data.get("models", [])
+ g.pipeline_postprocessors = data.get("postprocessors", [])
+
+ # Also save model configs separately for easier access
+ if not hasattr(g, 'pipeline_model_configs'):
+ g.pipeline_model_configs = {}
+ for model in data.get("models", []):
+ if 'config' in model and model['config']:
+ g.pipeline_model_configs[model['name']] = model['config']
+
+ # Log the updated globals state
+ logger.warning(f"\n{'='*80}")
+ logger.warning(f"UPDATED GLOBALS (g) STATE:")
+ logger.warning(f"{'='*80}")
+ logger.warning(f"\ng.input_norms ({len(g.input_norms)} items):")
+ for idx, norm in enumerate(g.input_norms):
+ logger.warning(f" [{idx}] {norm}")
+
+ logger.warning(f"\ng.postprocess ({len(g.postprocess)} items):")
+ for idx, post in enumerate(g.postprocess):
+ logger.warning(f" [{idx}] {post}")
+
+ logger.warning(f"\ng.jobs ({len(g.jobs)} items):")
+ for idx, job in enumerate(g.jobs):
+ logger.warning(f" [{idx}] model_name={getattr(job, 'model_name', 'N/A')}, host={getattr(job, 'host', 'N/A')}")
+
+ logger.warning(f"\ng.pipeline_inputs ({len(g.pipeline_inputs)} items): {g.pipeline_inputs}")
+ logger.warning(f"\ng.pipeline_outputs ({len(g.pipeline_outputs)} items): {g.pipeline_outputs}")
+ logger.warning(f"\ng.pipeline_edges ({len(g.pipeline_edges)} items): {g.pipeline_edges}")
+ logger.warning(f"\ng.pipeline_normalizers ({len(g.pipeline_normalizers)} items): {g.pipeline_normalizers}")
+ logger.warning(f"\ng.pipeline_models ({len(g.pipeline_models)} items): {g.pipeline_models}")
+ logger.warning(f"\ng.pipeline_postprocessors ({len(g.pipeline_postprocessors)} items): {g.pipeline_postprocessors}")
+
+ logger.warning(f"{'='*80}\n")
+
+ return jsonify({
+ "message": "Pipeline applied successfully",
+ "normalizers_applied": len(g.input_norms),
+ "postprocessors_applied": len(g.postprocess),
+ })
+
+ except Exception as e:
+ logger.error(f"Error applying pipeline: {e}")
+ return jsonify({"error": str(e)}), 500
+
+
+def validate_pipeline_config(config):
+ """Helper function to validate pipeline configuration"""
+ try:
+ normalizer_names = [n.get("name") for n in config.get("input_normalizers", [])]
+ available_norms = get_input_normalizers()
+ # Extract just the normalizer names from the list of dicts
+ available_norm_names = [norm["name"] for norm in available_norms]
+ for norm_name in normalizer_names:
+ if norm_name not in available_norm_names:
+ return {"valid": False, "error": f"Unknown normalizer: {norm_name}"}
+
+ processor_names = [p.get("name") for p in config.get("postprocessors", [])]
+ available_procs = get_postprocessors_list()
+ # Extract just the postprocessor names from the list of dicts
+ available_proc_names = [proc["name"] for proc in available_procs]
+ for proc_name in processor_names:
+ if proc_name not in available_proc_names:
+ return {"valid": False, "error": f"Unknown postprocessor: {proc_name}"}
+
+ return {"valid": True}
+
+ except Exception as e:
+ return {"valid": False, "error": str(e)}
+
+
+@app.route("/api/blockwise/validate", methods=["POST"])
+def validate_blockwise():
+ """Validate if pipeline is ready for blockwise processing"""
+ try:
+ data = request.get_json()
+ pipeline = data.get("pipeline", {})
+
+ # Check required components
+ if not pipeline.get("inputs") or len(pipeline["inputs"]) == 0:
+ return {"valid": False, "error": "No input nodes defined"}
+
+ if not pipeline.get("outputs") or len(pipeline["outputs"]) == 0:
+ return {"valid": False, "error": "No output nodes defined"}
+
+ if not pipeline.get("models") or len(pipeline["models"]) == 0:
+ return {"valid": False, "error": "No models defined"}
+
+ # Check blockwise config
+ if not pipeline.get("blockwise_config") or len(pipeline["blockwise_config"]) == 0:
+ return {"valid": False, "error": "No blockwise configuration defined"}
+
+ # Check input has dataset_path
+ input_node = pipeline["inputs"][0]
+ if not input_node.get("params", {}).get("dataset_path"):
+ return {"valid": False, "error": "Input node missing dataset_path"}
+
+ # Check output has dataset_path
+ output_node = pipeline["outputs"][0]
+ if not output_node.get("params", {}).get("dataset_path"):
+ return {"valid": False, "error": "Output node missing dataset_path"}
+
+ logger.info("Pipeline validation passed")
+ return {"valid": True, "message": "Pipeline is ready for blockwise processing"}
+
+ except Exception as e:
+ logger.error(f"Validation error: {str(e)}")
+ return {"valid": False, "error": str(e)}
+
+
+@app.route("/api/blockwise/generate", methods=["POST"])
+def generate_blockwise_task():
+ """Generate blockwise task YAML files"""
+ try:
+ data = request.get_json()
+ pipeline = data.get("pipeline", {})
+
+ # First validate
+ validation = validate_blockwise()
+ if not validation.get("valid"):
+ return {"success": False, "error": validation.get("error")}
+
+ # Get blockwise config
+ blockwise_config = pipeline["blockwise_config"][0]
+ input_node = pipeline["inputs"][0]
+ output_node = pipeline["outputs"][0]
+
+ # Create task YAML content
+ task_name = f"cellmap_flow_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+ task_yaml = {
+ "data_path": input_node["params"]["dataset_path"],
+ "output_path": output_node["params"]["dataset_path"],
+ "task_name": task_name,
+ "charge_group": blockwise_config["params"]["charge_group"],
+ "queue": blockwise_config["params"]["queue"],
+ "workers": blockwise_config["params"]["nb_workers"],
+ "cpu_workers": blockwise_config["params"]["nb_cores_worker"],
+ "tmp_dir": blockwise_config["params"]["tmp_dir"],
+ "models": [],
+ "input_normalizers": [],
+ "postprocessors": []
+ }
+
+ # Add model_mode if multiple models are present and a merge mode is selected
+ model_count = len(pipeline.get("models", []))
+ model_mode = pipeline.get("model_mode", "")
+ if model_count > 1 and model_mode:
+ task_yaml["model_mode"] = model_mode
+ logger.info(f"Adding model_mode: {model_mode} for {model_count} models")
+
+ # Add models with full config
+ for model in pipeline.get("models", []):
+ model_entry = {
+ "name": model.get("name"),
+ **model.get("params", model.get("config", {}))
+ }
+ # Parse string representations of lists/tuples back to actual lists for specific fields
+ import ast
+ import re
+ for field in ["channels", "input_size", "output_size", "input_voxel_size", "output_voxel_size"]:
+ if field in model_entry:
+ value = model_entry[field]
+ # If it's already a list, keep it
+ if isinstance(value, (list, tuple)):
+ model_entry[field] = list(value)
+ logger.info(f"Field {field} is already a list: {model_entry[field]}")
+ # If it's a string that looks like a list/tuple, parse it
+ elif isinstance(value, str):
+ value_stripped = value.strip().strip("'\"") # Remove outer quotes
+ if (value_stripped.startswith('[') or value_stripped.startswith('(')) and \
+ (value_stripped.endswith(']') or value_stripped.endswith(')')):
+ try:
+ # Fix unquoted identifiers: convert [mito] to ['mito']
+ # Replace word characters not inside quotes with quoted versions
+ fixed_value = re.sub(r'\b([a-zA-Z_][a-zA-Z0-9_]*)\b', r"'\1'", value_stripped)
+ # Remove duplicate quotes: ''mito'' -> 'mito'
+ fixed_value = re.sub(r"''+", "'", fixed_value)
+ logger.info(f"Fixing {field}: {value_stripped!r} -> {fixed_value!r}")
+
+ parsed = ast.literal_eval(fixed_value)
+ if isinstance(parsed, (list, tuple)):
+ model_entry[field] = list(parsed)
+ logger.info(f"Parsed {field} from string {value!r} to list {model_entry[field]}")
+ except Exception as e:
+ logger.warning(f"Failed to parse {field}: {value}, error: {e}")
+
+ task_yaml["models"].append(model_entry)
+
+ # Add normalizers
+ for norm in pipeline.get("normalizers", []):
+ norm_entry = {
+ "name": norm.get("name"),
+ "params": norm.get("params", {})
+ }
+ task_yaml["input_normalizers"].append(norm_entry)
+
+ # Add postprocessors
+ for post in pipeline.get("postprocessors", []):
+ post_entry = {
+ "name": post.get("name"),
+ "params": post.get("params", {})
+ }
+ task_yaml["postprocessors"].append(post_entry)
+
+ # Add output_channels from OUTPUT node if configured
+ output_channels = output_node.get("params", {}).get("output_channels", [])
+ if output_channels and isinstance(output_channels, list) and len(output_channels) > 0:
+ task_yaml["output_channels"] = output_channels
+ logger.info(f"Adding output_channels to YAML: {output_channels}")
+
+ # Convert to YAML format with proper list handling
+ yaml_content = yaml.dump(task_yaml, default_flow_style=False, allow_unicode=True)
+
+ # Save to file
+ yaml_filename = f"{task_name}.yaml"
+ tasks_dir = get_blockwise_tasks_dir()
+ yaml_path = os.path.join(tasks_dir, yaml_filename)
+ with open(yaml_path, 'w') as f:
+ f.write(yaml_content)
+
+ logger.info(f"Generated blockwise task YAML at: {yaml_path}")
+ logger.info(f"Task YAML content:\n{yaml_content}")
+
+ return {
+ "success": True,
+ "task_yaml": yaml_content,
+ "task_config": task_yaml,
+ "task_path": yaml_path,
+ "task_name": task_name,
+ "message": "Blockwise task generated successfully"
+ }
+
+ except Exception as e:
+ logger.error(f"Task generation error: {str(e)}")
+ return {"success": False, "error": str(e)}
+
+
+@app.route("/api/blockwise/precheck", methods=["POST"])
+def precheck_blockwise_task():
+ """Precheck blockwise task configuration"""
+ try:
+ from cellmap_flow.blockwise.blockwise_processor import CellMapFlowBlockwiseProcessor
+
+ data = request.get_json()
+ pipeline = data.get("pipeline", {})
+
+ # First validate
+ validation = validate_blockwise()
+ if not validation.get("valid"):
+ return {"success": False, "error": validation.get("error")}
+
+ # Generate task YAML first
+ gen_result = generate_blockwise_task()
+ if not gen_result.get("success"):
+ return {"success": False, "error": gen_result.get("error")}
+
+ yaml_path = gen_result.get("task_path")
+
+ # Try to instantiate the processor to validate configuration
+ try:
+ _ = CellMapFlowBlockwiseProcessor(yaml_path, create=True)
+ logger.info(f"Blockwise precheck passed for: {yaml_path}")
+ return {
+ "success": True,
+ "message": "success"
+ }
+ except Exception as e:
+ logger.error(f"Blockwise precheck failed: {str(e)}")
+ return {"success": False, "error": str(e)}
+
+ except Exception as e:
+ logger.error(f"Precheck error: {str(e)}")
+ return {"success": False, "error": str(e)}
+
+
+@app.route("/api/blockwise/submit", methods=["POST"])
+def submit_blockwise_task():
+ """Submit blockwise task to LSF"""
+ try:
+ data = request.get_json()
+ pipeline = data.get("pipeline", {})
+ job_name = data.get("job_name", f"cellmap_flow_{int(time.time())}")
+
+ # First validate
+ validation = validate_blockwise()
+ if not validation.get("valid"):
+ return {"success": False, "error": validation.get("error")}
+
+ # Generate task YAML
+ gen_result = generate_blockwise_task()
+ if not gen_result.get("success"):
+ return {"success": False, "error": gen_result.get("error")}
+
+ yaml_path = gen_result["task_path"]
+ blockwise_config = pipeline["blockwise_config"][0]
+
+ # Build bsub command
+ cores_master = blockwise_config["params"]["nb_cores_master"]
+ charge_group = blockwise_config["params"]["charge_group"]
+ queue = blockwise_config["params"]["queue"]
+
+ bsub_cmd = [
+ "bsub",
+ "-J", job_name,
+ "-n", str(cores_master),
+ "-P", charge_group,
+ # "-q", queue,
+ "python", "-m", "cellmap_flow.blockwise.cli",
+ yaml_path
+ ]
+
+ logger.info(f"Submitting LSF job: {' '.join(bsub_cmd)}")
+
+ # Submit job - use same environment as parent process
+ result = subprocess.run(bsub_cmd, capture_output=True, text=True, env=os.environ)
+
+ if result.returncode == 0:
+ output = result.stdout.strip()
+ logger.info(f"Job submitted successfully: {output}")
+
+ # Extract job ID from bsub output (format: "Job <12345> is submitted")
+ match = re.search(r'<(\d+)>', output)
+ job_id = match.group(1) if match else "unknown"
+
+ return {
+ "success": True,
+ "job_id": job_id,
+ "task_path": yaml_path,
+ "command": " ".join(bsub_cmd),
+ "message": f"Task submitted as job {job_id}"
+ }
+ else:
+ error_msg = result.stderr or result.stdout
+ logger.error(f"LSF submission failed: {error_msg}")
+ return {"success": False, "error": f"LSF error: {error_msg}"}
+
+ except Exception as e:
+ logger.error(f"Submission error: {str(e)}")
+ return {"success": False, "error": str(e)}
+
+
def create_and_run_app(neuroglancer_url=None, inference_servers=None):
global NEUROGLANCER_URL, INFERENCE_SERVER
NEUROGLANCER_URL = neuroglancer_url
diff --git a/cellmap_flow/dashboard/package.json b/cellmap_flow/dashboard/package.json
new file mode 100644
index 0000000..168dba6
--- /dev/null
+++ b/cellmap_flow/dashboard/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "cellmap-flow-pipeline-builder",
+ "version": "0.1.0",
+ "description": "Drag-and-drop pipeline builder for CellMapFlow",
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "reactflow": "^11.10.0"
+ },
+ "devDependencies": {
+ "@babel/preset-react": "^7.22.0",
+ "@babel/preset-env": "^7.22.0",
+ "webpack": "^5.88.0",
+ "webpack-cli": "^5.1.0",
+ "babel-loader": "^9.1.3"
+ },
+ "scripts": {
+ "build": "webpack --mode production",
+ "dev": "webpack --mode development --watch"
+ }
+}
diff --git a/cellmap_flow/dashboard/static/css/pipeline_builder.css b/cellmap_flow/dashboard/static/css/pipeline_builder.css
new file mode 100644
index 0000000..57f55d4
--- /dev/null
+++ b/cellmap_flow/dashboard/static/css/pipeline_builder.css
@@ -0,0 +1,726 @@
+
+ :root {
+ --bg-primary: #0f172a;
+ --bg-secondary: #1e293b;
+ --bg-tertiary: #334155;
+ --text-primary: #f1f5f9;
+ --text-secondary: #cbd5e1;
+ --border-color: #475569;
+ --accent-blue: #3b82f6;
+ --accent-purple: #8b5cf6;
+ --accent-green: #10b981;
+ --accent-red: #ef4444;
+ }
+
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ height: 100vh;
+ overflow: hidden;
+ }
+
+ .app-container {
+ display: flex;
+ height: 100vh;
+ flex-direction: column;
+ }
+
+ .header {
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border-color);
+ padding: 16px 24px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+ }
+
+ .header h1 {
+ font-size: 20px;
+ font-weight: 600;
+ margin: 0;
+ }
+
+ .apply-btn {
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 500;
+ transition: all 0.2s;
+ }
+
+ .apply-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
+ }
+
+ .header-actions {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ }
+
+ .action-btn {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+ padding: 8px 14px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ transition: all 0.2s;
+ }
+
+ .action-btn:hover {
+ background: var(--bg-hover);
+ border-color: #64748b;
+ }
+
+ .action-btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ color: var(--text-secondary);
+ }
+
+ .action-btn:disabled:hover {
+ background: var(--bg-primary);
+ border-color: var(--border-color);
+ transform: none;
+ }
+
+ .import-btn {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+ padding: 8px 14px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ transition: all 0.2s;
+ display: inline-block;
+ }
+
+ .import-btn:hover {
+ background: var(--bg-hover);
+ border-color: #64748b;
+ }
+
+ .file-input {
+ display: none;
+ }
+
+ .main {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+ gap: 0;
+ }
+
+ /* Sidebar */
+ .sidebar {
+ width: 280px;
+ background: var(--bg-secondary);
+ border-right: 1px solid var(--border-color);
+ overflow-y: auto;
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ }
+
+ .library-section {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .section-title {
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: var(--text-secondary);
+ opacity: 0.7;
+ margin-bottom: 4px;
+ }
+
+ .section-toggle {
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ padding: 8px 12px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ transition: all 0.2s;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .section-toggle:hover {
+ background: var(--accent-blue);
+ border-color: var(--accent-blue);
+ }
+
+ .section-toggle.active {
+ background: var(--accent-blue);
+ border-color: var(--accent-blue);
+ }
+
+ .library-items {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ max-height: 200px;
+ overflow-y: auto;
+ display: none;
+ }
+
+ .library-items.show {
+ display: flex;
+ }
+
+ .library-item {
+ background: var(--bg-primary);
+ border: 1px solid var(--border-color);
+ padding: 8px 12px;
+ border-radius: 4px;
+ cursor: grab;
+ font-size: 12px;
+ transition: all 0.2s;
+ user-select: none;
+ }
+
+ .library-item:hover {
+ background: var(--accent-blue);
+ border-color: var(--accent-blue);
+ color: white;
+ }
+
+ .library-item:active {
+ cursor: grabbing;
+ opacity: 0.8;
+ }
+
+ /* Canvas */
+ .canvas {
+ flex: 1;
+ background: linear-gradient(135deg, #0f172a 0%, #1a1f35 100%);
+ overflow: auto;
+ padding: 32px;
+ position: relative;
+ }
+
+ .canvas-content {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ min-height: 600px;
+ }
+
+ /* SVG connections layer */
+ .connections-svg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 1;
+ }
+
+ .connection-path {
+ fill: none;
+ stroke: rgba(16, 185, 129, 0.6);
+ stroke-width: 3;
+ pointer-events: stroke;
+ cursor: pointer;
+ transition: all 0.2s;
+ }
+
+ .connection-path:hover {
+ stroke: rgba(16, 185, 129, 1);
+ stroke-width: 4;
+ filter: drop-shadow(0 0 8px rgba(16, 185, 129, 0.8));
+ }
+
+ .connection-path.dragging-connection {
+ stroke: rgba(59, 130, 246, 0.8);
+ stroke-dasharray: 5, 5;
+ animation: dash 0.5s linear infinite;
+ }
+
+ @keyframes dash {
+ to {
+ stroke-dashoffset: -10;
+ }
+ }
+
+ /* Nodes */
+ .node-box {
+ background: var(--bg-secondary);
+ border: 2px solid var(--border-color);
+ border-radius: 8px;
+ padding: 16px;
+ min-width: 280px;
+ max-width: 320px;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
+ transition: box-shadow 0.2s;
+ position: absolute;
+ cursor: grab;
+ user-select: none;
+ z-index: 10;
+ }
+
+ .node-box:active {
+ cursor: grabbing;
+ }
+
+ .node-box.dragging {
+ z-index: 1000;
+ box-shadow: 0 8px 24px rgba(59, 130, 246, 0.4);
+ opacity: 0.95;
+ }
+
+ .node-box.dirty {
+ border-color: var(--accent-blue);
+ box-shadow: 0 0 12px rgba(59, 130, 246, 0.3);
+ }
+
+ .node-box:hover {
+ border-color: var(--accent-blue);
+ box-shadow: 0 6px 16px rgba(59, 130, 246, 0.2);
+ }
+
+ /* Special INPUT/OUTPUT nodes */
+ .node-box.io-node {
+ min-width: 120px;
+ max-width: 1600px;
+ padding: 12px;
+ background: linear-gradient(135deg, var(--bg-tertiary), var(--bg-secondary));
+ }
+
+ .node-box.io-node.input-node {
+ border-color: var(--accent-purple);
+ }
+
+ .node-box.io-node.output-node {
+ border-color: var(--accent-green);
+ }
+
+ .node-box.io-node .node-input {
+ display: none;
+ }
+
+ .node-box.io-node.input-node .node-output {
+ display: block;
+ }
+
+ .node-box.io-node.output-node .node-input {
+ display: block;
+ }
+
+ .node-box.io-node.output-node .node-output {
+ display: none;
+ }
+
+ /* Configuration nodes */
+ .node-box.config-node {
+ min-width: 350px;
+ max-width: 450px;
+ background: linear-gradient(135deg, #2a2a3e, var(--bg-secondary));
+ border-color: #f59e0b;
+ }
+
+ .node-box.config-node:hover {
+ border-color: #fbbf24;
+ box-shadow: 0 6px 16px rgba(245, 158, 11, 0.2);
+ }
+
+ .node-box.config-node .node-input,
+ .node-box.config-node .node-output {
+ display: none;
+ }
+
+ /* Node connection dots */
+ .node-input, .node-output {
+ position: absolute;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 30% 30%, #10b981, #059669);
+ box-shadow: 0 0 8px rgba(16, 185, 129, 0.5), inset 0 1px 2px rgba(255,255,255,0.2);
+ cursor: crosshair;
+ transition: all 0.2s;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: 100;
+ pointer-events: all;
+ }
+
+ .node-input:hover, .node-output:hover {
+ transform: translateY(-50%) scale(1.4);
+ box-shadow: 0 0 16px rgba(16, 185, 129, 1), inset 0 1px 2px rgba(255,255,255,0.4);
+ }
+
+ .node-input {
+ left: -9px;
+ }
+
+ .node-output {
+ right: -9px;
+ }
+
+ .node-input.active, .node-output.active {
+ background: radial-gradient(circle at 30% 30%, #3b82f6, #2563eb);
+ box-shadow: 0 0 16px rgba(59, 130, 246, 1);
+ transform: translateY(-50%) scale(1.5);
+ }
+
+ .node-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid var(--border-color);
+ }
+
+ .node-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--text-primary);
+ }
+
+ .node-type {
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--text-secondary);
+ opacity: 0.7;
+ }
+
+ .node-actions {
+ display: flex;
+ gap: 6px;
+ }
+
+ .icon-btn {
+ background: none;
+ border: none;
+ color: var(--text-secondary);
+ cursor: pointer;
+ font-size: 16px;
+ padding: 4px 8px;
+ border-radius: 4px;
+ transition: all 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .icon-btn:hover {
+ color: var(--text-primary);
+ background: var(--bg-tertiary);
+ }
+
+ .icon-btn.delete:hover {
+ color: var(--accent-red);
+ }
+
+ .icon-btn.save {
+ display: none;
+ }
+
+ .node-box.dirty .icon-btn.save {
+ display: flex;
+ color: var(--accent-green);
+ }
+
+ .configure-btn {
+ background: var(--bg-tertiary);
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+ padding: 6px 10px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 500;
+ width: 100%;
+ transition: all 0.2s;
+ }
+
+ .configure-btn:hover {
+ background: var(--accent-blue);
+ border-color: var(--accent-blue);
+ color: white;
+ }
+
+ .selected-channels {
+ padding: 4px;
+ background: rgba(0, 0, 0, 0.2);
+ border-radius: 3px;
+ max-height: 50px;
+ overflow-y: auto;
+ word-break: break-word;
+ }
+
+ .node-box.dirty .icon-btn.save:hover {
+ color: white;
+ background: var(--accent-green);
+ }
+
+ .node-params {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .param-group {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ }
+
+ .param-label {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--text-secondary);
+ opacity: 0.8;
+ }
+
+ .param-input {
+ background: var(--bg-primary);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ padding: 3px 6px;
+ border-radius: 3px;
+ font-size: 11px;
+ font-family: 'Menlo', 'Monaco', monospace;
+ transition: all 0.2s;
+ height: 24px;
+ min-height: 24px;
+ }
+
+ .param-input:focus {
+ outline: none;
+ border-color: var(--accent-blue);
+ box-shadow: 0 0 8px rgba(59, 130, 246, 0.2);
+ }
+
+ .param-input:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+
+ /* Blockwise config fields */
+ .blockwise-fields {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .field-group {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ }
+
+ /* Message */
+ .message {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ padding: 12px 16px;
+ border-radius: 6px;
+ font-size: 13px;
+ max-width: 400px;
+ animation: slideIn 0.3s ease-out;
+ z-index: 10000;
+ }
+
+ .message.success {
+ background: rgb(16, 185, 129);
+ color: #cae7d5;
+ border: 1px solid rgba(16, 185, 129, 0.4);
+ }
+
+ .message.error {
+ background: rgba(239, 68, 68);
+ color: #fefefe;
+ border: 1px solid rgba(239, 68, 68, 0.4);
+ }
+
+ .message.info {
+ background: rgba(59, 130, 246, 0.2);
+ color: #93c5fd;
+ border: 1px solid rgba(59, 130, 246, 0.4);
+ }
+
+ @keyframes slideIn {
+ from {
+ transform: translateX(400px);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0);
+ opacity: 1;
+ }
+ }
+
+ /* Modal Styles */
+ .modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.7);
+ z-index: 5000;
+ }
+
+ .modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 5001;
+ }
+
+ .modal-content {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
+ display: flex;
+ flex-direction: column;
+ }
+
+ .modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px;
+ border-bottom: 1px solid var(--border-color);
+ }
+
+ .modal-header h2 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+ }
+
+ .modal-close-btn {
+ background: none;
+ border: none;
+ color: var(--text-secondary);
+ font-size: 20px;
+ cursor: pointer;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .modal-close-btn:hover {
+ color: var(--text-primary);
+ }
+
+ .modal-body {
+ padding: 16px;
+ flex: 1;
+ overflow-y: auto;
+ }
+
+ .modal-footer {
+ display: flex;
+ gap: 8px;
+ padding: 16px;
+ border-top: 1px solid var(--border-color);
+ justify-content: flex-end;
+ }
+
+ .channel-checkbox-group {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px;
+ border-radius: 4px;
+ margin-bottom: 8px;
+ background: var(--bg-tertiary);
+ }
+
+ .channel-checkbox-group input[type="checkbox"] {
+ cursor: pointer;
+ width: 16px;
+ height: 16px;
+ }
+
+ .channel-checkbox-group label {
+ cursor: pointer;
+ flex: 1;
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ }
+
+ /* Scrollbars */
+ ::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+ }
+
+ ::-webkit-scrollbar-track {
+ background: var(--bg-primary);
+ }
+
+ ::-webkit-scrollbar-thumb {
+ background: var(--border-color);
+ border-radius: 4px;
+ }
+
+ ::-webkit-scrollbar-thumb:hover {
+ background: #64748b;
+ }
+
+ /* Empty state */
+ .empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: var(--text-secondary);
+ opacity: 0.5;
+ gap: 12px;
+ }
+
+ .empty-state-icon {
+ font-size: 48px;
+ opacity: 0.3;
+ }
+
+ .empty-state-text {
+ font-size: 14px;
+ text-align: center;
+ max-width: 300px;
+ line-height: 1.5;
+ }
\ No newline at end of file
diff --git a/cellmap_flow/dashboard/static/js/bundle-pipeline.js b/cellmap_flow/dashboard/static/js/bundle-pipeline.js
new file mode 100644
index 0000000..381a974
--- /dev/null
+++ b/cellmap_flow/dashboard/static/js/bundle-pipeline.js
@@ -0,0 +1,324 @@
+// In-browser bundle for the pipeline builder — uses UMD React and ReactFlow globals
+// This file is transpiled by Babel in the browser (development convenience).
+
+// Defer accessing ReactFlow UMD globals until runtime to avoid errors
+let ReactFlowProvider, ReactFlow, addEdge, useNodesState, useEdgesState, Controls, Background, useReactFlow, Handle, Position;
+const { useCallback, useState } = React;
+
+const nodeTypes = {
+ normalizer: PipelineNode,
+ postprocessor: PipelineNode,
+ input: PipelineNode,
+ output: PipelineNode,
+};
+
+function PipelineNode({ data, selected, id }) {
+ const nodeColors = {
+ input: '#90EE90',
+ normalizer: '#87CEEB',
+ postprocessor: '#FFB6C1',
+ output: '#FFD700',
+ };
+ const bgColor = nodeColors[data.type] || '#fff';
+
+ return (
+ React.createElement('div', {
+ style: {
+ padding: '10px',
+ border: `2px ${selected ? 'blue' : '#ddd'} solid`,
+ borderRadius: '8px',
+ background: bgColor,
+ minWidth: '120px',
+ textAlign: 'center',
+ fontWeight: 'bold',
+ cursor: 'pointer',
+ },
+ },
+ data.type !== 'input' && React.createElement(Handle, { type: 'target', position: Position.Top }),
+ React.createElement('div', null, data.label),
+ data.type !== 'output' && React.createElement(Handle, { type: 'source', position: Position.Bottom })
+ )
+ );
+}
+
+function PipelineToolbar({
+ availableNormalizers,
+ availablePostprocessors,
+ onAddNormalizer,
+ onAddPostprocessor,
+}) {
+ return (
+ React.createElement('div', { style: {
+ padding: '10px',
+ background: '#f5f5f5',
+ borderBottom: '1px solid #ddd',
+ display: 'flex',
+ gap: '10px',
+ alignItems: 'center',
+ } },
+ React.createElement('label', { style: { fontWeight: 'bold' } }, 'Add Normalizer:'),
+ React.createElement('select', {
+ onChange: (e) => {
+ if (e.target.value) {
+ onAddNormalizer(e.target.value);
+ e.target.value = '';
+ }
+ }
+ },
+ React.createElement('option', { value: '' }, 'Select a normalizer...'),
+ availableNormalizers && Object.keys(availableNormalizers).map((norm) => React.createElement('option', { key: norm, value: norm }, norm))
+ ),
+
+ React.createElement('label', { style: { fontWeight: 'bold', marginLeft: '20px' } }, 'Add Postprocessor:'),
+ React.createElement('select', {
+ onChange: (e) => {
+ if (e.target.value) {
+ onAddPostprocessor(e.target.value);
+ e.target.value = '';
+ }
+ }
+ },
+ React.createElement('option', { value: '' }, 'Select a postprocessor...'),
+ availablePostprocessors && Object.keys(availablePostprocessors).map((post) => React.createElement('option', { key: post, value: post }, post))
+ )
+ )
+ );
+}
+
+function PipelineExporter({
+ selectedNode,
+ nodes,
+ edges,
+ onExport,
+ onImport,
+ onUpdateParams,
+ onDeleteNode,
+}) {
+ const [paramInputs, setParamInputs] = useState({});
+ const selectedNodeData = nodes.find((n) => n.id === selectedNode);
+
+ const applyParams = () => {
+ if (selectedNode && selectedNodeData) {
+ try {
+ const params = {};
+ Object.keys(paramInputs).forEach((key) => {
+ try {
+ params[key] = JSON.parse(paramInputs[key]);
+ } catch {
+ params[key] = paramInputs[key];
+ }
+ });
+ onUpdateParams(selectedNode, params);
+ setParamInputs({});
+ } catch (error) {
+ alert('Invalid parameter format: ' + error.message);
+ }
+ }
+ };
+
+ const exportToYAML = () => {
+ const workflow = {
+ input_normalizers: [],
+ postprocessors: [],
+ };
+ nodes.forEach((node) => {
+ if (node.type === 'normalizer') {
+ workflow.input_normalizers.push({ name: node.data.name, params: node.data.params || {} });
+ } else if (node.type === 'postprocessor') {
+ workflow.postprocessors.push({ name: node.data.name, params: node.data.params || {} });
+ }
+ });
+ const yamlContent = generateYAML(workflow);
+ downloadFile(yamlContent, 'pipeline.yaml', 'text/yaml');
+ };
+
+ const exportToJSON = () => {
+ const pipelineData = onExport();
+ const jsonContent = JSON.stringify(pipelineData, null, 2);
+ downloadFile(jsonContent, 'pipeline.json', 'application/json');
+ };
+
+ const importFromFile = (event) => {
+ const file = event.target.files[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ try {
+ const content = e.target.result;
+ const data = file.name.endsWith('.yaml') ? parseYAML(content) : JSON.parse(content);
+ onImport(data);
+ } catch (error) {
+ alert('Error importing file: ' + error.message);
+ }
+ };
+ reader.readAsText(file);
+ }
+ };
+
+ return (
+ React.createElement('div', { style: {
+ width: '300px',
+ padding: '15px',
+ background: '#fff',
+ borderLeft: '1px solid #ddd',
+ overflowY: 'auto',
+ display: 'flex',
+ flexDirection: 'column',
+ } },
+ React.createElement('h3', null, 'Pipeline Controls'),
+ selectedNodeData && selectedNodeData.type !== 'input' && selectedNodeData.type !== 'output' && (
+ React.createElement('div', { style: { marginBottom: '20px', padding: '10px', background: '#f9f9f9', borderRadius: '5px' } },
+ React.createElement('h4', { style: { marginTop: 0 } }, selectedNodeData.data.label),
+ React.createElement('p', { style: { fontSize: '12px', color: '#666' } }, `Node ID: ${selectedNode}`),
+ React.createElement('label', { style: { fontWeight: 'bold', fontSize: '12px' } }, 'Parameters:'),
+ React.createElement('div', { style: { marginTop: '8px', marginBottom: '10px' } },
+ React.createElement('input', {
+ type: 'text',
+ placeholder: '{"param1": 0.5}',
+ onChange: (e) => setParamInputs({ param_input: e.target.value }),
+ style: { width: '100%', padding: '5px', fontSize: '11px' },
+ })
+ ),
+ React.createElement('button', { onClick: applyParams, style: { width: '100%', padding: '6px', background: '#4CAF50', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '12px', marginBottom: '8px' } }, 'Apply Parameters'),
+ React.createElement('button', { onClick: () => onDeleteNode(selectedNode), style: { width: '100%', padding: '6px', background: '#f44336', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '12px' } }, 'Delete Node')
+ )
+ ),
+ React.createElement('div', { style: { marginTop: 'auto' } },
+ React.createElement('h4', null, 'Export Pipeline'),
+ React.createElement('button', { onClick: exportToYAML, style: { width: '100%', padding: '8px', background: '#2196F3', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', marginBottom: '8px' } }, 'Export as YAML'),
+ React.createElement('button', { onClick: exportToJSON, style: { width: '100%', padding: '8px', background: '#2196F3', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', marginBottom: '8px' } }, 'Export as JSON'),
+ React.createElement('h4', { style: { marginTop: '15px' } }, 'Import Pipeline'),
+ React.createElement('input', { type: 'file', accept: '.yaml,.json', onChange: importFromFile, style: { width: '100%', fontSize: '12px' } })
+ )
+ )
+ );
+}
+
+function PipelineBuilder({ availableNormalizers, availablePostprocessors }) {
+ const [nodes, setNodes, onNodesChange] = useNodesState([
+ { id: 'input', data: { label: 'Input Data', type: 'input' }, position: { x: 0, y: 0 }, type: 'input' },
+ { id: 'output', data: { label: 'Output', type: 'output' }, position: { x: 400, y: 300 }, type: 'output' },
+ ]);
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
+ const [selectedNode, setSelectedNode] = useState(null);
+ const reactFlowInstance = useReactFlow();
+
+ const onConnectCb = useCallback((connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]);
+
+ const addNormalizer = useCallback((normalizerName) => {
+ const id = `norm-${Date.now()}`;
+ const newNode = { id, type: 'normalizer', data: { label: normalizerName, type: 'normalizer', name: normalizerName, params: {} }, position: { x: 100, y: 150 } };
+ setNodes((nds) => [...nds, newNode]);
+ }, [setNodes]);
+
+ const addPostprocessor = useCallback((processorName) => {
+ const id = `post-${Date.now()}`;
+ const newNode = { id, type: 'postprocessor', data: { label: processorName, type: 'postprocessor', name: processorName, params: {} }, position: { x: 250, y: 150 } };
+ setNodes((nds) => [...nds, newNode]);
+ }, [setNodes]);
+
+ const updateNodeParams = useCallback((nodeId, params) => setNodes((nds) => nds.map((node) => node.id === nodeId ? { ...node, data: { ...node.data, params } } : node)), [setNodes]);
+
+ const deleteNode = useCallback((nodeId) => { setNodes((nds) => nds.filter((node) => node.id !== nodeId)); setEdges((eds) => eds.filter((edge) => edge.source !== nodeId && edge.target !== nodeId)); }, [setNodes, setEdges]);
+
+ const exportPipeline = useCallback(() => ({ nodes, edges, timestamp: new Date().toISOString() }), [nodes, edges]);
+ const importPipeline = useCallback((pipelineData) => { if (pipelineData.nodes) setNodes(pipelineData.nodes); if (pipelineData.edges) setEdges(pipelineData.edges); }, [setNodes, setEdges]);
+
+ return (
+ React.createElement('div', { style: { width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' } },
+ React.createElement(PipelineToolbar, { availableNormalizers, availablePostprocessors, onAddNormalizer: addNormalizer, onAddPostprocessor: addPostprocessor }),
+ React.createElement('div', { style: { flex: 1, display: 'flex' } },
+ React.createElement('div', { style: { flex: 1 } },
+ React.createElement(ReactFlow, { nodes, edges, onNodesChange, onEdgesChange, onConnect: onConnectCb, nodeTypes, onNodeClick: (event, node) => setSelectedNode(node.id) },
+ React.createElement(Background, null),
+ React.createElement(Controls, null)
+ )
+ ),
+ React.createElement(PipelineExporter, { selectedNode, nodes, edges, onExport: exportPipeline, onImport: importPipeline, onUpdateParams: updateNodeParams, onDeleteNode: deleteNode })
+ )
+ )
+ );
+}
+
+// Helpers
+function generateYAML(data) {
+ let yaml = '';
+ yaml += 'input_normalizers:\n';
+ data.input_normalizers.forEach((norm) => {
+ yaml += ` - name: ${norm.name}\n`;
+ yaml += ' params:\n';
+ Object.keys(norm.params).forEach((key) => { yaml += ` ${key}: ${JSON.stringify(norm.params[key])}\n`; });
+ });
+ yaml += 'postprocessors:\n';
+ data.postprocessors.forEach((post) => {
+ yaml += ` - name: ${post.name}\n`;
+ yaml += ' params:\n';
+ Object.keys(post.params).forEach((key) => { yaml += ` ${key}: ${JSON.stringify(post.params[key])}\n`; });
+ });
+ return yaml;
+}
+
+function parseYAML(yaml) {
+ const lines = yaml.split('\n');
+ const result = { input_normalizers: [], postprocessors: [] };
+ let currentSection = null;
+ let currentItem = null;
+ lines.forEach((line) => {
+ const trimmed = line.trim();
+ if (trimmed.startsWith('input_normalizers:')) currentSection = 'input_normalizers';
+ else if (trimmed.startsWith('postprocessors:')) currentSection = 'postprocessors';
+ else if (trimmed.startsWith('- name:')) { currentItem = { name: trimmed.replace('- name: ', ''), params: {} }; }
+ else if (trimmed.startsWith('name:') && !trimmed.startsWith('- name:')) { if (currentItem) currentItem.name = trimmed.replace('name: ', ''); }
+ else if (trimmed.startsWith('params:')) {}
+ else if (trimmed && currentItem && !trimmed.startsWith('-')) {
+ const [key, value] = trimmed.split(':').map((s) => s.trim());
+ if (key && value) {
+ try { currentItem.params[key] = JSON.parse(value); } catch { currentItem.params[key] = value; }
+ }
+ }
+ if ((trimmed.startsWith('- name:') || trimmed.startsWith('name:')) && currentItem && Object.keys(currentItem.params).length > 0) { if (currentSection) result[currentSection].push(currentItem); currentItem = null; }
+ });
+ if (currentItem && currentSection) result[currentSection].push(currentItem);
+ return result;
+}
+
+function downloadFile(content, filename, mimeType) {
+ const element = document.createElement('a');
+ element.setAttribute('href', 'data:' + mimeType + ';charset=utf-8,' + encodeURIComponent(content));
+ element.setAttribute('download', filename);
+ element.style.display = 'none';
+ document.body.appendChild(element);
+ element.click();
+ document.body.removeChild(element);
+}
+
+// Mount app
+document.addEventListener('DOMContentLoaded', function () {
+ const APP_CONFIG = window.APP_CONFIG || { availableNormalizers: {}, availablePostprocessors: {} };
+
+ // Ensure ReactFlow UMD global is available
+ const RF = window.ReactFlow;
+ if (!RF) {
+ console.error(
+ 'ReactFlow UMD not found on window.ReactFlow. Ensure ReactFlow script is loaded before bundle.'
+ );
+ return;
+ }
+
+ // Extract needed ReactFlow exports at runtime
+ ReactFlowProvider = RF.ReactFlowProvider;
+ ReactFlow = RF.ReactFlow;
+ addEdge = RF.addEdge;
+ useNodesState = RF.useNodesState;
+ useEdgesState = RF.useEdgesState;
+ Controls = RF.Controls;
+ Background = RF.Background;
+ useReactFlow = RF.useReactFlow;
+ Handle = RF.Handle;
+ Position = RF.Position;
+
+ ReactDOM.render(
+ React.createElement(ReactFlowProvider, null, React.createElement(PipelineBuilder, { availableNormalizers: APP_CONFIG.availableNormalizers, availablePostprocessors: APP_CONFIG.availablePostprocessors })),
+ document.getElementById('pipeline-root')
+ );
+});
diff --git a/cellmap_flow/dashboard/static/js/index-pipeline.js b/cellmap_flow/dashboard/static/js/index-pipeline.js
new file mode 100644
index 0000000..c0d3d35
--- /dev/null
+++ b/cellmap_flow/dashboard/static/js/index-pipeline.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { ReactFlowProvider } from 'reactflow';
+import { PipelineBuilder } from './pipeline-builder';
+
+// This will be injected by Flask
+const APP_CONFIG = window.APP_CONFIG || {
+ availableNormalizers: {},
+ availablePostprocessors: {},
+};
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('pipeline-root')
+);
diff --git a/cellmap_flow/dashboard/static/js/pipeline-builder.js b/cellmap_flow/dashboard/static/js/pipeline-builder.js
new file mode 100644
index 0000000..5c500af
--- /dev/null
+++ b/cellmap_flow/dashboard/static/js/pipeline-builder.js
@@ -0,0 +1,170 @@
+import React, { useCallback, useState, useRef } from 'react';
+import ReactFlow, {
+ Node,
+ Edge,
+ addEdge,
+ useNodesState,
+ useEdgesState,
+ Controls,
+ Background,
+ useReactFlow,
+ NodeTypes,
+} from 'reactflow';
+import 'reactflow/dist/style.css';
+import { PipelineNode } from './pipeline-nodes';
+import { PipelineToolbar } from './pipeline-toolbar';
+import { PipelineExporter } from './pipeline-exporter';
+
+const nodeTypes = {
+ normalizer: PipelineNode,
+ postprocessor: PipelineNode,
+ input: PipelineNode,
+ output: PipelineNode,
+};
+
+export const PipelineBuilder = ({ availableNormalizers, availablePostprocessors }) => {
+ const [nodes, setNodes, onNodesChange] = useNodesState([
+ {
+ id: 'input',
+ data: { label: 'Input Data', type: 'input' },
+ position: { x: 0, y: 0 },
+ type: 'input',
+ },
+ {
+ id: 'output',
+ data: { label: 'Output', type: 'output' },
+ position: { x: 400, y: 300 },
+ type: 'output',
+ },
+ ]);
+
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
+ const [selectedNode, setSelectedNode] = useState(null);
+ const reactFlowInstance = useReactFlow();
+
+ const onConnect = useCallback(
+ (connection) => {
+ setEdges((eds) => addEdge(connection, eds));
+ },
+ [setEdges]
+ );
+
+ const addNormalizer = useCallback(
+ (normalizerName) => {
+ const id = `norm-${Date.now()}`;
+ const newNode = {
+ id,
+ type: 'normalizer',
+ data: {
+ label: normalizerName,
+ type: 'normalizer',
+ name: normalizerName,
+ params: {},
+ },
+ position: { x: 100, y: 150 },
+ };
+ setNodes((nds) => [...nds, newNode]);
+ },
+ [setNodes]
+ );
+
+ const addPostprocessor = useCallback(
+ (processorName) => {
+ const id = `post-${Date.now()}`;
+ const newNode = {
+ id,
+ type: 'postprocessor',
+ data: {
+ label: processorName,
+ type: 'postprocessor',
+ name: processorName,
+ params: {},
+ },
+ position: { x: 250, y: 150 },
+ };
+ setNodes((nds) => [...nds, newNode]);
+ },
+ [setNodes]
+ );
+
+ const updateNodeParams = useCallback(
+ (nodeId, params) => {
+ setNodes((nds) =>
+ nds.map((node) =>
+ node.id === nodeId
+ ? { ...node, data: { ...node.data, params } }
+ : node
+ )
+ );
+ },
+ [setNodes]
+ );
+
+ const deleteNode = useCallback(
+ (nodeId) => {
+ setNodes((nds) => nds.filter((node) => node.id !== nodeId));
+ setEdges((eds) =>
+ eds.filter((edge) => edge.source !== nodeId && edge.target !== nodeId)
+ );
+ },
+ [setNodes, setEdges]
+ );
+
+ const exportPipeline = useCallback(() => {
+ return {
+ nodes,
+ edges,
+ timestamp: new Date().toISOString(),
+ };
+ }, [nodes, edges]);
+
+ const importPipeline = useCallback(
+ (pipelineData) => {
+ if (pipelineData.nodes) {
+ setNodes(pipelineData.nodes);
+ }
+ if (pipelineData.edges) {
+ setEdges(pipelineData.edges);
+ }
+ },
+ [setNodes, setEdges]
+ );
+
+ return (
+
+
+
+
+
+ setSelectedNode(node.id)}
+ >
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/cellmap_flow/dashboard/static/js/pipeline-exporter.js b/cellmap_flow/dashboard/static/js/pipeline-exporter.js
new file mode 100644
index 0000000..5997103
--- /dev/null
+++ b/cellmap_flow/dashboard/static/js/pipeline-exporter.js
@@ -0,0 +1,337 @@
+import React, { useState } from 'react';
+
+export const PipelineExporter = ({
+ selectedNode,
+ nodes,
+ edges,
+ onExport,
+ onImport,
+ onUpdateParams,
+ onDeleteNode,
+}) => {
+ const [paramInputs, setParamInputs] = useState({});
+
+ const selectedNodeData = nodes.find((n) => n.id === selectedNode);
+
+ const handleParamChange = (key, value) => {
+ setParamInputs({ ...paramInputs, [key]: value });
+ };
+
+ const saveNodeData = () => {
+ if (selectedNodeData && (selectedNodeData.type === 'input' || selectedNodeData.type === 'output')) {
+ alert(`${selectedNodeData.type.charAt(0).toUpperCase() + selectedNodeData.type.slice(1)} node saved.`);
+ }
+ };
+
+ const applyParams = () => {
+ if (selectedNode && selectedNodeData) {
+ try {
+ const params = {};
+ Object.keys(paramInputs).forEach((key) => {
+ try {
+ params[key] = JSON.parse(paramInputs[key]);
+ } catch {
+ params[key] = paramInputs[key];
+ }
+ });
+ onUpdateParams(selectedNode, params);
+ setParamInputs({});
+ } catch (error) {
+ alert('Invalid parameter format: ' + error.message);
+ }
+ }
+ };
+
+ const exportToYAML = async () => {
+ try {
+ // Fetch dataset_path from backend
+ const response = await fetch('/api/dataset_path');
+ const data = await response.json();
+ const datasetPath = data.dataset_path || '';
+
+ const pipelineData = onExport();
+
+ // Extract workflow structure
+ const workflow = {
+ input_normalizers: [],
+ postprocessors: [],
+ };
+
+ // Order nodes by edges
+ nodes.forEach((node) => {
+ if (node.type === 'normalizer') {
+ workflow.input_normalizers.push({
+ name: node.data.name,
+ params: node.data.params || {},
+ });
+ } else if (node.type === 'postprocessor') {
+ workflow.postprocessors.push({
+ name: node.data.name,
+ params: node.data.params || {},
+ });
+ }
+ });
+
+ const yamlContent = generateYAML(workflow, datasetPath);
+ downloadFile(yamlContent, 'pipeline.yaml', 'text/yaml');
+ } catch (error) {
+ console.error('Error exporting YAML:', error);
+ alert('Error exporting YAML: ' + error.message);
+ }
+ };
+
+ const exportToJSON = () => {
+ const pipelineData = onExport();
+ const jsonContent = JSON.stringify(pipelineData, null, 2);
+ downloadFile(jsonContent, 'pipeline.json', 'application/json');
+ };
+
+ const importFromFile = (event) => {
+ const file = event.target.files[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ try {
+ const content = e.target.result;
+ const data = file.name.endsWith('.yaml')
+ ? parseYAML(content)
+ : JSON.parse(content);
+ onImport(data);
+ } catch (error) {
+ alert('Error importing file: ' + error.message);
+ }
+ };
+ reader.readAsText(file);
+ }
+ };
+
+ return (
+
+
Pipeline Controls
+
+ {/* Node Inspector */}
+ {selectedNodeData && (
+
+
{selectedNodeData.data.label}
+
Node ID: {selectedNode}
+
Type: {selectedNodeData.type}
+
+ {(selectedNodeData.type === 'input' || selectedNodeData.type === 'output') ? (
+
+ ) : (
+ <>
+
+
+ setParamInputs({ param_input: e.target.value })}
+ style={{ width: '100%', padding: '5px', fontSize: '11px' }}
+ />
+
+
+
+
+
+ >
+ )}
+
+ )}
+
+ {/* Export/Import */}
+
+
Export Pipeline
+
+
+
+ Import Pipeline
+
+
+
+ );
+};
+
+// Helper functions
+const generateYAML = (data, datasetPath = '') => {
+ let yaml = '';
+
+ if (datasetPath) {
+ yaml += `dataset_path: ${datasetPath}\n`;
+ yaml += '\n';
+ }
+
+ yaml += 'input_normalizers:\n';
+ if (data.input_normalizers.length === 0) {
+ yaml += ' []\n';
+ } else {
+ data.input_normalizers.forEach((norm) => {
+ yaml += ` - name: ${norm.name}\n`;
+ yaml += ' params:\n';
+ if (Object.keys(norm.params).length === 0) {
+ yaml += ' {}\n';
+ } else {
+ Object.keys(norm.params).forEach((key) => {
+ yaml += ` ${key}: ${JSON.stringify(norm.params[key])}\n`;
+ });
+ }
+ });
+ }
+
+ yaml += 'postprocessors:\n';
+ if (data.postprocessors.length === 0) {
+ yaml += ' []\n';
+ } else {
+ data.postprocessors.forEach((post) => {
+ yaml += ` - name: ${post.name}\n`;
+ yaml += ' params:\n';
+ if (Object.keys(post.params).length === 0) {
+ yaml += ' {}\n';
+ } else {
+ Object.keys(post.params).forEach((key) => {
+ yaml += ` ${key}: ${JSON.stringify(post.params[key])}\n`;
+ });
+ }
+ });
+ }
+
+ return yaml;
+};
+
+const parseYAML = (yaml) => {
+ // Simple YAML parser for our use case
+ const lines = yaml.split('\n');
+ const result = {
+ input_normalizers: [],
+ postprocessors: [],
+ };
+
+ let currentSection = null;
+ let currentItem = null;
+
+ lines.forEach((line) => {
+ const trimmed = line.trim();
+ if (trimmed.startsWith('input_normalizers:')) currentSection = 'input_normalizers';
+ else if (trimmed.startsWith('postprocessors:')) currentSection = 'postprocessors';
+ else if (trimmed.startsWith('- name:')) {
+ currentItem = { name: trimmed.replace('- name: ', ''), params: {} };
+ } else if (trimmed.startsWith('name:') && !trimmed.startsWith('- name:')) {
+ if (currentItem) currentItem.name = trimmed.replace('name: ', '');
+ } else if (trimmed.startsWith('params:')) {
+ // params section
+ } else if (trimmed && currentItem && !trimmed.startsWith('-')) {
+ const [key, value] = trimmed.split(':').map((s) => s.trim());
+ if (key && value) {
+ try {
+ currentItem.params[key] = JSON.parse(value);
+ } catch {
+ currentItem.params[key] = value;
+ }
+ }
+ }
+
+ // Save item when moving to next
+ if ((trimmed.startsWith('- name:') || trimmed.startsWith('name:')) && currentItem && Object.keys(currentItem.params).length > 0) {
+ if (currentSection) result[currentSection].push(currentItem);
+ currentItem = null;
+ }
+ });
+
+ // Push last item
+ if (currentItem && currentSection) result[currentSection].push(currentItem);
+
+ return result;
+};
+
+const downloadFile = (content, filename, mimeType) => {
+ const element = document.createElement('a');
+ element.setAttribute('href', 'data:' + mimeType + ';charset=utf-8,' + encodeURIComponent(content));
+ element.setAttribute('download', filename);
+ element.style.display = 'none';
+ document.body.appendChild(element);
+ element.click();
+ document.body.removeChild(element);
+};
diff --git a/cellmap_flow/dashboard/static/js/pipeline-nodes.js b/cellmap_flow/dashboard/static/js/pipeline-nodes.js
new file mode 100644
index 0000000..2a649cc
--- /dev/null
+++ b/cellmap_flow/dashboard/static/js/pipeline-nodes.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import { Handle, Position } from 'reactflow';
+
+const nodeColors = {
+ input: '#90EE90',
+ normalizer: '#87CEEB',
+ postprocessor: '#FFB6C1',
+ output: '#FFD700',
+};
+
+export const PipelineNode = ({ data, selected, id }) => {
+ const bgColor = nodeColors[data.type] || '#fff';
+
+ return (
+
+ {data.type !== 'input' && (
+
+ )}
+
{data.label}
+ {data.type !== 'output' && (
+
+ )}
+
+ );
+};
diff --git a/cellmap_flow/dashboard/static/js/pipeline-toolbar.js b/cellmap_flow/dashboard/static/js/pipeline-toolbar.js
new file mode 100644
index 0000000..fbf6e3e
--- /dev/null
+++ b/cellmap_flow/dashboard/static/js/pipeline-toolbar.js
@@ -0,0 +1,53 @@
+import React from 'react';
+
+export const PipelineToolbar = ({
+ availableNormalizers,
+ availablePostprocessors,
+ onAddNormalizer,
+ onAddPostprocessor,
+}) => {
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/cellmap_flow/dashboard/static/js/react-dom.production.min.js b/cellmap_flow/dashboard/static/js/react-dom.production.min.js
new file mode 100644
index 0000000..fb4e099
--- /dev/null
+++ b/cellmap_flow/dashboard/static/js/react-dom.production.min.js
@@ -0,0 +1,267 @@
+/**
+ * @license React
+ * react-dom.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+(function(){/*
+ Modernizr 3.0.0pre (Custom Build) | MIT
+*/
+'use strict';(function(Q,zb){"object"===typeof exports&&"undefined"!==typeof module?zb(exports,require("react")):"function"===typeof define&&define.amd?define(["exports","react"],zb):(Q=Q||self,zb(Q.ReactDOM={},Q.React))})(this,function(Q,zb){function m(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;cb}return!1}function Y(a,b,c,d,e,f,g){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f;this.removeEmptyString=g}function $d(a,b,c,d){var e=R.hasOwnProperty(b)?R[b]:null;if(null!==e?0!==e.type:d||!(2h||e[g]!==f[h]){var k="\n"+e[g].replace(" at new "," at ");a.displayName&&k.includes("")&&(k=k.replace("",a.displayName));return k}while(1<=g&&0<=h)}break}}}finally{ce=!1,Error.prepareStackTrace=c}return(a=a?a.displayName||a.name:"")?bc(a):
+""}function fj(a){switch(a.tag){case 5:return bc(a.type);case 16:return bc("Lazy");case 13:return bc("Suspense");case 19:return bc("SuspenseList");case 0:case 2:case 15:return a=be(a.type,!1),a;case 11:return a=be(a.type.render,!1),a;case 1:return a=be(a.type,!0),a;default:return""}}function de(a){if(null==a)return null;if("function"===typeof a)return a.displayName||a.name||null;if("string"===typeof a)return a;switch(a){case Bb:return"Fragment";case Cb:return"Portal";case ee:return"Profiler";case fe:return"StrictMode";
+case ge:return"Suspense";case he:return"SuspenseList"}if("object"===typeof a)switch(a.$$typeof){case gg:return(a.displayName||"Context")+".Consumer";case hg:return(a._context.displayName||"Context")+".Provider";case ie:var b=a.render;a=a.displayName;a||(a=b.displayName||b.name||"",a=""!==a?"ForwardRef("+a+")":"ForwardRef");return a;case je:return b=a.displayName||null,null!==b?b:de(a.type)||"Memo";case Ta:b=a._payload;a=a._init;try{return de(a(b))}catch(c){}}return null}function gj(a){var b=a.type;
+switch(a.tag){case 24:return"Cache";case 9:return(b.displayName||"Context")+".Consumer";case 10:return(b._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return a=b.render,a=a.displayName||a.name||"",b.displayName||(""!==a?"ForwardRef("+a+")":"ForwardRef");case 7:return"Fragment";case 5:return b;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return de(b);case 8:return b===fe?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";
+case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if("function"===typeof b)return b.displayName||b.name||null;if("string"===typeof b)return b}return null}function Ua(a){switch(typeof a){case "boolean":case "number":case "string":case "undefined":return a;case "object":return a;default:return""}}function ig(a){var b=a.type;return(a=a.nodeName)&&"input"===a.toLowerCase()&&("checkbox"===b||"radio"===
+b)}function hj(a){var b=ig(a)?"checked":"value",c=Object.getOwnPropertyDescriptor(a.constructor.prototype,b),d=""+a[b];if(!a.hasOwnProperty(b)&&"undefined"!==typeof c&&"function"===typeof c.get&&"function"===typeof c.set){var e=c.get,f=c.set;Object.defineProperty(a,b,{configurable:!0,get:function(){return e.call(this)},set:function(a){d=""+a;f.call(this,a)}});Object.defineProperty(a,b,{enumerable:c.enumerable});return{getValue:function(){return d},setValue:function(a){d=""+a},stopTracking:function(){a._valueTracker=
+null;delete a[b]}}}}function Pc(a){a._valueTracker||(a._valueTracker=hj(a))}function jg(a){if(!a)return!1;var b=a._valueTracker;if(!b)return!0;var c=b.getValue();var d="";a&&(d=ig(a)?a.checked?"true":"false":a.value);a=d;return a!==c?(b.setValue(a),!0):!1}function Qc(a){a=a||("undefined"!==typeof document?document:void 0);if("undefined"===typeof a)return null;try{return a.activeElement||a.body}catch(b){return a.body}}function ke(a,b){var c=b.checked;return E({},b,{defaultChecked:void 0,defaultValue:void 0,
+value:void 0,checked:null!=c?c:a._wrapperState.initialChecked})}function kg(a,b){var c=null==b.defaultValue?"":b.defaultValue,d=null!=b.checked?b.checked:b.defaultChecked;c=Ua(null!=b.value?b.value:c);a._wrapperState={initialChecked:d,initialValue:c,controlled:"checkbox"===b.type||"radio"===b.type?null!=b.checked:null!=b.value}}function lg(a,b){b=b.checked;null!=b&&$d(a,"checked",b,!1)}function le(a,b){lg(a,b);var c=Ua(b.value),d=b.type;if(null!=c)if("number"===d){if(0===c&&""===a.value||a.value!=
+c)a.value=""+c}else a.value!==""+c&&(a.value=""+c);else if("submit"===d||"reset"===d){a.removeAttribute("value");return}b.hasOwnProperty("value")?me(a,b.type,c):b.hasOwnProperty("defaultValue")&&me(a,b.type,Ua(b.defaultValue));null==b.checked&&null!=b.defaultChecked&&(a.defaultChecked=!!b.defaultChecked)}function mg(a,b,c){if(b.hasOwnProperty("value")||b.hasOwnProperty("defaultValue")){var d=b.type;if(!("submit"!==d&&"reset"!==d||void 0!==b.value&&null!==b.value))return;b=""+a._wrapperState.initialValue;
+c||b===a.value||(a.value=b);a.defaultValue=b}c=a.name;""!==c&&(a.name="");a.defaultChecked=!!a._wrapperState.initialChecked;""!==c&&(a.name=c)}function me(a,b,c){if("number"!==b||Qc(a.ownerDocument)!==a)null==c?a.defaultValue=""+a._wrapperState.initialValue:a.defaultValue!==""+c&&(a.defaultValue=""+c)}function Db(a,b,c,d){a=a.options;if(b){b={};for(var e=0;e>>=0;return 0===a?32:31-(qj(a)/rj|0)|0}function hc(a){switch(a&-a){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return a&
+4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return a&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return a}}function Vc(a,b){var c=a.pendingLanes;if(0===c)return 0;var d=0,e=a.suspendedLanes,f=a.pingedLanes,g=c&268435455;if(0!==g){var h=g&~e;0!==h?d=hc(h):(f&=g,0!==f&&(d=hc(f)))}else g=c&~e,0!==g?d=hc(g):0!==f&&(d=hc(f));if(0===d)return 0;if(0!==b&&b!==d&&0===(b&e)&&
+(e=d&-d,f=b&-b,e>=f||16===e&&0!==(f&4194240)))return b;0!==(d&4)&&(d|=c&16);b=a.entangledLanes;if(0!==b)for(a=a.entanglements,b&=d;0c;c++)b.push(a);
+return b}function ic(a,b,c){a.pendingLanes|=b;536870912!==b&&(a.suspendedLanes=0,a.pingedLanes=0);a=a.eventTimes;b=31-ta(b);a[b]=c}function uj(a,b){var c=a.pendingLanes&~b;a.pendingLanes=b;a.suspendedLanes=0;a.pingedLanes=0;a.expiredLanes&=b;a.mutableReadLanes&=b;a.entangledLanes&=b;b=a.entanglements;var d=a.eventTimes;for(a=a.expirationTimes;0=b)return{node:c,offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=$g(c)}}function bh(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?bh(a,b.parentNode):"contains"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1}function ch(){for(var a=window,b=Qc();b instanceof a.HTMLIFrameElement;){try{var c="string"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break;
+b=Qc(a.document)}return b}function Ie(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&("input"===b&&("text"===a.type||"search"===a.type||"tel"===a.type||"url"===a.type||"password"===a.type)||"textarea"===b||"true"===a.contentEditable)}function Tj(a){var b=ch(),c=a.focusedElem,d=a.selectionRange;if(b!==c&&c&&c.ownerDocument&&bh(c.ownerDocument.documentElement,c)){if(null!==d&&Ie(c))if(b=d.start,a=d.end,void 0===a&&(a=b),"selectionStart"in c)c.selectionStart=b,c.selectionEnd=Math.min(a,c.value.length);
+else if(a=(b=c.ownerDocument||document)&&b.defaultView||window,a.getSelection){a=a.getSelection();var e=c.textContent.length,f=Math.min(d.start,e);d=void 0===d.end?f:Math.min(d.end,e);!a.extend&&f>d&&(e=d,d=f,f=e);e=ah(c,f);var g=ah(c,d);e&&g&&(1!==a.rangeCount||a.anchorNode!==e.node||a.anchorOffset!==e.offset||a.focusNode!==g.node||a.focusOffset!==g.offset)&&(b=b.createRange(),b.setStart(e.node,e.offset),a.removeAllRanges(),f>d?(a.addRange(b),a.extend(g.node,g.offset)):(b.setEnd(g.node,g.offset),
+a.addRange(b)))}b=[];for(a=c;a=a.parentNode;)1===a.nodeType&&b.push({element:a,left:a.scrollLeft,top:a.scrollTop});"function"===typeof c.focus&&c.focus();for(c=0;cMb||(a.current=Se[Mb],Se[Mb]=null,Mb--)}
+function y(a,b,c){Mb++;Se[Mb]=a.current;a.current=b}function Nb(a,b){var c=a.type.contextTypes;if(!c)return cb;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function ea(a){a=a.childContextTypes;return null!==a&&void 0!==a}function th(a,b,c){if(J.current!==cb)throw Error(m(168));
+y(J,b);y(S,c)}function uh(a,b,c){var d=a.stateNode;b=b.childContextTypes;if("function"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in b))throw Error(m(108,gj(a)||"Unknown",e));return E({},c,d)}function ld(a){a=(a=a.stateNode)&&a.__reactInternalMemoizedMergedChildContext||cb;pb=J.current;y(J,a);y(S,S.current);return!0}function vh(a,b,c){var d=a.stateNode;if(!d)throw Error(m(169));c?(a=uh(a,b,pb),d.__reactInternalMemoizedMergedChildContext=a,v(S),v(J),y(J,a)):v(S);
+y(S,c)}function wh(a){null===La?La=[a]:La.push(a)}function jk(a){md=!0;wh(a)}function db(){if(!Te&&null!==La){Te=!0;var a=0,b=z;try{var c=La;for(z=1;a>=g;e-=g;Ma=1<<32-ta(b)+e|c<t?(q=l,l=null):q=l.sibling;var A=r(e,l,h[t],k);if(null===A){null===l&&(l=q);break}a&&l&&null===A.alternate&&b(e,l);g=f(A,g,t);null===m?n=A:m.sibling=A;m=A;l=q}if(t===h.length)return c(e,l),D&&qb(e,t),n;if(null===l){for(;t<
+h.length;t++)l=u(e,h[t],k),null!==l&&(g=f(l,g,t),null===m?n=l:m.sibling=l,m=l);D&&qb(e,t);return n}for(l=d(e,l);tt?(A=q,q=null):A=q.sibling;var x=r(e,q,w.value,k);if(null===x){null===q&&(q=A);break}a&&q&&null===x.alternate&&b(e,q);g=f(x,g,t);null===l?n=x:l.sibling=x;l=x;q=A}if(w.done)return c(e,q),D&&qb(e,t),n;if(null===q){for(;!w.done;t++,w=h.next())w=u(e,w.value,k),null!==w&&(g=f(w,g,t),null===l?n=w:l.sibling=w,l=w);D&&qb(e,t);return n}for(q=d(e,q);!w.done;t++,w=h.next())w=p(q,e,t,w.value,k),null!==w&&(a&&null!==w.alternate&&q.delete(null===w.key?t:w.key),g=f(w,g,t),null===l?n=w:l.sibling=
+w,l=w);a&&q.forEach(function(a){return b(e,a)});D&&qb(e,t);return n}function v(a,d,f,h){"object"===typeof f&&null!==f&&f.type===Bb&&null===f.key&&(f=f.props.children);if("object"===typeof f&&null!==f){switch(f.$$typeof){case sd:a:{for(var k=f.key,n=d;null!==n;){if(n.key===k){k=f.type;if(k===Bb){if(7===n.tag){c(a,n.sibling);d=e(n,f.props.children);d.return=a;a=d;break a}}else if(n.elementType===k||"object"===typeof k&&null!==k&&k.$$typeof===Ta&&Ch(k)===n.type){c(a,n.sibling);d=e(n,f.props);d.ref=vc(a,
+n,f);d.return=a;a=d;break a}c(a,n);break}else b(a,n);n=n.sibling}f.type===Bb?(d=sb(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=rd(f.type,f.key,f.props,null,a.mode,h),h.ref=vc(a,d,f),h.return=a,a=h)}return g(a);case Cb:a:{for(n=f.key;null!==d;){if(d.key===n)if(4===d.tag&&d.stateNode.containerInfo===f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[]);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=$e(f,a.mode,h);d.return=a;
+a=d}return g(a);case Ta:return n=f._init,v(a,d,n(f._payload),h)}if(cc(f))return x(a,d,f,h);if(ac(f))return I(a,d,f,h);qd(a,f)}return"string"===typeof f&&""!==f||"number"===typeof f?(f=""+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f),d.return=a,a=d):(c(a,d),d=Ze(f,a.mode,h),d.return=a,a=d),g(a)):c(a,d)}return v}function af(){bf=Rb=td=null}function cf(a,b){b=ud.current;v(ud);a._currentValue=b}function df(a,b,c){for(;null!==a;){var d=a.alternate;(a.childLanes&b)!==b?(a.childLanes|=b,null!==d&&(d.childLanes|=
+b)):null!==d&&(d.childLanes&b)!==b&&(d.childLanes|=b);if(a===c)break;a=a.return}}function Sb(a,b){td=a;bf=Rb=null;a=a.dependencies;null!==a&&null!==a.firstContext&&(0!==(a.lanes&b)&&(ha=!0),a.firstContext=null)}function qa(a){var b=a._currentValue;if(bf!==a)if(a={context:a,memoizedValue:b,next:null},null===Rb){if(null===td)throw Error(m(308));Rb=a;td.dependencies={lanes:0,firstContext:a}}else Rb=Rb.next=a;return b}function ef(a){null===tb?tb=[a]:tb.push(a)}function Eh(a,b,c,d){var e=b.interleaved;
+null===e?(c.next=c,ef(b)):(c.next=e.next,e.next=c);b.interleaved=c;return Oa(a,d)}function Oa(a,b){a.lanes|=b;var c=a.alternate;null!==c&&(c.lanes|=b);c=a;for(a=a.return;null!==a;)a.childLanes|=b,c=a.alternate,null!==c&&(c.childLanes|=b),c=a,a=a.return;return 3===c.tag?c.stateNode:null}function ff(a){a.updateQueue={baseState:a.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Fh(a,b){a=a.updateQueue;b.updateQueue===a&&(b.updateQueue=
+{baseState:a.baseState,firstBaseUpdate:a.firstBaseUpdate,lastBaseUpdate:a.lastBaseUpdate,shared:a.shared,effects:a.effects})}function Pa(a,b){return{eventTime:a,lane:b,tag:0,payload:null,callback:null,next:null}}function fb(a,b,c){var d=a.updateQueue;if(null===d)return null;d=d.shared;if(0!==(p&2)){var e=d.pending;null===e?b.next=b:(b.next=e.next,e.next=b);d.pending=b;return kk(a,c)}e=d.interleaved;null===e?(b.next=b,ef(d)):(b.next=e.next,e.next=b);d.interleaved=b;return Oa(a,c)}function vd(a,b,c){b=
+b.updateQueue;if(null!==b&&(b=b.shared,0!==(c&4194240))){var d=b.lanes;d&=a.pendingLanes;c|=d;b.lanes=c;xe(a,c)}}function Gh(a,b){var c=a.updateQueue,d=a.alternate;if(null!==d&&(d=d.updateQueue,c===d)){var e=null,f=null;c=c.firstBaseUpdate;if(null!==c){do{var g={eventTime:c.eventTime,lane:c.lane,tag:c.tag,payload:c.payload,callback:c.callback,next:null};null===f?e=f=g:f=f.next=g;c=c.next}while(null!==c);null===f?e=f=b:f=f.next=b}else e=f=b;c={baseState:d.baseState,firstBaseUpdate:e,lastBaseUpdate:f,
+shared:d.shared,effects:d.effects};a.updateQueue=c;return}a=c.lastBaseUpdate;null===a?c.firstBaseUpdate=b:a.next=b;c.lastBaseUpdate=b}function wd(a,b,c,d){var e=a.updateQueue;gb=!1;var f=e.firstBaseUpdate,g=e.lastBaseUpdate,h=e.shared.pending;if(null!==h){e.shared.pending=null;var k=h,n=k.next;k.next=null;null===g?f=n:g.next=n;g=k;var l=a.alternate;null!==l&&(l=l.updateQueue,h=l.lastBaseUpdate,h!==g&&(null===h?l.firstBaseUpdate=n:h.next=n,l.lastBaseUpdate=k))}if(null!==f){var m=e.baseState;g=0;l=
+n=k=null;h=f;do{var r=h.lane,p=h.eventTime;if((d&r)===r){null!==l&&(l=l.next={eventTime:p,lane:0,tag:h.tag,payload:h.payload,callback:h.callback,next:null});a:{var x=a,v=h;r=b;p=c;switch(v.tag){case 1:x=v.payload;if("function"===typeof x){m=x.call(p,m,r);break a}m=x;break a;case 3:x.flags=x.flags&-65537|128;case 0:x=v.payload;r="function"===typeof x?x.call(p,m,r):x;if(null===r||void 0===r)break a;m=E({},m,r);break a;case 2:gb=!0}}null!==h.callback&&0!==h.lane&&(a.flags|=64,r=e.effects,null===r?e.effects=
+[h]:r.push(h))}else p={eventTime:p,lane:r,tag:h.tag,payload:h.payload,callback:h.callback,next:null},null===l?(n=l=p,k=m):l=l.next=p,g|=r;h=h.next;if(null===h)if(h=e.shared.pending,null===h)break;else r=h,h=r.next,r.next=null,e.lastBaseUpdate=r,e.shared.pending=null}while(1);null===l&&(k=m);e.baseState=k;e.firstBaseUpdate=n;e.lastBaseUpdate=l;b=e.shared.interleaved;if(null!==b){e=b;do g|=e.lane,e=e.next;while(e!==b)}else null===f&&(e.shared.lanes=0);ra|=g;a.lanes=g;a.memoizedState=m}}function Hh(a,
+b,c){a=b.effects;b.effects=null;if(null!==a)for(b=0;bc?c:4;a(!0);var d=sf.transition;sf.transition=
+{};try{a(!1),b()}finally{z=c,sf.transition=d}}function $h(){return sa().memoizedState}function qk(a,b,c){var d=hb(a);c={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(ai(a))bi(b,c);else if(c=Eh(a,b,c,d),null!==c){var e=Z();xa(c,a,d,e);ci(c,b,d)}}function ok(a,b,c){var d=hb(a),e={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(ai(a))bi(b,e);else{var f=a.alternate;if(0===a.lanes&&(null===f||0===f.lanes)&&(f=b.lastRenderedReducer,null!==f))try{var g=b.lastRenderedState,
+h=f(g,c);e.hasEagerState=!0;e.eagerState=h;if(ua(h,g)){var k=b.interleaved;null===k?(e.next=e,ef(b)):(e.next=k.next,k.next=e);b.interleaved=e;return}}catch(n){}finally{}c=Eh(a,b,e,d);null!==c&&(e=Z(),xa(c,a,d,e),ci(c,b,d))}}function ai(a){var b=a.alternate;return a===C||null!==b&&b===C}function bi(a,b){zc=Ad=!0;var c=a.pending;null===c?b.next=b:(b.next=c.next,c.next=b);a.pending=b}function ci(a,b,c){if(0!==(c&4194240)){var d=b.lanes;d&=a.pendingLanes;c|=d;b.lanes=c;xe(a,c)}}function ya(a,b){if(a&&
+a.defaultProps){b=E({},b);a=a.defaultProps;for(var c in a)void 0===b[c]&&(b[c]=a[c]);return b}return b}function tf(a,b,c,d){b=a.memoizedState;c=c(d,b);c=null===c||void 0===c?b:E({},b,c);a.memoizedState=c;0===a.lanes&&(a.updateQueue.baseState=c)}function di(a,b,c,d,e,f,g){a=a.stateNode;return"function"===typeof a.shouldComponentUpdate?a.shouldComponentUpdate(d,f,g):b.prototype&&b.prototype.isPureReactComponent?!qc(c,d)||!qc(e,f):!0}function ei(a,b,c){var d=!1,e=cb;var f=b.contextType;"object"===typeof f&&
+null!==f?f=qa(f):(e=ea(b)?pb:J.current,d=b.contextTypes,f=(d=null!==d&&void 0!==d)?Nb(a,e):cb);b=new b(c,f);a.memoizedState=null!==b.state&&void 0!==b.state?b.state:null;b.updater=Dd;a.stateNode=b;b._reactInternals=a;d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=e,a.__reactInternalMemoizedMaskedChildContext=f);return b}function fi(a,b,c,d){a=b.state;"function"===typeof b.componentWillReceiveProps&&b.componentWillReceiveProps(c,d);"function"===typeof b.UNSAFE_componentWillReceiveProps&&
+b.UNSAFE_componentWillReceiveProps(c,d);b.state!==a&&Dd.enqueueReplaceState(b,b.state,null)}function uf(a,b,c,d){var e=a.stateNode;e.props=c;e.state=a.memoizedState;e.refs={};ff(a);var f=b.contextType;"object"===typeof f&&null!==f?e.context=qa(f):(f=ea(b)?pb:J.current,e.context=Nb(a,f));e.state=a.memoizedState;f=b.getDerivedStateFromProps;"function"===typeof f&&(tf(a,b,f,c),e.state=a.memoizedState);"function"===typeof b.getDerivedStateFromProps||"function"===typeof e.getSnapshotBeforeUpdate||"function"!==
+typeof e.UNSAFE_componentWillMount&&"function"!==typeof e.componentWillMount||(b=e.state,"function"===typeof e.componentWillMount&&e.componentWillMount(),"function"===typeof e.UNSAFE_componentWillMount&&e.UNSAFE_componentWillMount(),b!==e.state&&Dd.enqueueReplaceState(e,e.state,null),wd(a,c,e,d),e.state=a.memoizedState);"function"===typeof e.componentDidMount&&(a.flags|=4194308)}function Ub(a,b){try{var c="",d=b;do c+=fj(d),d=d.return;while(d);var e=c}catch(f){e="\nError generating stack: "+f.message+
+"\n"+f.stack}return{value:a,source:b,stack:e,digest:null}}function vf(a,b,c){return{value:a,source:null,stack:null!=c?c:null,digest:null!=b?b:null}}function wf(a,b){try{console.error(b.value)}catch(c){setTimeout(function(){throw c;})}}function gi(a,b,c){c=Pa(-1,c);c.tag=3;c.payload={element:null};var d=b.value;c.callback=function(){Ed||(Ed=!0,xf=d);wf(a,b)};return c}function hi(a,b,c){c=Pa(-1,c);c.tag=3;var d=a.type.getDerivedStateFromError;if("function"===typeof d){var e=b.value;c.payload=function(){return d(e)};
+c.callback=function(){wf(a,b)}}var f=a.stateNode;null!==f&&"function"===typeof f.componentDidCatch&&(c.callback=function(){wf(a,b);"function"!==typeof d&&(null===ib?ib=new Set([this]):ib.add(this));var c=b.stack;this.componentDidCatch(b.value,{componentStack:null!==c?c:""})});return c}function ii(a,b,c){var d=a.pingCache;if(null===d){d=a.pingCache=new rk;var e=new Set;d.set(b,e)}else e=d.get(b),void 0===e&&(e=new Set,d.set(b,e));e.has(c)||(e.add(c),a=sk.bind(null,a,b,c),b.then(a,a))}function ji(a){do{var b;
+if(b=13===a.tag)b=a.memoizedState,b=null!==b?null!==b.dehydrated?!0:!1:!0;if(b)return a;a=a.return}while(null!==a);return null}function ki(a,b,c,d,e){if(0===(a.mode&1))return a===b?a.flags|=65536:(a.flags|=128,c.flags|=131072,c.flags&=-52805,1===c.tag&&(null===c.alternate?c.tag=17:(b=Pa(-1,1),b.tag=2,fb(c,b,1))),c.lanes|=1),a;a.flags|=65536;a.lanes=e;return a}function aa(a,b,c,d){b.child=null===a?li(b,null,c,d):Vb(b,a.child,c,d)}function mi(a,b,c,d,e){c=c.render;var f=b.ref;Sb(b,e);d=mf(a,b,c,d,f,
+e);c=nf();if(null!==a&&!ha)return b.updateQueue=a.updateQueue,b.flags&=-2053,a.lanes&=~e,Qa(a,b,e);D&&c&&Ue(b);b.flags|=1;aa(a,b,d,e);return b.child}function ni(a,b,c,d,e){if(null===a){var f=c.type;if("function"===typeof f&&!yf(f)&&void 0===f.defaultProps&&null===c.compare&&void 0===c.defaultProps)return b.tag=15,b.type=f,oi(a,b,f,d,e);a=rd(c.type,null,d,b,b.mode,e);a.ref=b.ref;a.return=b;return b.child=a}f=a.child;if(0===(a.lanes&e)){var g=f.memoizedProps;c=c.compare;c=null!==c?c:qc;if(c(g,d)&&a.ref===
+b.ref)return Qa(a,b,e)}b.flags|=1;a=eb(f,d);a.ref=b.ref;a.return=b;return b.child=a}function oi(a,b,c,d,e){if(null!==a){var f=a.memoizedProps;if(qc(f,d)&&a.ref===b.ref)if(ha=!1,b.pendingProps=d=f,0!==(a.lanes&e))0!==(a.flags&131072)&&(ha=!0);else return b.lanes=a.lanes,Qa(a,b,e)}return zf(a,b,c,d,e)}function pi(a,b,c){var d=b.pendingProps,e=d.children,f=null!==a?a.memoizedState:null;if("hidden"===d.mode)if(0===(b.mode&1))b.memoizedState={baseLanes:0,cachePool:null,transitions:null},y(Ga,ba),ba|=c;
+else{if(0===(c&1073741824))return a=null!==f?f.baseLanes|c:c,b.lanes=b.childLanes=1073741824,b.memoizedState={baseLanes:a,cachePool:null,transitions:null},b.updateQueue=null,y(Ga,ba),ba|=a,null;b.memoizedState={baseLanes:0,cachePool:null,transitions:null};d=null!==f?f.baseLanes:c;y(Ga,ba);ba|=d}else null!==f?(d=f.baseLanes|c,b.memoizedState=null):d=c,y(Ga,ba),ba|=d;aa(a,b,e,c);return b.child}function qi(a,b){var c=b.ref;if(null===a&&null!==c||null!==a&&a.ref!==c)b.flags|=512,b.flags|=2097152}function zf(a,
+b,c,d,e){var f=ea(c)?pb:J.current;f=Nb(b,f);Sb(b,e);c=mf(a,b,c,d,f,e);d=nf();if(null!==a&&!ha)return b.updateQueue=a.updateQueue,b.flags&=-2053,a.lanes&=~e,Qa(a,b,e);D&&d&&Ue(b);b.flags|=1;aa(a,b,c,e);return b.child}function ri(a,b,c,d,e){if(ea(c)){var f=!0;ld(b)}else f=!1;Sb(b,e);if(null===b.stateNode)Fd(a,b),ei(b,c,d),uf(b,c,d,e),d=!0;else if(null===a){var g=b.stateNode,h=b.memoizedProps;g.props=h;var k=g.context,n=c.contextType;"object"===typeof n&&null!==n?n=qa(n):(n=ea(c)?pb:J.current,n=Nb(b,
+n));var l=c.getDerivedStateFromProps,m="function"===typeof l||"function"===typeof g.getSnapshotBeforeUpdate;m||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==d||k!==n)&&fi(b,g,d,n);gb=!1;var r=b.memoizedState;g.state=r;wd(b,d,g,e);k=b.memoizedState;h!==d||r!==k||S.current||gb?("function"===typeof l&&(tf(b,c,l,d),k=b.memoizedState),(h=gb||di(b,c,h,d,r,k,n))?(m||"function"!==typeof g.UNSAFE_componentWillMount&&"function"!==typeof g.componentWillMount||
+("function"===typeof g.componentWillMount&&g.componentWillMount(),"function"===typeof g.UNSAFE_componentWillMount&&g.UNSAFE_componentWillMount()),"function"===typeof g.componentDidMount&&(b.flags|=4194308)):("function"===typeof g.componentDidMount&&(b.flags|=4194308),b.memoizedProps=d,b.memoizedState=k),g.props=d,g.state=k,g.context=n,d=h):("function"===typeof g.componentDidMount&&(b.flags|=4194308),d=!1)}else{g=b.stateNode;Fh(a,b);h=b.memoizedProps;n=b.type===b.elementType?h:ya(b.type,h);g.props=
+n;m=b.pendingProps;r=g.context;k=c.contextType;"object"===typeof k&&null!==k?k=qa(k):(k=ea(c)?pb:J.current,k=Nb(b,k));var p=c.getDerivedStateFromProps;(l="function"===typeof p||"function"===typeof g.getSnapshotBeforeUpdate)||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==m||r!==k)&&fi(b,g,d,k);gb=!1;r=b.memoizedState;g.state=r;wd(b,d,g,e);var x=b.memoizedState;h!==m||r!==x||S.current||gb?("function"===typeof p&&(tf(b,c,p,d),x=b.memoizedState),
+(n=gb||di(b,c,n,d,r,x,k)||!1)?(l||"function"!==typeof g.UNSAFE_componentWillUpdate&&"function"!==typeof g.componentWillUpdate||("function"===typeof g.componentWillUpdate&&g.componentWillUpdate(d,x,k),"function"===typeof g.UNSAFE_componentWillUpdate&&g.UNSAFE_componentWillUpdate(d,x,k)),"function"===typeof g.componentDidUpdate&&(b.flags|=4),"function"===typeof g.getSnapshotBeforeUpdate&&(b.flags|=1024)):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=
+4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=1024),b.memoizedProps=d,b.memoizedState=x),g.props=d,g.state=x,g.context=k,d=n):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=1024),d=!1)}return Af(a,b,c,d,f,e)}function Af(a,b,c,d,e,f){qi(a,b);var g=0!==(b.flags&128);if(!d&&!g)return e&&vh(b,c,!1),
+Qa(a,b,f);d=b.stateNode;tk.current=b;var h=g&&"function"!==typeof c.getDerivedStateFromError?null:d.render();b.flags|=1;null!==a&&g?(b.child=Vb(b,a.child,null,f),b.child=Vb(b,null,h,f)):aa(a,b,h,f);b.memoizedState=d.state;e&&vh(b,c,!0);return b.child}function si(a){var b=a.stateNode;b.pendingContext?th(a,b.pendingContext,b.pendingContext!==b.context):b.context&&th(a,b.context,!1);gf(a,b.containerInfo)}function ti(a,b,c,d,e){Qb();Ye(e);b.flags|=256;aa(a,b,c,d);return b.child}function Bf(a){return{baseLanes:a,
+cachePool:null,transitions:null}}function ui(a,b,c){var d=b.pendingProps,e=F.current,f=!1,g=0!==(b.flags&128),h;(h=g)||(h=null!==a&&null===a.memoizedState?!1:0!==(e&2));if(h)f=!0,b.flags&=-129;else if(null===a||null!==a.memoizedState)e|=1;y(F,e&1);if(null===a){Xe(b);a=b.memoizedState;if(null!==a&&(a=a.dehydrated,null!==a))return 0===(b.mode&1)?b.lanes=1:"$!"===a.data?b.lanes=8:b.lanes=1073741824,null;g=d.children;a=d.fallback;return f?(d=b.mode,f=b.child,g={mode:"hidden",children:g},0===(d&1)&&null!==
+f?(f.childLanes=0,f.pendingProps=g):f=Gd(g,d,0,null),a=sb(a,d,c,null),f.return=b,a.return=b,f.sibling=a,b.child=f,b.child.memoizedState=Bf(c),b.memoizedState=Cf,a):Df(b,g)}e=a.memoizedState;if(null!==e&&(h=e.dehydrated,null!==h))return uk(a,b,g,d,h,e,c);if(f){f=d.fallback;g=b.mode;e=a.child;h=e.sibling;var k={mode:"hidden",children:d.children};0===(g&1)&&b.child!==e?(d=b.child,d.childLanes=0,d.pendingProps=k,b.deletions=null):(d=eb(e,k),d.subtreeFlags=e.subtreeFlags&14680064);null!==h?f=eb(h,f):(f=
+sb(f,g,c,null),f.flags|=2);f.return=b;d.return=b;d.sibling=f;b.child=d;d=f;f=b.child;g=a.child.memoizedState;g=null===g?Bf(c):{baseLanes:g.baseLanes|c,cachePool:null,transitions:g.transitions};f.memoizedState=g;f.childLanes=a.childLanes&~c;b.memoizedState=Cf;return d}f=a.child;a=f.sibling;d=eb(f,{mode:"visible",children:d.children});0===(b.mode&1)&&(d.lanes=c);d.return=b;d.sibling=null;null!==a&&(c=b.deletions,null===c?(b.deletions=[a],b.flags|=16):c.push(a));b.child=d;b.memoizedState=null;return d}
+function Df(a,b,c){b=Gd({mode:"visible",children:b},a.mode,0,null);b.return=a;return a.child=b}function Hd(a,b,c,d){null!==d&&Ye(d);Vb(b,a.child,null,c);a=Df(b,b.pendingProps.children);a.flags|=2;b.memoizedState=null;return a}function uk(a,b,c,d,e,f,g){if(c){if(b.flags&256)return b.flags&=-257,d=vf(Error(m(422))),Hd(a,b,g,d);if(null!==b.memoizedState)return b.child=a.child,b.flags|=128,null;f=d.fallback;e=b.mode;d=Gd({mode:"visible",children:d.children},e,0,null);f=sb(f,e,g,null);f.flags|=2;d.return=
+b;f.return=b;d.sibling=f;b.child=d;0!==(b.mode&1)&&Vb(b,a.child,null,g);b.child.memoizedState=Bf(g);b.memoizedState=Cf;return f}if(0===(b.mode&1))return Hd(a,b,g,null);if("$!"===e.data){d=e.nextSibling&&e.nextSibling.dataset;if(d)var h=d.dgst;d=h;f=Error(m(419));d=vf(f,d,void 0);return Hd(a,b,g,d)}h=0!==(g&a.childLanes);if(ha||h){d=O;if(null!==d){switch(g&-g){case 4:e=2;break;case 16:e=8;break;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:e=
+32;break;case 536870912:e=268435456;break;default:e=0}e=0!==(e&(d.suspendedLanes|g))?0:e;0!==e&&e!==f.retryLane&&(f.retryLane=e,Oa(a,e),xa(d,a,e,-1))}Ef();d=vf(Error(m(421)));return Hd(a,b,g,d)}if("$?"===e.data)return b.flags|=128,b.child=a.child,b=vk.bind(null,a),e._reactRetry=b,null;a=f.treeContext;fa=Ka(e.nextSibling);la=b;D=!0;wa=null;null!==a&&(na[oa++]=Ma,na[oa++]=Na,na[oa++]=rb,Ma=a.id,Na=a.overflow,rb=b);b=Df(b,d.children);b.flags|=4096;return b}function vi(a,b,c){a.lanes|=b;var d=a.alternate;
+null!==d&&(d.lanes|=b);df(a.return,b,c)}function Ff(a,b,c,d,e){var f=a.memoizedState;null===f?a.memoizedState={isBackwards:b,rendering:null,renderingStartTime:0,last:d,tail:c,tailMode:e}:(f.isBackwards=b,f.rendering=null,f.renderingStartTime=0,f.last=d,f.tail=c,f.tailMode=e)}function wi(a,b,c){var d=b.pendingProps,e=d.revealOrder,f=d.tail;aa(a,b,d.children,c);d=F.current;if(0!==(d&2))d=d&1|2,b.flags|=128;else{if(null!==a&&0!==(a.flags&128))a:for(a=b.child;null!==a;){if(13===a.tag)null!==a.memoizedState&&
+vi(a,c,b);else if(19===a.tag)vi(a,c,b);else if(null!==a.child){a.child.return=a;a=a.child;continue}if(a===b)break a;for(;null===a.sibling;){if(null===a.return||a.return===b)break a;a=a.return}a.sibling.return=a.return;a=a.sibling}d&=1}y(F,d);if(0===(b.mode&1))b.memoizedState=null;else switch(e){case "forwards":c=b.child;for(e=null;null!==c;)a=c.alternate,null!==a&&null===xd(a)&&(e=c),c=c.sibling;c=e;null===c?(e=b.child,b.child=null):(e=c.sibling,c.sibling=null);Ff(b,!1,e,c,f);break;case "backwards":c=
+null;e=b.child;for(b.child=null;null!==e;){a=e.alternate;if(null!==a&&null===xd(a)){b.child=e;break}a=e.sibling;e.sibling=c;c=e;e=a}Ff(b,!0,c,null,f);break;case "together":Ff(b,!1,null,null,void 0);break;default:b.memoizedState=null}return b.child}function Fd(a,b){0===(b.mode&1)&&null!==a&&(a.alternate=null,b.alternate=null,b.flags|=2)}function Qa(a,b,c){null!==a&&(b.dependencies=a.dependencies);ra|=b.lanes;if(0===(c&b.childLanes))return null;if(null!==a&&b.child!==a.child)throw Error(m(153));if(null!==
+b.child){a=b.child;c=eb(a,a.pendingProps);b.child=c;for(c.return=b;null!==a.sibling;)a=a.sibling,c=c.sibling=eb(a,a.pendingProps),c.return=b;c.sibling=null}return b.child}function wk(a,b,c){switch(b.tag){case 3:si(b);Qb();break;case 5:Ih(b);break;case 1:ea(b.type)&&ld(b);break;case 4:gf(b,b.stateNode.containerInfo);break;case 10:var d=b.type._context,e=b.memoizedProps.value;y(ud,d._currentValue);d._currentValue=e;break;case 13:d=b.memoizedState;if(null!==d){if(null!==d.dehydrated)return y(F,F.current&
+1),b.flags|=128,null;if(0!==(c&b.child.childLanes))return ui(a,b,c);y(F,F.current&1);a=Qa(a,b,c);return null!==a?a.sibling:null}y(F,F.current&1);break;case 19:d=0!==(c&b.childLanes);if(0!==(a.flags&128)){if(d)return wi(a,b,c);b.flags|=128}e=b.memoizedState;null!==e&&(e.rendering=null,e.tail=null,e.lastEffect=null);y(F,F.current);if(d)break;else return null;case 22:case 23:return b.lanes=0,pi(a,b,c)}return Qa(a,b,c)}function Dc(a,b){if(!D)switch(a.tailMode){case "hidden":b=a.tail;for(var c=null;null!==
+b;)null!==b.alternate&&(c=b),b=b.sibling;null===c?a.tail=null:c.sibling=null;break;case "collapsed":c=a.tail;for(var d=null;null!==c;)null!==c.alternate&&(d=c),c=c.sibling;null===d?b||null===a.tail?a.tail=null:a.tail.sibling=null:d.sibling=null}}function W(a){var b=null!==a.alternate&&a.alternate.child===a.child,c=0,d=0;if(b)for(var e=a.child;null!==e;)c|=e.lanes|e.childLanes,d|=e.subtreeFlags&14680064,d|=e.flags&14680064,e.return=a,e=e.sibling;else for(e=a.child;null!==e;)c|=e.lanes|e.childLanes,
+d|=e.subtreeFlags,d|=e.flags,e.return=a,e=e.sibling;a.subtreeFlags|=d;a.childLanes=c;return b}function xk(a,b,c){var d=b.pendingProps;Ve(b);switch(b.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return W(b),null;case 1:return ea(b.type)&&(v(S),v(J)),W(b),null;case 3:d=b.stateNode;Tb();v(S);v(J);jf();d.pendingContext&&(d.context=d.pendingContext,d.pendingContext=null);if(null===a||null===a.child)pd(b)?b.flags|=4:null===a||a.memoizedState.isDehydrated&&0===(b.flags&
+256)||(b.flags|=1024,null!==wa&&(Gf(wa),wa=null));xi(a,b);W(b);return null;case 5:hf(b);var e=ub(xc.current);c=b.type;if(null!==a&&null!=b.stateNode)yk(a,b,c,d,e),a.ref!==b.ref&&(b.flags|=512,b.flags|=2097152);else{if(!d){if(null===b.stateNode)throw Error(m(166));W(b);return null}a=ub(Ea.current);if(pd(b)){d=b.stateNode;c=b.type;var f=b.memoizedProps;d[Da]=b;d[uc]=f;a=0!==(b.mode&1);switch(c){case "dialog":B("cancel",d);B("close",d);break;case "iframe":case "object":case "embed":B("load",d);break;
+case "video":case "audio":for(e=0;e\x3c/script>",a=a.removeChild(a.firstChild)):"string"===typeof d.is?a=g.createElement(c,{is:d.is}):(a=g.createElement(c),"select"===c&&(g=a,d.multiple?g.multiple=!0:d.size&&(g.size=d.size))):a=g.createElementNS(a,c);a[Da]=b;a[uc]=d;zk(a,b,!1,!1);b.stateNode=a;a:{g=qe(c,d);switch(c){case "dialog":B("cancel",a);B("close",a);e=d;break;case "iframe":case "object":case "embed":B("load",a);e=d;break;
+case "video":case "audio":for(e=0;eHf&&(b.flags|=128,d=!0,Dc(f,!1),b.lanes=4194304)}else{if(!d)if(a=xd(g),null!==a){if(b.flags|=128,d=!0,c=a.updateQueue,null!==c&&(b.updateQueue=c,b.flags|=4),Dc(f,!0),null===f.tail&&"hidden"===f.tailMode&&!g.alternate&&!D)return W(b),null}else 2*P()-f.renderingStartTime>Hf&&1073741824!==c&&(b.flags|=
+128,d=!0,Dc(f,!1),b.lanes=4194304);f.isBackwards?(g.sibling=b.child,b.child=g):(c=f.last,null!==c?c.sibling=g:b.child=g,f.last=g)}if(null!==f.tail)return b=f.tail,f.rendering=b,f.tail=b.sibling,f.renderingStartTime=P(),b.sibling=null,c=F.current,y(F,d?c&1|2:c&1),b;W(b);return null;case 22:case 23:return ba=Ga.current,v(Ga),d=null!==b.memoizedState,null!==a&&null!==a.memoizedState!==d&&(b.flags|=8192),d&&0!==(b.mode&1)?0!==(ba&1073741824)&&(W(b),b.subtreeFlags&6&&(b.flags|=8192)):W(b),null;case 24:return null;
+case 25:return null}throw Error(m(156,b.tag));}function Bk(a,b,c){Ve(b);switch(b.tag){case 1:return ea(b.type)&&(v(S),v(J)),a=b.flags,a&65536?(b.flags=a&-65537|128,b):null;case 3:return Tb(),v(S),v(J),jf(),a=b.flags,0!==(a&65536)&&0===(a&128)?(b.flags=a&-65537|128,b):null;case 5:return hf(b),null;case 13:v(F);a=b.memoizedState;if(null!==a&&null!==a.dehydrated){if(null===b.alternate)throw Error(m(340));Qb()}a=b.flags;return a&65536?(b.flags=a&-65537|128,b):null;case 19:return v(F),null;case 4:return Tb(),
+null;case 10:return cf(b.type._context),null;case 22:case 23:return ba=Ga.current,v(Ga),null;case 24:return null;default:return null}}function Wb(a,b){var c=a.ref;if(null!==c)if("function"===typeof c)try{c(null)}catch(d){G(a,b,d)}else c.current=null}function If(a,b,c){try{c()}catch(d){G(a,b,d)}}function Ck(a,b){Jf=Zc;a=ch();if(Ie(a)){if("selectionStart"in a)var c={start:a.selectionStart,end:a.selectionEnd};else a:{c=(c=a.ownerDocument)&&c.defaultView||window;var d=c.getSelection&&c.getSelection();
+if(d&&0!==d.rangeCount){c=d.anchorNode;var e=d.anchorOffset,f=d.focusNode;d=d.focusOffset;try{c.nodeType,f.nodeType}catch(M){c=null;break a}var g=0,h=-1,k=-1,n=0,q=0,u=a,r=null;b:for(;;){for(var p;;){u!==c||0!==e&&3!==u.nodeType||(h=g+e);u!==f||0!==d&&3!==u.nodeType||(k=g+d);3===u.nodeType&&(g+=u.nodeValue.length);if(null===(p=u.firstChild))break;r=u;u=p}for(;;){if(u===a)break b;r===c&&++n===e&&(h=g);r===f&&++q===d&&(k=g);if(null!==(p=u.nextSibling))break;u=r;r=u.parentNode}u=p}c=-1===h||-1===k?null:
+{start:h,end:k}}else c=null}c=c||{start:0,end:0}}else c=null;Kf={focusedElem:a,selectionRange:c};Zc=!1;for(l=b;null!==l;)if(b=l,a=b.child,0!==(b.subtreeFlags&1028)&&null!==a)a.return=b,l=a;else for(;null!==l;){b=l;try{var x=b.alternate;if(0!==(b.flags&1024))switch(b.tag){case 0:case 11:case 15:break;case 1:if(null!==x){var v=x.memoizedProps,z=x.memoizedState,w=b.stateNode,A=w.getSnapshotBeforeUpdate(b.elementType===b.type?v:ya(b.type,v),z);w.__reactInternalSnapshotBeforeUpdate=A}break;case 3:var t=
+b.stateNode.containerInfo;1===t.nodeType?t.textContent="":9===t.nodeType&&t.documentElement&&t.removeChild(t.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(m(163));}}catch(M){G(b,b.return,M)}a=b.sibling;if(null!==a){a.return=b.return;l=a;break}l=b.return}x=zi;zi=!1;return x}function Gc(a,b,c){var d=b.updateQueue;d=null!==d?d.lastEffect:null;if(null!==d){var e=d=d.next;do{if((e.tag&a)===a){var f=e.destroy;e.destroy=void 0;void 0!==f&&If(b,c,f)}e=e.next}while(e!==d)}}
+function Id(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.create;c.destroy=d()}c=c.next}while(c!==b)}}function Lf(a){var b=a.ref;if(null!==b){var c=a.stateNode;switch(a.tag){case 5:a=c;break;default:a=c}"function"===typeof b?b(a):b.current=a}}function Ai(a){var b=a.alternate;null!==b&&(a.alternate=null,Ai(b));a.child=null;a.deletions=null;a.sibling=null;5===a.tag&&(b=a.stateNode,null!==b&&(delete b[Da],delete b[uc],delete b[Me],delete b[Dk],
+delete b[Ek]));a.stateNode=null;a.return=null;a.dependencies=null;a.memoizedProps=null;a.memoizedState=null;a.pendingProps=null;a.stateNode=null;a.updateQueue=null}function Bi(a){return 5===a.tag||3===a.tag||4===a.tag}function Ci(a){a:for(;;){for(;null===a.sibling;){if(null===a.return||Bi(a.return))return null;a=a.return}a.sibling.return=a.return;for(a=a.sibling;5!==a.tag&&6!==a.tag&&18!==a.tag;){if(a.flags&2)continue a;if(null===a.child||4===a.tag)continue a;else a.child.return=a,a=a.child}if(!(a.flags&
+2))return a.stateNode}}function Mf(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?8===c.nodeType?c.parentNode.insertBefore(a,b):c.insertBefore(a,b):(8===c.nodeType?(b=c.parentNode,b.insertBefore(a,c)):(b=c,b.appendChild(a)),c=c._reactRootContainer,null!==c&&void 0!==c||null!==b.onclick||(b.onclick=kd));else if(4!==d&&(a=a.child,null!==a))for(Mf(a,b,c),a=a.sibling;null!==a;)Mf(a,b,c),a=a.sibling}function Nf(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?c.insertBefore(a,b):c.appendChild(a);
+else if(4!==d&&(a=a.child,null!==a))for(Nf(a,b,c),a=a.sibling;null!==a;)Nf(a,b,c),a=a.sibling}function jb(a,b,c){for(c=c.child;null!==c;)Di(a,b,c),c=c.sibling}function Di(a,b,c){if(Ca&&"function"===typeof Ca.onCommitFiberUnmount)try{Ca.onCommitFiberUnmount(Uc,c)}catch(h){}switch(c.tag){case 5:X||Wb(c,b);case 6:var d=T,e=za;T=null;jb(a,b,c);T=d;za=e;null!==T&&(za?(a=T,c=c.stateNode,8===a.nodeType?a.parentNode.removeChild(c):a.removeChild(c)):T.removeChild(c.stateNode));break;case 18:null!==T&&(za?
+(a=T,c=c.stateNode,8===a.nodeType?Re(a.parentNode,c):1===a.nodeType&&Re(a,c),nc(a)):Re(T,c.stateNode));break;case 4:d=T;e=za;T=c.stateNode.containerInfo;za=!0;jb(a,b,c);T=d;za=e;break;case 0:case 11:case 14:case 15:if(!X&&(d=c.updateQueue,null!==d&&(d=d.lastEffect,null!==d))){e=d=d.next;do{var f=e,g=f.destroy;f=f.tag;void 0!==g&&(0!==(f&2)?If(c,b,g):0!==(f&4)&&If(c,b,g));e=e.next}while(e!==d)}jb(a,b,c);break;case 1:if(!X&&(Wb(c,b),d=c.stateNode,"function"===typeof d.componentWillUnmount))try{d.props=
+c.memoizedProps,d.state=c.memoizedState,d.componentWillUnmount()}catch(h){G(c,b,h)}jb(a,b,c);break;case 21:jb(a,b,c);break;case 22:c.mode&1?(X=(d=X)||null!==c.memoizedState,jb(a,b,c),X=d):jb(a,b,c);break;default:jb(a,b,c)}}function Ei(a){var b=a.updateQueue;if(null!==b){a.updateQueue=null;var c=a.stateNode;null===c&&(c=a.stateNode=new Fk);b.forEach(function(b){var d=Gk.bind(null,a,b);c.has(b)||(c.add(b),b.then(d,d))})}}function Aa(a,b,c){c=b.deletions;if(null!==c)for(var d=0;de&&(e=g);d&=~f}d=e;d=P()-d;d=(120>d?120:480>d?480:1080>d?1080:1920>d?1920:3E3>d?3E3:4320>d?4320:1960*Mk(d/1960))-d;if(10a?16:a;if(null===lb)var d=!1;else{a=lb;lb=null;Qd=0;if(0!==(p&6))throw Error(m(331));var e=p;p|=4;for(l=a.current;null!==l;){var f=l,g=f.child;if(0!==(l.flags&16)){var h=f.deletions;if(null!==h){for(var k=0;kP()-Of?wb(a,0):Sf|=c);ia(a,b)}function Ti(a,b){0===b&&(0===(a.mode&1)?b=1:(b=Rd,Rd<<=1,0===(Rd&130023424)&&(Rd=4194304)));var c=Z();a=Oa(a,b);null!==a&&(ic(a,b,c),ia(a,c))}function vk(a){var b=a.memoizedState,c=0;null!==b&&(c=b.retryLane);Ti(a,c)}function Gk(a,b){var c=0;switch(a.tag){case 13:var d=a.stateNode;var e=a.memoizedState;null!==e&&(c=e.retryLane);
+break;case 19:d=a.stateNode;break;default:throw Error(m(314));}null!==d&&d.delete(b);Ti(a,c)}function Mi(a,b){return xh(a,b)}function Tk(a,b,c,d){this.tag=a;this.key=c;this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null;this.index=0;this.ref=null;this.pendingProps=b;this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null;this.mode=d;this.subtreeFlags=this.flags=0;this.deletions=null;this.childLanes=this.lanes=0;this.alternate=null}function yf(a){a=
+a.prototype;return!(!a||!a.isReactComponent)}function Uk(a){if("function"===typeof a)return yf(a)?1:0;if(void 0!==a&&null!==a){a=a.$$typeof;if(a===ie)return 11;if(a===je)return 14}return 2}function eb(a,b){var c=a.alternate;null===c?(c=pa(a.tag,b,a.key,a.mode),c.elementType=a.elementType,c.type=a.type,c.stateNode=a.stateNode,c.alternate=a,a.alternate=c):(c.pendingProps=b,c.type=a.type,c.flags=0,c.subtreeFlags=0,c.deletions=null);c.flags=a.flags&14680064;c.childLanes=a.childLanes;c.lanes=a.lanes;c.child=
+a.child;c.memoizedProps=a.memoizedProps;c.memoizedState=a.memoizedState;c.updateQueue=a.updateQueue;b=a.dependencies;c.dependencies=null===b?null:{lanes:b.lanes,firstContext:b.firstContext};c.sibling=a.sibling;c.index=a.index;c.ref=a.ref;return c}function rd(a,b,c,d,e,f){var g=2;d=a;if("function"===typeof a)yf(a)&&(g=1);else if("string"===typeof a)g=5;else a:switch(a){case Bb:return sb(c.children,e,f,b);case fe:g=8;e|=8;break;case ee:return a=pa(12,c,b,e|2),a.elementType=ee,a.lanes=f,a;case ge:return a=
+pa(13,c,b,e),a.elementType=ge,a.lanes=f,a;case he:return a=pa(19,c,b,e),a.elementType=he,a.lanes=f,a;case Ui:return Gd(c,e,f,b);default:if("object"===typeof a&&null!==a)switch(a.$$typeof){case hg:g=10;break a;case gg:g=9;break a;case ie:g=11;break a;case je:g=14;break a;case Ta:g=16;d=null;break a}throw Error(m(130,null==a?a:typeof a,""));}b=pa(g,c,b,e);b.elementType=a;b.type=d;b.lanes=f;return b}function sb(a,b,c,d){a=pa(7,a,d,b);a.lanes=c;return a}function Gd(a,b,c,d){a=pa(22,a,d,b);a.elementType=
+Ui;a.lanes=c;a.stateNode={isHidden:!1};return a}function Ze(a,b,c){a=pa(6,a,null,b);a.lanes=c;return a}function $e(a,b,c){b=pa(4,null!==a.children?a.children:[],a.key,b);b.lanes=c;b.stateNode={containerInfo:a.containerInfo,pendingChildren:null,implementation:a.implementation};return b}function Vk(a,b,c,d,e){this.tag=b;this.containerInfo=a;this.finishedWork=this.pingCache=this.current=this.pendingChildren=null;this.timeoutHandle=-1;this.callbackNode=this.pendingContext=this.context=null;this.callbackPriority=
+0;this.eventTimes=we(0);this.expirationTimes=we(-1);this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0;this.entanglements=we(0);this.identifierPrefix=d;this.onRecoverableError=e;this.mutableSourceEagerHydrationData=null}function Vf(a,b,c,d,e,f,g,h,k,l){a=new Vk(a,b,c,h,k);1===b?(b=1,!0===f&&(b|=8)):b=0;f=pa(3,null,null,b);a.current=f;f.stateNode=a;f.memoizedState={element:d,isDehydrated:c,cache:null,transitions:null,
+pendingSuspenseBoundaries:null};ff(f);return a}function Wk(a,b,c){var d=3"+b.valueOf().toString()+"";for(b=Xd.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;b.firstChild;)a.appendChild(b.firstChild)}}),Fc=function(a,b){if(b){var c=a.firstChild;if(c&&c===a.lastChild&&3===c.nodeType){c.nodeValue=b;return}}a.textContent=b},dc={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,
+borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,
+strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},$k=["Webkit","ms","Moz","O"];Object.keys(dc).forEach(function(a){$k.forEach(function(b){b=b+a.charAt(0).toUpperCase()+a.substring(1);dc[b]=dc[a]})});var ij=E({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),ze=null,se=null,Eb=null,Fb=null,xg=function(a,b){return a(b)},yg=function(){},te=!1,Oe=!1;if(Ia)try{var Lc={};Object.defineProperty(Lc,
+"passive",{get:function(){Oe=!0}});window.addEventListener("test",Lc,Lc);window.removeEventListener("test",Lc,Lc)}catch(a){Oe=!1}var kj=function(a,b,c,d,e,f,g,h,k){var l=Array.prototype.slice.call(arguments,3);try{b.apply(c,l)}catch(q){this.onError(q)}},gc=!1,Sc=null,Tc=!1,ue=null,lj={onError:function(a){gc=!0;Sc=a}},Ba=zb.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler,Jg=Ba.unstable_scheduleCallback,Kg=Ba.unstable_NormalPriority,xh=Jg,Ki=Ba.unstable_cancelCallback,Pk=Ba.unstable_shouldYield,
+Sk=Ba.unstable_requestPaint,P=Ba.unstable_now,Dj=Ba.unstable_getCurrentPriorityLevel,De=Ba.unstable_ImmediatePriority,Mg=Ba.unstable_UserBlockingPriority,ad=Kg,Ej=Ba.unstable_LowPriority,Ng=Ba.unstable_IdlePriority,Uc=null,Ca=null,ta=Math.clz32?Math.clz32:pj,qj=Math.log,rj=Math.LN2,Wc=64,Rd=4194304,z=0,Ae=!1,Yc=[],Va=null,Wa=null,Xa=null,jc=new Map,kc=new Map,Ya=[],Bj="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset submit".split(" "),
+Gb=Sa.ReactCurrentBatchConfig,Zc=!0,$c=null,Za=null,Ee=null,bd=null,Yb={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(a){return a.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},He=ka(Yb),Mc=E({},Yb,{view:0,detail:0}),ak=ka(Mc),ag,bg,Nc,Yd=E({},Mc,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:Fe,button:0,buttons:0,relatedTarget:function(a){return void 0===a.relatedTarget?a.fromElement===a.srcElement?a.toElement:a.fromElement:
+a.relatedTarget},movementX:function(a){if("movementX"in a)return a.movementX;a!==Nc&&(Nc&&"mousemove"===a.type?(ag=a.screenX-Nc.screenX,bg=a.screenY-Nc.screenY):bg=ag=0,Nc=a);return ag},movementY:function(a){return"movementY"in a?a.movementY:bg}}),ih=ka(Yd),al=E({},Yd,{dataTransfer:0}),Wj=ka(al),bl=E({},Mc,{relatedTarget:0}),Pe=ka(bl),cl=E({},Yb,{animationName:0,elapsedTime:0,pseudoElement:0}),Yj=ka(cl),dl=E({},Yb,{clipboardData:function(a){return"clipboardData"in a?a.clipboardData:window.clipboardData}}),
+ck=ka(dl),el=E({},Yb,{data:0}),qh=ka(el),fk=qh,fl={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},gl={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",
+112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},Gj={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"},hl=E({},Mc,{key:function(a){if(a.key){var b=fl[a.key]||a.key;if("Unidentified"!==b)return b}return"keypress"===a.type?(a=cd(a),13===a?"Enter":String.fromCharCode(a)):"keydown"===a.type||"keyup"===a.type?gl[a.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,
+metaKey:0,repeat:0,locale:0,getModifierState:Fe,charCode:function(a){return"keypress"===a.type?cd(a):0},keyCode:function(a){return"keydown"===a.type||"keyup"===a.type?a.keyCode:0},which:function(a){return"keypress"===a.type?cd(a):"keydown"===a.type||"keyup"===a.type?a.keyCode:0}}),Vj=ka(hl),il=E({},Yd,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0}),nh=ka(il),jl=E({},Mc,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,
+ctrlKey:0,shiftKey:0,getModifierState:Fe}),Xj=ka(jl),kl=E({},Yb,{propertyName:0,elapsedTime:0,pseudoElement:0}),Zj=ka(kl),ll=E({},Yd,{deltaX:function(a){return"deltaX"in a?a.deltaX:"wheelDeltaX"in a?-a.wheelDeltaX:0},deltaY:function(a){return"deltaY"in a?a.deltaY:"wheelDeltaY"in a?-a.wheelDeltaY:"wheelDelta"in a?-a.wheelDelta:0},deltaZ:0,deltaMode:0}),bk=ka(ll),Hj=[9,13,27,32],Ge=Ia&&"CompositionEvent"in window,Oc=null;Ia&&"documentMode"in document&&(Oc=document.documentMode);var ek=Ia&&"TextEvent"in
+window&&!Oc,Ug=Ia&&(!Ge||Oc&&8=Oc),Tg=String.fromCharCode(32),Sg=!1,Hb=!1,Kj={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0},oc=null,pc=null,ph=!1;Ia&&(ph=Lj("input")&&(!document.documentMode||9=document.documentMode,Jb=null,Ke=null,rc=null,Je=!1,Kb={animationend:gd("Animation","AnimationEnd"),
+animationiteration:gd("Animation","AnimationIteration"),animationstart:gd("Animation","AnimationStart"),transitionend:gd("Transition","TransitionEnd")},Le={},eh={};Ia&&(eh=document.createElement("div").style,"AnimationEvent"in window||(delete Kb.animationend.animation,delete Kb.animationiteration.animation,delete Kb.animationstart.animation),"TransitionEvent"in window||delete Kb.transitionend.transition);var jh=hd("animationend"),kh=hd("animationiteration"),lh=hd("animationstart"),mh=hd("transitionend"),
+fh=new Map,Zi="abort auxClick cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");
+(function(){for(var a=0;ab}return!1}function Y(a,b,c,d,e,f,g){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f;this.removeEmptyString=g}function $d(a,b,c,d){var e=R.hasOwnProperty(b)?R[b]:null;if(null!==e?0!==e.type:d||!(2h||e[g]!==f[h]){var k="\n"+e[g].replace(" at new "," at ");a.displayName&&k.includes("")&&(k=k.replace("",a.displayName));return k}while(1<=g&&0<=h)}break}}}finally{ce=!1,Error.prepareStackTrace=c}return(a=a?a.displayName||a.name:"")?bc(a):
+""}function fj(a){switch(a.tag){case 5:return bc(a.type);case 16:return bc("Lazy");case 13:return bc("Suspense");case 19:return bc("SuspenseList");case 0:case 2:case 15:return a=be(a.type,!1),a;case 11:return a=be(a.type.render,!1),a;case 1:return a=be(a.type,!0),a;default:return""}}function de(a){if(null==a)return null;if("function"===typeof a)return a.displayName||a.name||null;if("string"===typeof a)return a;switch(a){case Bb:return"Fragment";case Cb:return"Portal";case ee:return"Profiler";case fe:return"StrictMode";
+case ge:return"Suspense";case he:return"SuspenseList"}if("object"===typeof a)switch(a.$$typeof){case gg:return(a.displayName||"Context")+".Consumer";case hg:return(a._context.displayName||"Context")+".Provider";case ie:var b=a.render;a=a.displayName;a||(a=b.displayName||b.name||"",a=""!==a?"ForwardRef("+a+")":"ForwardRef");return a;case je:return b=a.displayName||null,null!==b?b:de(a.type)||"Memo";case Ta:b=a._payload;a=a._init;try{return de(a(b))}catch(c){}}return null}function gj(a){var b=a.type;
+switch(a.tag){case 24:return"Cache";case 9:return(b.displayName||"Context")+".Consumer";case 10:return(b._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return a=b.render,a=a.displayName||a.name||"",b.displayName||(""!==a?"ForwardRef("+a+")":"ForwardRef");case 7:return"Fragment";case 5:return b;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return de(b);case 8:return b===fe?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";
+case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if("function"===typeof b)return b.displayName||b.name||null;if("string"===typeof b)return b}return null}function Ua(a){switch(typeof a){case "boolean":case "number":case "string":case "undefined":return a;case "object":return a;default:return""}}function ig(a){var b=a.type;return(a=a.nodeName)&&"input"===a.toLowerCase()&&("checkbox"===b||"radio"===
+b)}function hj(a){var b=ig(a)?"checked":"value",c=Object.getOwnPropertyDescriptor(a.constructor.prototype,b),d=""+a[b];if(!a.hasOwnProperty(b)&&"undefined"!==typeof c&&"function"===typeof c.get&&"function"===typeof c.set){var e=c.get,f=c.set;Object.defineProperty(a,b,{configurable:!0,get:function(){return e.call(this)},set:function(a){d=""+a;f.call(this,a)}});Object.defineProperty(a,b,{enumerable:c.enumerable});return{getValue:function(){return d},setValue:function(a){d=""+a},stopTracking:function(){a._valueTracker=
+null;delete a[b]}}}}function Pc(a){a._valueTracker||(a._valueTracker=hj(a))}function jg(a){if(!a)return!1;var b=a._valueTracker;if(!b)return!0;var c=b.getValue();var d="";a&&(d=ig(a)?a.checked?"true":"false":a.value);a=d;return a!==c?(b.setValue(a),!0):!1}function Qc(a){a=a||("undefined"!==typeof document?document:void 0);if("undefined"===typeof a)return null;try{return a.activeElement||a.body}catch(b){return a.body}}function ke(a,b){var c=b.checked;return E({},b,{defaultChecked:void 0,defaultValue:void 0,
+value:void 0,checked:null!=c?c:a._wrapperState.initialChecked})}function kg(a,b){var c=null==b.defaultValue?"":b.defaultValue,d=null!=b.checked?b.checked:b.defaultChecked;c=Ua(null!=b.value?b.value:c);a._wrapperState={initialChecked:d,initialValue:c,controlled:"checkbox"===b.type||"radio"===b.type?null!=b.checked:null!=b.value}}function lg(a,b){b=b.checked;null!=b&&$d(a,"checked",b,!1)}function le(a,b){lg(a,b);var c=Ua(b.value),d=b.type;if(null!=c)if("number"===d){if(0===c&&""===a.value||a.value!=
+c)a.value=""+c}else a.value!==""+c&&(a.value=""+c);else if("submit"===d||"reset"===d){a.removeAttribute("value");return}b.hasOwnProperty("value")?me(a,b.type,c):b.hasOwnProperty("defaultValue")&&me(a,b.type,Ua(b.defaultValue));null==b.checked&&null!=b.defaultChecked&&(a.defaultChecked=!!b.defaultChecked)}function mg(a,b,c){if(b.hasOwnProperty("value")||b.hasOwnProperty("defaultValue")){var d=b.type;if(!("submit"!==d&&"reset"!==d||void 0!==b.value&&null!==b.value))return;b=""+a._wrapperState.initialValue;
+c||b===a.value||(a.value=b);a.defaultValue=b}c=a.name;""!==c&&(a.name="");a.defaultChecked=!!a._wrapperState.initialChecked;""!==c&&(a.name=c)}function me(a,b,c){if("number"!==b||Qc(a.ownerDocument)!==a)null==c?a.defaultValue=""+a._wrapperState.initialValue:a.defaultValue!==""+c&&(a.defaultValue=""+c)}function Db(a,b,c,d){a=a.options;if(b){b={};for(var e=0;e>>=0;return 0===a?32:31-(qj(a)/rj|0)|0}function hc(a){switch(a&-a){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return a&
+4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return a&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return a}}function Vc(a,b){var c=a.pendingLanes;if(0===c)return 0;var d=0,e=a.suspendedLanes,f=a.pingedLanes,g=c&268435455;if(0!==g){var h=g&~e;0!==h?d=hc(h):(f&=g,0!==f&&(d=hc(f)))}else g=c&~e,0!==g?d=hc(g):0!==f&&(d=hc(f));if(0===d)return 0;if(0!==b&&b!==d&&0===(b&e)&&
+(e=d&-d,f=b&-b,e>=f||16===e&&0!==(f&4194240)))return b;0!==(d&4)&&(d|=c&16);b=a.entangledLanes;if(0!==b)for(a=a.entanglements,b&=d;0c;c++)b.push(a);
+return b}function ic(a,b,c){a.pendingLanes|=b;536870912!==b&&(a.suspendedLanes=0,a.pingedLanes=0);a=a.eventTimes;b=31-ta(b);a[b]=c}function uj(a,b){var c=a.pendingLanes&~b;a.pendingLanes=b;a.suspendedLanes=0;a.pingedLanes=0;a.expiredLanes&=b;a.mutableReadLanes&=b;a.entangledLanes&=b;b=a.entanglements;var d=a.eventTimes;for(a=a.expirationTimes;0=b)return{node:c,offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=$g(c)}}function bh(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?bh(a,b.parentNode):"contains"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1}function ch(){for(var a=window,b=Qc();b instanceof a.HTMLIFrameElement;){try{var c="string"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break;
+b=Qc(a.document)}return b}function Ie(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&("input"===b&&("text"===a.type||"search"===a.type||"tel"===a.type||"url"===a.type||"password"===a.type)||"textarea"===b||"true"===a.contentEditable)}function Tj(a){var b=ch(),c=a.focusedElem,d=a.selectionRange;if(b!==c&&c&&c.ownerDocument&&bh(c.ownerDocument.documentElement,c)){if(null!==d&&Ie(c))if(b=d.start,a=d.end,void 0===a&&(a=b),"selectionStart"in c)c.selectionStart=b,c.selectionEnd=Math.min(a,c.value.length);
+else if(a=(b=c.ownerDocument||document)&&b.defaultView||window,a.getSelection){a=a.getSelection();var e=c.textContent.length,f=Math.min(d.start,e);d=void 0===d.end?f:Math.min(d.end,e);!a.extend&&f>d&&(e=d,d=f,f=e);e=ah(c,f);var g=ah(c,d);e&&g&&(1!==a.rangeCount||a.anchorNode!==e.node||a.anchorOffset!==e.offset||a.focusNode!==g.node||a.focusOffset!==g.offset)&&(b=b.createRange(),b.setStart(e.node,e.offset),a.removeAllRanges(),f>d?(a.addRange(b),a.extend(g.node,g.offset)):(b.setEnd(g.node,g.offset),
+a.addRange(b)))}b=[];for(a=c;a=a.parentNode;)1===a.nodeType&&b.push({element:a,left:a.scrollLeft,top:a.scrollTop});"function"===typeof c.focus&&c.focus();for(c=0;cMb||(a.current=Se[Mb],Se[Mb]=null,Mb--)}
+function y(a,b,c){Mb++;Se[Mb]=a.current;a.current=b}function Nb(a,b){var c=a.type.contextTypes;if(!c)return cb;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function ea(a){a=a.childContextTypes;return null!==a&&void 0!==a}function th(a,b,c){if(J.current!==cb)throw Error(m(168));
+y(J,b);y(S,c)}function uh(a,b,c){var d=a.stateNode;b=b.childContextTypes;if("function"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in b))throw Error(m(108,gj(a)||"Unknown",e));return E({},c,d)}function ld(a){a=(a=a.stateNode)&&a.__reactInternalMemoizedMergedChildContext||cb;pb=J.current;y(J,a);y(S,S.current);return!0}function vh(a,b,c){var d=a.stateNode;if(!d)throw Error(m(169));c?(a=uh(a,b,pb),d.__reactInternalMemoizedMergedChildContext=a,v(S),v(J),y(J,a)):v(S);
+y(S,c)}function wh(a){null===La?La=[a]:La.push(a)}function jk(a){md=!0;wh(a)}function db(){if(!Te&&null!==La){Te=!0;var a=0,b=z;try{var c=La;for(z=1;a>=g;e-=g;Ma=1<<32-ta(b)+e|c<t?(q=l,l=null):q=l.sibling;var A=r(e,l,h[t],k);if(null===A){null===l&&(l=q);break}a&&l&&null===A.alternate&&b(e,l);g=f(A,g,t);null===m?n=A:m.sibling=A;m=A;l=q}if(t===h.length)return c(e,l),D&&qb(e,t),n;if(null===l){for(;t<
+h.length;t++)l=u(e,h[t],k),null!==l&&(g=f(l,g,t),null===m?n=l:m.sibling=l,m=l);D&&qb(e,t);return n}for(l=d(e,l);tt?(A=q,q=null):A=q.sibling;var x=r(e,q,w.value,k);if(null===x){null===q&&(q=A);break}a&&q&&null===x.alternate&&b(e,q);g=f(x,g,t);null===l?n=x:l.sibling=x;l=x;q=A}if(w.done)return c(e,q),D&&qb(e,t),n;if(null===q){for(;!w.done;t++,w=h.next())w=u(e,w.value,k),null!==w&&(g=f(w,g,t),null===l?n=w:l.sibling=w,l=w);D&&qb(e,t);return n}for(q=d(e,q);!w.done;t++,w=h.next())w=p(q,e,t,w.value,k),null!==w&&(a&&null!==w.alternate&&q.delete(null===w.key?t:w.key),g=f(w,g,t),null===l?n=w:l.sibling=
+w,l=w);a&&q.forEach(function(a){return b(e,a)});D&&qb(e,t);return n}function v(a,d,f,h){"object"===typeof f&&null!==f&&f.type===Bb&&null===f.key&&(f=f.props.children);if("object"===typeof f&&null!==f){switch(f.$$typeof){case sd:a:{for(var k=f.key,n=d;null!==n;){if(n.key===k){k=f.type;if(k===Bb){if(7===n.tag){c(a,n.sibling);d=e(n,f.props.children);d.return=a;a=d;break a}}else if(n.elementType===k||"object"===typeof k&&null!==k&&k.$$typeof===Ta&&Ch(k)===n.type){c(a,n.sibling);d=e(n,f.props);d.ref=vc(a,
+n,f);d.return=a;a=d;break a}c(a,n);break}else b(a,n);n=n.sibling}f.type===Bb?(d=sb(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=rd(f.type,f.key,f.props,null,a.mode,h),h.ref=vc(a,d,f),h.return=a,a=h)}return g(a);case Cb:a:{for(n=f.key;null!==d;){if(d.key===n)if(4===d.tag&&d.stateNode.containerInfo===f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[]);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=$e(f,a.mode,h);d.return=a;
+a=d}return g(a);case Ta:return n=f._init,v(a,d,n(f._payload),h)}if(cc(f))return x(a,d,f,h);if(ac(f))return I(a,d,f,h);qd(a,f)}return"string"===typeof f&&""!==f||"number"===typeof f?(f=""+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f),d.return=a,a=d):(c(a,d),d=Ze(f,a.mode,h),d.return=a,a=d),g(a)):c(a,d)}return v}function af(){bf=Rb=td=null}function cf(a,b){b=ud.current;v(ud);a._currentValue=b}function df(a,b,c){for(;null!==a;){var d=a.alternate;(a.childLanes&b)!==b?(a.childLanes|=b,null!==d&&(d.childLanes|=
+b)):null!==d&&(d.childLanes&b)!==b&&(d.childLanes|=b);if(a===c)break;a=a.return}}function Sb(a,b){td=a;bf=Rb=null;a=a.dependencies;null!==a&&null!==a.firstContext&&(0!==(a.lanes&b)&&(ha=!0),a.firstContext=null)}function qa(a){var b=a._currentValue;if(bf!==a)if(a={context:a,memoizedValue:b,next:null},null===Rb){if(null===td)throw Error(m(308));Rb=a;td.dependencies={lanes:0,firstContext:a}}else Rb=Rb.next=a;return b}function ef(a){null===tb?tb=[a]:tb.push(a)}function Eh(a,b,c,d){var e=b.interleaved;
+null===e?(c.next=c,ef(b)):(c.next=e.next,e.next=c);b.interleaved=c;return Oa(a,d)}function Oa(a,b){a.lanes|=b;var c=a.alternate;null!==c&&(c.lanes|=b);c=a;for(a=a.return;null!==a;)a.childLanes|=b,c=a.alternate,null!==c&&(c.childLanes|=b),c=a,a=a.return;return 3===c.tag?c.stateNode:null}function ff(a){a.updateQueue={baseState:a.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Fh(a,b){a=a.updateQueue;b.updateQueue===a&&(b.updateQueue=
+{baseState:a.baseState,firstBaseUpdate:a.firstBaseUpdate,lastBaseUpdate:a.lastBaseUpdate,shared:a.shared,effects:a.effects})}function Pa(a,b){return{eventTime:a,lane:b,tag:0,payload:null,callback:null,next:null}}function fb(a,b,c){var d=a.updateQueue;if(null===d)return null;d=d.shared;if(0!==(p&2)){var e=d.pending;null===e?b.next=b:(b.next=e.next,e.next=b);d.pending=b;return kk(a,c)}e=d.interleaved;null===e?(b.next=b,ef(d)):(b.next=e.next,e.next=b);d.interleaved=b;return Oa(a,c)}function vd(a,b,c){b=
+b.updateQueue;if(null!==b&&(b=b.shared,0!==(c&4194240))){var d=b.lanes;d&=a.pendingLanes;c|=d;b.lanes=c;xe(a,c)}}function Gh(a,b){var c=a.updateQueue,d=a.alternate;if(null!==d&&(d=d.updateQueue,c===d)){var e=null,f=null;c=c.firstBaseUpdate;if(null!==c){do{var g={eventTime:c.eventTime,lane:c.lane,tag:c.tag,payload:c.payload,callback:c.callback,next:null};null===f?e=f=g:f=f.next=g;c=c.next}while(null!==c);null===f?e=f=b:f=f.next=b}else e=f=b;c={baseState:d.baseState,firstBaseUpdate:e,lastBaseUpdate:f,
+shared:d.shared,effects:d.effects};a.updateQueue=c;return}a=c.lastBaseUpdate;null===a?c.firstBaseUpdate=b:a.next=b;c.lastBaseUpdate=b}function wd(a,b,c,d){var e=a.updateQueue;gb=!1;var f=e.firstBaseUpdate,g=e.lastBaseUpdate,h=e.shared.pending;if(null!==h){e.shared.pending=null;var k=h,n=k.next;k.next=null;null===g?f=n:g.next=n;g=k;var l=a.alternate;null!==l&&(l=l.updateQueue,h=l.lastBaseUpdate,h!==g&&(null===h?l.firstBaseUpdate=n:h.next=n,l.lastBaseUpdate=k))}if(null!==f){var m=e.baseState;g=0;l=
+n=k=null;h=f;do{var r=h.lane,p=h.eventTime;if((d&r)===r){null!==l&&(l=l.next={eventTime:p,lane:0,tag:h.tag,payload:h.payload,callback:h.callback,next:null});a:{var x=a,v=h;r=b;p=c;switch(v.tag){case 1:x=v.payload;if("function"===typeof x){m=x.call(p,m,r);break a}m=x;break a;case 3:x.flags=x.flags&-65537|128;case 0:x=v.payload;r="function"===typeof x?x.call(p,m,r):x;if(null===r||void 0===r)break a;m=E({},m,r);break a;case 2:gb=!0}}null!==h.callback&&0!==h.lane&&(a.flags|=64,r=e.effects,null===r?e.effects=
+[h]:r.push(h))}else p={eventTime:p,lane:r,tag:h.tag,payload:h.payload,callback:h.callback,next:null},null===l?(n=l=p,k=m):l=l.next=p,g|=r;h=h.next;if(null===h)if(h=e.shared.pending,null===h)break;else r=h,h=r.next,r.next=null,e.lastBaseUpdate=r,e.shared.pending=null}while(1);null===l&&(k=m);e.baseState=k;e.firstBaseUpdate=n;e.lastBaseUpdate=l;b=e.shared.interleaved;if(null!==b){e=b;do g|=e.lane,e=e.next;while(e!==b)}else null===f&&(e.shared.lanes=0);ra|=g;a.lanes=g;a.memoizedState=m}}function Hh(a,
+b,c){a=b.effects;b.effects=null;if(null!==a)for(b=0;bc?c:4;a(!0);var d=sf.transition;sf.transition=
+{};try{a(!1),b()}finally{z=c,sf.transition=d}}function $h(){return sa().memoizedState}function qk(a,b,c){var d=hb(a);c={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(ai(a))bi(b,c);else if(c=Eh(a,b,c,d),null!==c){var e=Z();xa(c,a,d,e);ci(c,b,d)}}function ok(a,b,c){var d=hb(a),e={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(ai(a))bi(b,e);else{var f=a.alternate;if(0===a.lanes&&(null===f||0===f.lanes)&&(f=b.lastRenderedReducer,null!==f))try{var g=b.lastRenderedState,
+h=f(g,c);e.hasEagerState=!0;e.eagerState=h;if(ua(h,g)){var k=b.interleaved;null===k?(e.next=e,ef(b)):(e.next=k.next,k.next=e);b.interleaved=e;return}}catch(n){}finally{}c=Eh(a,b,e,d);null!==c&&(e=Z(),xa(c,a,d,e),ci(c,b,d))}}function ai(a){var b=a.alternate;return a===C||null!==b&&b===C}function bi(a,b){zc=Ad=!0;var c=a.pending;null===c?b.next=b:(b.next=c.next,c.next=b);a.pending=b}function ci(a,b,c){if(0!==(c&4194240)){var d=b.lanes;d&=a.pendingLanes;c|=d;b.lanes=c;xe(a,c)}}function ya(a,b){if(a&&
+a.defaultProps){b=E({},b);a=a.defaultProps;for(var c in a)void 0===b[c]&&(b[c]=a[c]);return b}return b}function tf(a,b,c,d){b=a.memoizedState;c=c(d,b);c=null===c||void 0===c?b:E({},b,c);a.memoizedState=c;0===a.lanes&&(a.updateQueue.baseState=c)}function di(a,b,c,d,e,f,g){a=a.stateNode;return"function"===typeof a.shouldComponentUpdate?a.shouldComponentUpdate(d,f,g):b.prototype&&b.prototype.isPureReactComponent?!qc(c,d)||!qc(e,f):!0}function ei(a,b,c){var d=!1,e=cb;var f=b.contextType;"object"===typeof f&&
+null!==f?f=qa(f):(e=ea(b)?pb:J.current,d=b.contextTypes,f=(d=null!==d&&void 0!==d)?Nb(a,e):cb);b=new b(c,f);a.memoizedState=null!==b.state&&void 0!==b.state?b.state:null;b.updater=Dd;a.stateNode=b;b._reactInternals=a;d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=e,a.__reactInternalMemoizedMaskedChildContext=f);return b}function fi(a,b,c,d){a=b.state;"function"===typeof b.componentWillReceiveProps&&b.componentWillReceiveProps(c,d);"function"===typeof b.UNSAFE_componentWillReceiveProps&&
+b.UNSAFE_componentWillReceiveProps(c,d);b.state!==a&&Dd.enqueueReplaceState(b,b.state,null)}function uf(a,b,c,d){var e=a.stateNode;e.props=c;e.state=a.memoizedState;e.refs={};ff(a);var f=b.contextType;"object"===typeof f&&null!==f?e.context=qa(f):(f=ea(b)?pb:J.current,e.context=Nb(a,f));e.state=a.memoizedState;f=b.getDerivedStateFromProps;"function"===typeof f&&(tf(a,b,f,c),e.state=a.memoizedState);"function"===typeof b.getDerivedStateFromProps||"function"===typeof e.getSnapshotBeforeUpdate||"function"!==
+typeof e.UNSAFE_componentWillMount&&"function"!==typeof e.componentWillMount||(b=e.state,"function"===typeof e.componentWillMount&&e.componentWillMount(),"function"===typeof e.UNSAFE_componentWillMount&&e.UNSAFE_componentWillMount(),b!==e.state&&Dd.enqueueReplaceState(e,e.state,null),wd(a,c,e,d),e.state=a.memoizedState);"function"===typeof e.componentDidMount&&(a.flags|=4194308)}function Ub(a,b){try{var c="",d=b;do c+=fj(d),d=d.return;while(d);var e=c}catch(f){e="\nError generating stack: "+f.message+
+"\n"+f.stack}return{value:a,source:b,stack:e,digest:null}}function vf(a,b,c){return{value:a,source:null,stack:null!=c?c:null,digest:null!=b?b:null}}function wf(a,b){try{console.error(b.value)}catch(c){setTimeout(function(){throw c;})}}function gi(a,b,c){c=Pa(-1,c);c.tag=3;c.payload={element:null};var d=b.value;c.callback=function(){Ed||(Ed=!0,xf=d);wf(a,b)};return c}function hi(a,b,c){c=Pa(-1,c);c.tag=3;var d=a.type.getDerivedStateFromError;if("function"===typeof d){var e=b.value;c.payload=function(){return d(e)};
+c.callback=function(){wf(a,b)}}var f=a.stateNode;null!==f&&"function"===typeof f.componentDidCatch&&(c.callback=function(){wf(a,b);"function"!==typeof d&&(null===ib?ib=new Set([this]):ib.add(this));var c=b.stack;this.componentDidCatch(b.value,{componentStack:null!==c?c:""})});return c}function ii(a,b,c){var d=a.pingCache;if(null===d){d=a.pingCache=new rk;var e=new Set;d.set(b,e)}else e=d.get(b),void 0===e&&(e=new Set,d.set(b,e));e.has(c)||(e.add(c),a=sk.bind(null,a,b,c),b.then(a,a))}function ji(a){do{var b;
+if(b=13===a.tag)b=a.memoizedState,b=null!==b?null!==b.dehydrated?!0:!1:!0;if(b)return a;a=a.return}while(null!==a);return null}function ki(a,b,c,d,e){if(0===(a.mode&1))return a===b?a.flags|=65536:(a.flags|=128,c.flags|=131072,c.flags&=-52805,1===c.tag&&(null===c.alternate?c.tag=17:(b=Pa(-1,1),b.tag=2,fb(c,b,1))),c.lanes|=1),a;a.flags|=65536;a.lanes=e;return a}function aa(a,b,c,d){b.child=null===a?li(b,null,c,d):Vb(b,a.child,c,d)}function mi(a,b,c,d,e){c=c.render;var f=b.ref;Sb(b,e);d=mf(a,b,c,d,f,
+e);c=nf();if(null!==a&&!ha)return b.updateQueue=a.updateQueue,b.flags&=-2053,a.lanes&=~e,Qa(a,b,e);D&&c&&Ue(b);b.flags|=1;aa(a,b,d,e);return b.child}function ni(a,b,c,d,e){if(null===a){var f=c.type;if("function"===typeof f&&!yf(f)&&void 0===f.defaultProps&&null===c.compare&&void 0===c.defaultProps)return b.tag=15,b.type=f,oi(a,b,f,d,e);a=rd(c.type,null,d,b,b.mode,e);a.ref=b.ref;a.return=b;return b.child=a}f=a.child;if(0===(a.lanes&e)){var g=f.memoizedProps;c=c.compare;c=null!==c?c:qc;if(c(g,d)&&a.ref===
+b.ref)return Qa(a,b,e)}b.flags|=1;a=eb(f,d);a.ref=b.ref;a.return=b;return b.child=a}function oi(a,b,c,d,e){if(null!==a){var f=a.memoizedProps;if(qc(f,d)&&a.ref===b.ref)if(ha=!1,b.pendingProps=d=f,0!==(a.lanes&e))0!==(a.flags&131072)&&(ha=!0);else return b.lanes=a.lanes,Qa(a,b,e)}return zf(a,b,c,d,e)}function pi(a,b,c){var d=b.pendingProps,e=d.children,f=null!==a?a.memoizedState:null;if("hidden"===d.mode)if(0===(b.mode&1))b.memoizedState={baseLanes:0,cachePool:null,transitions:null},y(Ga,ba),ba|=c;
+else{if(0===(c&1073741824))return a=null!==f?f.baseLanes|c:c,b.lanes=b.childLanes=1073741824,b.memoizedState={baseLanes:a,cachePool:null,transitions:null},b.updateQueue=null,y(Ga,ba),ba|=a,null;b.memoizedState={baseLanes:0,cachePool:null,transitions:null};d=null!==f?f.baseLanes:c;y(Ga,ba);ba|=d}else null!==f?(d=f.baseLanes|c,b.memoizedState=null):d=c,y(Ga,ba),ba|=d;aa(a,b,e,c);return b.child}function qi(a,b){var c=b.ref;if(null===a&&null!==c||null!==a&&a.ref!==c)b.flags|=512,b.flags|=2097152}function zf(a,
+b,c,d,e){var f=ea(c)?pb:J.current;f=Nb(b,f);Sb(b,e);c=mf(a,b,c,d,f,e);d=nf();if(null!==a&&!ha)return b.updateQueue=a.updateQueue,b.flags&=-2053,a.lanes&=~e,Qa(a,b,e);D&&d&&Ue(b);b.flags|=1;aa(a,b,c,e);return b.child}function ri(a,b,c,d,e){if(ea(c)){var f=!0;ld(b)}else f=!1;Sb(b,e);if(null===b.stateNode)Fd(a,b),ei(b,c,d),uf(b,c,d,e),d=!0;else if(null===a){var g=b.stateNode,h=b.memoizedProps;g.props=h;var k=g.context,n=c.contextType;"object"===typeof n&&null!==n?n=qa(n):(n=ea(c)?pb:J.current,n=Nb(b,
+n));var l=c.getDerivedStateFromProps,m="function"===typeof l||"function"===typeof g.getSnapshotBeforeUpdate;m||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==d||k!==n)&&fi(b,g,d,n);gb=!1;var r=b.memoizedState;g.state=r;wd(b,d,g,e);k=b.memoizedState;h!==d||r!==k||S.current||gb?("function"===typeof l&&(tf(b,c,l,d),k=b.memoizedState),(h=gb||di(b,c,h,d,r,k,n))?(m||"function"!==typeof g.UNSAFE_componentWillMount&&"function"!==typeof g.componentWillMount||
+("function"===typeof g.componentWillMount&&g.componentWillMount(),"function"===typeof g.UNSAFE_componentWillMount&&g.UNSAFE_componentWillMount()),"function"===typeof g.componentDidMount&&(b.flags|=4194308)):("function"===typeof g.componentDidMount&&(b.flags|=4194308),b.memoizedProps=d,b.memoizedState=k),g.props=d,g.state=k,g.context=n,d=h):("function"===typeof g.componentDidMount&&(b.flags|=4194308),d=!1)}else{g=b.stateNode;Fh(a,b);h=b.memoizedProps;n=b.type===b.elementType?h:ya(b.type,h);g.props=
+n;m=b.pendingProps;r=g.context;k=c.contextType;"object"===typeof k&&null!==k?k=qa(k):(k=ea(c)?pb:J.current,k=Nb(b,k));var p=c.getDerivedStateFromProps;(l="function"===typeof p||"function"===typeof g.getSnapshotBeforeUpdate)||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==m||r!==k)&&fi(b,g,d,k);gb=!1;r=b.memoizedState;g.state=r;wd(b,d,g,e);var x=b.memoizedState;h!==m||r!==x||S.current||gb?("function"===typeof p&&(tf(b,c,p,d),x=b.memoizedState),
+(n=gb||di(b,c,n,d,r,x,k)||!1)?(l||"function"!==typeof g.UNSAFE_componentWillUpdate&&"function"!==typeof g.componentWillUpdate||("function"===typeof g.componentWillUpdate&&g.componentWillUpdate(d,x,k),"function"===typeof g.UNSAFE_componentWillUpdate&&g.UNSAFE_componentWillUpdate(d,x,k)),"function"===typeof g.componentDidUpdate&&(b.flags|=4),"function"===typeof g.getSnapshotBeforeUpdate&&(b.flags|=1024)):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=
+4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=1024),b.memoizedProps=d,b.memoizedState=x),g.props=d,g.state=x,g.context=k,d=n):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=1024),d=!1)}return Af(a,b,c,d,f,e)}function Af(a,b,c,d,e,f){qi(a,b);var g=0!==(b.flags&128);if(!d&&!g)return e&&vh(b,c,!1),
+Qa(a,b,f);d=b.stateNode;tk.current=b;var h=g&&"function"!==typeof c.getDerivedStateFromError?null:d.render();b.flags|=1;null!==a&&g?(b.child=Vb(b,a.child,null,f),b.child=Vb(b,null,h,f)):aa(a,b,h,f);b.memoizedState=d.state;e&&vh(b,c,!0);return b.child}function si(a){var b=a.stateNode;b.pendingContext?th(a,b.pendingContext,b.pendingContext!==b.context):b.context&&th(a,b.context,!1);gf(a,b.containerInfo)}function ti(a,b,c,d,e){Qb();Ye(e);b.flags|=256;aa(a,b,c,d);return b.child}function Bf(a){return{baseLanes:a,
+cachePool:null,transitions:null}}function ui(a,b,c){var d=b.pendingProps,e=F.current,f=!1,g=0!==(b.flags&128),h;(h=g)||(h=null!==a&&null===a.memoizedState?!1:0!==(e&2));if(h)f=!0,b.flags&=-129;else if(null===a||null!==a.memoizedState)e|=1;y(F,e&1);if(null===a){Xe(b);a=b.memoizedState;if(null!==a&&(a=a.dehydrated,null!==a))return 0===(b.mode&1)?b.lanes=1:"$!"===a.data?b.lanes=8:b.lanes=1073741824,null;g=d.children;a=d.fallback;return f?(d=b.mode,f=b.child,g={mode:"hidden",children:g},0===(d&1)&&null!==
+f?(f.childLanes=0,f.pendingProps=g):f=Gd(g,d,0,null),a=sb(a,d,c,null),f.return=b,a.return=b,f.sibling=a,b.child=f,b.child.memoizedState=Bf(c),b.memoizedState=Cf,a):Df(b,g)}e=a.memoizedState;if(null!==e&&(h=e.dehydrated,null!==h))return uk(a,b,g,d,h,e,c);if(f){f=d.fallback;g=b.mode;e=a.child;h=e.sibling;var k={mode:"hidden",children:d.children};0===(g&1)&&b.child!==e?(d=b.child,d.childLanes=0,d.pendingProps=k,b.deletions=null):(d=eb(e,k),d.subtreeFlags=e.subtreeFlags&14680064);null!==h?f=eb(h,f):(f=
+sb(f,g,c,null),f.flags|=2);f.return=b;d.return=b;d.sibling=f;b.child=d;d=f;f=b.child;g=a.child.memoizedState;g=null===g?Bf(c):{baseLanes:g.baseLanes|c,cachePool:null,transitions:g.transitions};f.memoizedState=g;f.childLanes=a.childLanes&~c;b.memoizedState=Cf;return d}f=a.child;a=f.sibling;d=eb(f,{mode:"visible",children:d.children});0===(b.mode&1)&&(d.lanes=c);d.return=b;d.sibling=null;null!==a&&(c=b.deletions,null===c?(b.deletions=[a],b.flags|=16):c.push(a));b.child=d;b.memoizedState=null;return d}
+function Df(a,b,c){b=Gd({mode:"visible",children:b},a.mode,0,null);b.return=a;return a.child=b}function Hd(a,b,c,d){null!==d&&Ye(d);Vb(b,a.child,null,c);a=Df(b,b.pendingProps.children);a.flags|=2;b.memoizedState=null;return a}function uk(a,b,c,d,e,f,g){if(c){if(b.flags&256)return b.flags&=-257,d=vf(Error(m(422))),Hd(a,b,g,d);if(null!==b.memoizedState)return b.child=a.child,b.flags|=128,null;f=d.fallback;e=b.mode;d=Gd({mode:"visible",children:d.children},e,0,null);f=sb(f,e,g,null);f.flags|=2;d.return=
+b;f.return=b;d.sibling=f;b.child=d;0!==(b.mode&1)&&Vb(b,a.child,null,g);b.child.memoizedState=Bf(g);b.memoizedState=Cf;return f}if(0===(b.mode&1))return Hd(a,b,g,null);if("$!"===e.data){d=e.nextSibling&&e.nextSibling.dataset;if(d)var h=d.dgst;d=h;f=Error(m(419));d=vf(f,d,void 0);return Hd(a,b,g,d)}h=0!==(g&a.childLanes);if(ha||h){d=O;if(null!==d){switch(g&-g){case 4:e=2;break;case 16:e=8;break;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:e=
+32;break;case 536870912:e=268435456;break;default:e=0}e=0!==(e&(d.suspendedLanes|g))?0:e;0!==e&&e!==f.retryLane&&(f.retryLane=e,Oa(a,e),xa(d,a,e,-1))}Ef();d=vf(Error(m(421)));return Hd(a,b,g,d)}if("$?"===e.data)return b.flags|=128,b.child=a.child,b=vk.bind(null,a),e._reactRetry=b,null;a=f.treeContext;fa=Ka(e.nextSibling);la=b;D=!0;wa=null;null!==a&&(na[oa++]=Ma,na[oa++]=Na,na[oa++]=rb,Ma=a.id,Na=a.overflow,rb=b);b=Df(b,d.children);b.flags|=4096;return b}function vi(a,b,c){a.lanes|=b;var d=a.alternate;
+null!==d&&(d.lanes|=b);df(a.return,b,c)}function Ff(a,b,c,d,e){var f=a.memoizedState;null===f?a.memoizedState={isBackwards:b,rendering:null,renderingStartTime:0,last:d,tail:c,tailMode:e}:(f.isBackwards=b,f.rendering=null,f.renderingStartTime=0,f.last=d,f.tail=c,f.tailMode=e)}function wi(a,b,c){var d=b.pendingProps,e=d.revealOrder,f=d.tail;aa(a,b,d.children,c);d=F.current;if(0!==(d&2))d=d&1|2,b.flags|=128;else{if(null!==a&&0!==(a.flags&128))a:for(a=b.child;null!==a;){if(13===a.tag)null!==a.memoizedState&&
+vi(a,c,b);else if(19===a.tag)vi(a,c,b);else if(null!==a.child){a.child.return=a;a=a.child;continue}if(a===b)break a;for(;null===a.sibling;){if(null===a.return||a.return===b)break a;a=a.return}a.sibling.return=a.return;a=a.sibling}d&=1}y(F,d);if(0===(b.mode&1))b.memoizedState=null;else switch(e){case "forwards":c=b.child;for(e=null;null!==c;)a=c.alternate,null!==a&&null===xd(a)&&(e=c),c=c.sibling;c=e;null===c?(e=b.child,b.child=null):(e=c.sibling,c.sibling=null);Ff(b,!1,e,c,f);break;case "backwards":c=
+null;e=b.child;for(b.child=null;null!==e;){a=e.alternate;if(null!==a&&null===xd(a)){b.child=e;break}a=e.sibling;e.sibling=c;c=e;e=a}Ff(b,!0,c,null,f);break;case "together":Ff(b,!1,null,null,void 0);break;default:b.memoizedState=null}return b.child}function Fd(a,b){0===(b.mode&1)&&null!==a&&(a.alternate=null,b.alternate=null,b.flags|=2)}function Qa(a,b,c){null!==a&&(b.dependencies=a.dependencies);ra|=b.lanes;if(0===(c&b.childLanes))return null;if(null!==a&&b.child!==a.child)throw Error(m(153));if(null!==
+b.child){a=b.child;c=eb(a,a.pendingProps);b.child=c;for(c.return=b;null!==a.sibling;)a=a.sibling,c=c.sibling=eb(a,a.pendingProps),c.return=b;c.sibling=null}return b.child}function wk(a,b,c){switch(b.tag){case 3:si(b);Qb();break;case 5:Ih(b);break;case 1:ea(b.type)&&ld(b);break;case 4:gf(b,b.stateNode.containerInfo);break;case 10:var d=b.type._context,e=b.memoizedProps.value;y(ud,d._currentValue);d._currentValue=e;break;case 13:d=b.memoizedState;if(null!==d){if(null!==d.dehydrated)return y(F,F.current&
+1),b.flags|=128,null;if(0!==(c&b.child.childLanes))return ui(a,b,c);y(F,F.current&1);a=Qa(a,b,c);return null!==a?a.sibling:null}y(F,F.current&1);break;case 19:d=0!==(c&b.childLanes);if(0!==(a.flags&128)){if(d)return wi(a,b,c);b.flags|=128}e=b.memoizedState;null!==e&&(e.rendering=null,e.tail=null,e.lastEffect=null);y(F,F.current);if(d)break;else return null;case 22:case 23:return b.lanes=0,pi(a,b,c)}return Qa(a,b,c)}function Dc(a,b){if(!D)switch(a.tailMode){case "hidden":b=a.tail;for(var c=null;null!==
+b;)null!==b.alternate&&(c=b),b=b.sibling;null===c?a.tail=null:c.sibling=null;break;case "collapsed":c=a.tail;for(var d=null;null!==c;)null!==c.alternate&&(d=c),c=c.sibling;null===d?b||null===a.tail?a.tail=null:a.tail.sibling=null:d.sibling=null}}function W(a){var b=null!==a.alternate&&a.alternate.child===a.child,c=0,d=0;if(b)for(var e=a.child;null!==e;)c|=e.lanes|e.childLanes,d|=e.subtreeFlags&14680064,d|=e.flags&14680064,e.return=a,e=e.sibling;else for(e=a.child;null!==e;)c|=e.lanes|e.childLanes,
+d|=e.subtreeFlags,d|=e.flags,e.return=a,e=e.sibling;a.subtreeFlags|=d;a.childLanes=c;return b}function xk(a,b,c){var d=b.pendingProps;Ve(b);switch(b.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return W(b),null;case 1:return ea(b.type)&&(v(S),v(J)),W(b),null;case 3:d=b.stateNode;Tb();v(S);v(J);jf();d.pendingContext&&(d.context=d.pendingContext,d.pendingContext=null);if(null===a||null===a.child)pd(b)?b.flags|=4:null===a||a.memoizedState.isDehydrated&&0===(b.flags&
+256)||(b.flags|=1024,null!==wa&&(Gf(wa),wa=null));xi(a,b);W(b);return null;case 5:hf(b);var e=ub(xc.current);c=b.type;if(null!==a&&null!=b.stateNode)yk(a,b,c,d,e),a.ref!==b.ref&&(b.flags|=512,b.flags|=2097152);else{if(!d){if(null===b.stateNode)throw Error(m(166));W(b);return null}a=ub(Ea.current);if(pd(b)){d=b.stateNode;c=b.type;var f=b.memoizedProps;d[Da]=b;d[uc]=f;a=0!==(b.mode&1);switch(c){case "dialog":B("cancel",d);B("close",d);break;case "iframe":case "object":case "embed":B("load",d);break;
+case "video":case "audio":for(e=0;e\x3c/script>",a=a.removeChild(a.firstChild)):"string"===typeof d.is?a=g.createElement(c,{is:d.is}):(a=g.createElement(c),"select"===c&&(g=a,d.multiple?g.multiple=!0:d.size&&(g.size=d.size))):a=g.createElementNS(a,c);a[Da]=b;a[uc]=d;zk(a,b,!1,!1);b.stateNode=a;a:{g=qe(c,d);switch(c){case "dialog":B("cancel",a);B("close",a);e=d;break;case "iframe":case "object":case "embed":B("load",a);e=d;break;
+case "video":case "audio":for(e=0;eHf&&(b.flags|=128,d=!0,Dc(f,!1),b.lanes=4194304)}else{if(!d)if(a=xd(g),null!==a){if(b.flags|=128,d=!0,c=a.updateQueue,null!==c&&(b.updateQueue=c,b.flags|=4),Dc(f,!0),null===f.tail&&"hidden"===f.tailMode&&!g.alternate&&!D)return W(b),null}else 2*P()-f.renderingStartTime>Hf&&1073741824!==c&&(b.flags|=
+128,d=!0,Dc(f,!1),b.lanes=4194304);f.isBackwards?(g.sibling=b.child,b.child=g):(c=f.last,null!==c?c.sibling=g:b.child=g,f.last=g)}if(null!==f.tail)return b=f.tail,f.rendering=b,f.tail=b.sibling,f.renderingStartTime=P(),b.sibling=null,c=F.current,y(F,d?c&1|2:c&1),b;W(b);return null;case 22:case 23:return ba=Ga.current,v(Ga),d=null!==b.memoizedState,null!==a&&null!==a.memoizedState!==d&&(b.flags|=8192),d&&0!==(b.mode&1)?0!==(ba&1073741824)&&(W(b),b.subtreeFlags&6&&(b.flags|=8192)):W(b),null;case 24:return null;
+case 25:return null}throw Error(m(156,b.tag));}function Bk(a,b,c){Ve(b);switch(b.tag){case 1:return ea(b.type)&&(v(S),v(J)),a=b.flags,a&65536?(b.flags=a&-65537|128,b):null;case 3:return Tb(),v(S),v(J),jf(),a=b.flags,0!==(a&65536)&&0===(a&128)?(b.flags=a&-65537|128,b):null;case 5:return hf(b),null;case 13:v(F);a=b.memoizedState;if(null!==a&&null!==a.dehydrated){if(null===b.alternate)throw Error(m(340));Qb()}a=b.flags;return a&65536?(b.flags=a&-65537|128,b):null;case 19:return v(F),null;case 4:return Tb(),
+null;case 10:return cf(b.type._context),null;case 22:case 23:return ba=Ga.current,v(Ga),null;case 24:return null;default:return null}}function Wb(a,b){var c=a.ref;if(null!==c)if("function"===typeof c)try{c(null)}catch(d){G(a,b,d)}else c.current=null}function If(a,b,c){try{c()}catch(d){G(a,b,d)}}function Ck(a,b){Jf=Zc;a=ch();if(Ie(a)){if("selectionStart"in a)var c={start:a.selectionStart,end:a.selectionEnd};else a:{c=(c=a.ownerDocument)&&c.defaultView||window;var d=c.getSelection&&c.getSelection();
+if(d&&0!==d.rangeCount){c=d.anchorNode;var e=d.anchorOffset,f=d.focusNode;d=d.focusOffset;try{c.nodeType,f.nodeType}catch(M){c=null;break a}var g=0,h=-1,k=-1,n=0,q=0,u=a,r=null;b:for(;;){for(var p;;){u!==c||0!==e&&3!==u.nodeType||(h=g+e);u!==f||0!==d&&3!==u.nodeType||(k=g+d);3===u.nodeType&&(g+=u.nodeValue.length);if(null===(p=u.firstChild))break;r=u;u=p}for(;;){if(u===a)break b;r===c&&++n===e&&(h=g);r===f&&++q===d&&(k=g);if(null!==(p=u.nextSibling))break;u=r;r=u.parentNode}u=p}c=-1===h||-1===k?null:
+{start:h,end:k}}else c=null}c=c||{start:0,end:0}}else c=null;Kf={focusedElem:a,selectionRange:c};Zc=!1;for(l=b;null!==l;)if(b=l,a=b.child,0!==(b.subtreeFlags&1028)&&null!==a)a.return=b,l=a;else for(;null!==l;){b=l;try{var x=b.alternate;if(0!==(b.flags&1024))switch(b.tag){case 0:case 11:case 15:break;case 1:if(null!==x){var v=x.memoizedProps,z=x.memoizedState,w=b.stateNode,A=w.getSnapshotBeforeUpdate(b.elementType===b.type?v:ya(b.type,v),z);w.__reactInternalSnapshotBeforeUpdate=A}break;case 3:var t=
+b.stateNode.containerInfo;1===t.nodeType?t.textContent="":9===t.nodeType&&t.documentElement&&t.removeChild(t.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(m(163));}}catch(M){G(b,b.return,M)}a=b.sibling;if(null!==a){a.return=b.return;l=a;break}l=b.return}x=zi;zi=!1;return x}function Gc(a,b,c){var d=b.updateQueue;d=null!==d?d.lastEffect:null;if(null!==d){var e=d=d.next;do{if((e.tag&a)===a){var f=e.destroy;e.destroy=void 0;void 0!==f&&If(b,c,f)}e=e.next}while(e!==d)}}
+function Id(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.create;c.destroy=d()}c=c.next}while(c!==b)}}function Lf(a){var b=a.ref;if(null!==b){var c=a.stateNode;switch(a.tag){case 5:a=c;break;default:a=c}"function"===typeof b?b(a):b.current=a}}function Ai(a){var b=a.alternate;null!==b&&(a.alternate=null,Ai(b));a.child=null;a.deletions=null;a.sibling=null;5===a.tag&&(b=a.stateNode,null!==b&&(delete b[Da],delete b[uc],delete b[Me],delete b[Dk],
+delete b[Ek]));a.stateNode=null;a.return=null;a.dependencies=null;a.memoizedProps=null;a.memoizedState=null;a.pendingProps=null;a.stateNode=null;a.updateQueue=null}function Bi(a){return 5===a.tag||3===a.tag||4===a.tag}function Ci(a){a:for(;;){for(;null===a.sibling;){if(null===a.return||Bi(a.return))return null;a=a.return}a.sibling.return=a.return;for(a=a.sibling;5!==a.tag&&6!==a.tag&&18!==a.tag;){if(a.flags&2)continue a;if(null===a.child||4===a.tag)continue a;else a.child.return=a,a=a.child}if(!(a.flags&
+2))return a.stateNode}}function Mf(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?8===c.nodeType?c.parentNode.insertBefore(a,b):c.insertBefore(a,b):(8===c.nodeType?(b=c.parentNode,b.insertBefore(a,c)):(b=c,b.appendChild(a)),c=c._reactRootContainer,null!==c&&void 0!==c||null!==b.onclick||(b.onclick=kd));else if(4!==d&&(a=a.child,null!==a))for(Mf(a,b,c),a=a.sibling;null!==a;)Mf(a,b,c),a=a.sibling}function Nf(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?c.insertBefore(a,b):c.appendChild(a);
+else if(4!==d&&(a=a.child,null!==a))for(Nf(a,b,c),a=a.sibling;null!==a;)Nf(a,b,c),a=a.sibling}function jb(a,b,c){for(c=c.child;null!==c;)Di(a,b,c),c=c.sibling}function Di(a,b,c){if(Ca&&"function"===typeof Ca.onCommitFiberUnmount)try{Ca.onCommitFiberUnmount(Uc,c)}catch(h){}switch(c.tag){case 5:X||Wb(c,b);case 6:var d=T,e=za;T=null;jb(a,b,c);T=d;za=e;null!==T&&(za?(a=T,c=c.stateNode,8===a.nodeType?a.parentNode.removeChild(c):a.removeChild(c)):T.removeChild(c.stateNode));break;case 18:null!==T&&(za?
+(a=T,c=c.stateNode,8===a.nodeType?Re(a.parentNode,c):1===a.nodeType&&Re(a,c),nc(a)):Re(T,c.stateNode));break;case 4:d=T;e=za;T=c.stateNode.containerInfo;za=!0;jb(a,b,c);T=d;za=e;break;case 0:case 11:case 14:case 15:if(!X&&(d=c.updateQueue,null!==d&&(d=d.lastEffect,null!==d))){e=d=d.next;do{var f=e,g=f.destroy;f=f.tag;void 0!==g&&(0!==(f&2)?If(c,b,g):0!==(f&4)&&If(c,b,g));e=e.next}while(e!==d)}jb(a,b,c);break;case 1:if(!X&&(Wb(c,b),d=c.stateNode,"function"===typeof d.componentWillUnmount))try{d.props=
+c.memoizedProps,d.state=c.memoizedState,d.componentWillUnmount()}catch(h){G(c,b,h)}jb(a,b,c);break;case 21:jb(a,b,c);break;case 22:c.mode&1?(X=(d=X)||null!==c.memoizedState,jb(a,b,c),X=d):jb(a,b,c);break;default:jb(a,b,c)}}function Ei(a){var b=a.updateQueue;if(null!==b){a.updateQueue=null;var c=a.stateNode;null===c&&(c=a.stateNode=new Fk);b.forEach(function(b){var d=Gk.bind(null,a,b);c.has(b)||(c.add(b),b.then(d,d))})}}function Aa(a,b,c){c=b.deletions;if(null!==c)for(var d=0;de&&(e=g);d&=~f}d=e;d=P()-d;d=(120>d?120:480>d?480:1080>d?1080:1920>d?1920:3E3>d?3E3:4320>d?4320:1960*Mk(d/1960))-d;if(10a?16:a;if(null===lb)var d=!1;else{a=lb;lb=null;Qd=0;if(0!==(p&6))throw Error(m(331));var e=p;p|=4;for(l=a.current;null!==l;){var f=l,g=f.child;if(0!==(l.flags&16)){var h=f.deletions;if(null!==h){for(var k=0;kP()-Of?wb(a,0):Sf|=c);ia(a,b)}function Ti(a,b){0===b&&(0===(a.mode&1)?b=1:(b=Rd,Rd<<=1,0===(Rd&130023424)&&(Rd=4194304)));var c=Z();a=Oa(a,b);null!==a&&(ic(a,b,c),ia(a,c))}function vk(a){var b=a.memoizedState,c=0;null!==b&&(c=b.retryLane);Ti(a,c)}function Gk(a,b){var c=0;switch(a.tag){case 13:var d=a.stateNode;var e=a.memoizedState;null!==e&&(c=e.retryLane);
+break;case 19:d=a.stateNode;break;default:throw Error(m(314));}null!==d&&d.delete(b);Ti(a,c)}function Mi(a,b){return xh(a,b)}function Tk(a,b,c,d){this.tag=a;this.key=c;this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null;this.index=0;this.ref=null;this.pendingProps=b;this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null;this.mode=d;this.subtreeFlags=this.flags=0;this.deletions=null;this.childLanes=this.lanes=0;this.alternate=null}function yf(a){a=
+a.prototype;return!(!a||!a.isReactComponent)}function Uk(a){if("function"===typeof a)return yf(a)?1:0;if(void 0!==a&&null!==a){a=a.$$typeof;if(a===ie)return 11;if(a===je)return 14}return 2}function eb(a,b){var c=a.alternate;null===c?(c=pa(a.tag,b,a.key,a.mode),c.elementType=a.elementType,c.type=a.type,c.stateNode=a.stateNode,c.alternate=a,a.alternate=c):(c.pendingProps=b,c.type=a.type,c.flags=0,c.subtreeFlags=0,c.deletions=null);c.flags=a.flags&14680064;c.childLanes=a.childLanes;c.lanes=a.lanes;c.child=
+a.child;c.memoizedProps=a.memoizedProps;c.memoizedState=a.memoizedState;c.updateQueue=a.updateQueue;b=a.dependencies;c.dependencies=null===b?null:{lanes:b.lanes,firstContext:b.firstContext};c.sibling=a.sibling;c.index=a.index;c.ref=a.ref;return c}function rd(a,b,c,d,e,f){var g=2;d=a;if("function"===typeof a)yf(a)&&(g=1);else if("string"===typeof a)g=5;else a:switch(a){case Bb:return sb(c.children,e,f,b);case fe:g=8;e|=8;break;case ee:return a=pa(12,c,b,e|2),a.elementType=ee,a.lanes=f,a;case ge:return a=
+pa(13,c,b,e),a.elementType=ge,a.lanes=f,a;case he:return a=pa(19,c,b,e),a.elementType=he,a.lanes=f,a;case Ui:return Gd(c,e,f,b);default:if("object"===typeof a&&null!==a)switch(a.$$typeof){case hg:g=10;break a;case gg:g=9;break a;case ie:g=11;break a;case je:g=14;break a;case Ta:g=16;d=null;break a}throw Error(m(130,null==a?a:typeof a,""));}b=pa(g,c,b,e);b.elementType=a;b.type=d;b.lanes=f;return b}function sb(a,b,c,d){a=pa(7,a,d,b);a.lanes=c;return a}function Gd(a,b,c,d){a=pa(22,a,d,b);a.elementType=
+Ui;a.lanes=c;a.stateNode={isHidden:!1};return a}function Ze(a,b,c){a=pa(6,a,null,b);a.lanes=c;return a}function $e(a,b,c){b=pa(4,null!==a.children?a.children:[],a.key,b);b.lanes=c;b.stateNode={containerInfo:a.containerInfo,pendingChildren:null,implementation:a.implementation};return b}function Vk(a,b,c,d,e){this.tag=b;this.containerInfo=a;this.finishedWork=this.pingCache=this.current=this.pendingChildren=null;this.timeoutHandle=-1;this.callbackNode=this.pendingContext=this.context=null;this.callbackPriority=
+0;this.eventTimes=we(0);this.expirationTimes=we(-1);this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0;this.entanglements=we(0);this.identifierPrefix=d;this.onRecoverableError=e;this.mutableSourceEagerHydrationData=null}function Vf(a,b,c,d,e,f,g,h,k,l){a=new Vk(a,b,c,h,k);1===b?(b=1,!0===f&&(b|=8)):b=0;f=pa(3,null,null,b);a.current=f;f.stateNode=a;f.memoizedState={element:d,isDehydrated:c,cache:null,transitions:null,
+pendingSuspenseBoundaries:null};ff(f);return a}function Wk(a,b,c){var d=3"+b.valueOf().toString()+"";for(b=Xd.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;b.firstChild;)a.appendChild(b.firstChild)}}),Fc=function(a,b){if(b){var c=a.firstChild;if(c&&c===a.lastChild&&3===c.nodeType){c.nodeValue=b;return}}a.textContent=b},dc={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,
+borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,
+strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},$k=["Webkit","ms","Moz","O"];Object.keys(dc).forEach(function(a){$k.forEach(function(b){b=b+a.charAt(0).toUpperCase()+a.substring(1);dc[b]=dc[a]})});var ij=E({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),ze=null,se=null,Eb=null,Fb=null,xg=function(a,b){return a(b)},yg=function(){},te=!1,Oe=!1;if(Ia)try{var Lc={};Object.defineProperty(Lc,
+"passive",{get:function(){Oe=!0}});window.addEventListener("test",Lc,Lc);window.removeEventListener("test",Lc,Lc)}catch(a){Oe=!1}var kj=function(a,b,c,d,e,f,g,h,k){var l=Array.prototype.slice.call(arguments,3);try{b.apply(c,l)}catch(q){this.onError(q)}},gc=!1,Sc=null,Tc=!1,ue=null,lj={onError:function(a){gc=!0;Sc=a}},Ba=zb.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler,Jg=Ba.unstable_scheduleCallback,Kg=Ba.unstable_NormalPriority,xh=Jg,Ki=Ba.unstable_cancelCallback,Pk=Ba.unstable_shouldYield,
+Sk=Ba.unstable_requestPaint,P=Ba.unstable_now,Dj=Ba.unstable_getCurrentPriorityLevel,De=Ba.unstable_ImmediatePriority,Mg=Ba.unstable_UserBlockingPriority,ad=Kg,Ej=Ba.unstable_LowPriority,Ng=Ba.unstable_IdlePriority,Uc=null,Ca=null,ta=Math.clz32?Math.clz32:pj,qj=Math.log,rj=Math.LN2,Wc=64,Rd=4194304,z=0,Ae=!1,Yc=[],Va=null,Wa=null,Xa=null,jc=new Map,kc=new Map,Ya=[],Bj="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset submit".split(" "),
+Gb=Sa.ReactCurrentBatchConfig,Zc=!0,$c=null,Za=null,Ee=null,bd=null,Yb={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(a){return a.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},He=ka(Yb),Mc=E({},Yb,{view:0,detail:0}),ak=ka(Mc),ag,bg,Nc,Yd=E({},Mc,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:Fe,button:0,buttons:0,relatedTarget:function(a){return void 0===a.relatedTarget?a.fromElement===a.srcElement?a.toElement:a.fromElement:
+a.relatedTarget},movementX:function(a){if("movementX"in a)return a.movementX;a!==Nc&&(Nc&&"mousemove"===a.type?(ag=a.screenX-Nc.screenX,bg=a.screenY-Nc.screenY):bg=ag=0,Nc=a);return ag},movementY:function(a){return"movementY"in a?a.movementY:bg}}),ih=ka(Yd),al=E({},Yd,{dataTransfer:0}),Wj=ka(al),bl=E({},Mc,{relatedTarget:0}),Pe=ka(bl),cl=E({},Yb,{animationName:0,elapsedTime:0,pseudoElement:0}),Yj=ka(cl),dl=E({},Yb,{clipboardData:function(a){return"clipboardData"in a?a.clipboardData:window.clipboardData}}),
+ck=ka(dl),el=E({},Yb,{data:0}),qh=ka(el),fk=qh,fl={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},gl={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",
+112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},Gj={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"},hl=E({},Mc,{key:function(a){if(a.key){var b=fl[a.key]||a.key;if("Unidentified"!==b)return b}return"keypress"===a.type?(a=cd(a),13===a?"Enter":String.fromCharCode(a)):"keydown"===a.type||"keyup"===a.type?gl[a.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,
+metaKey:0,repeat:0,locale:0,getModifierState:Fe,charCode:function(a){return"keypress"===a.type?cd(a):0},keyCode:function(a){return"keydown"===a.type||"keyup"===a.type?a.keyCode:0},which:function(a){return"keypress"===a.type?cd(a):"keydown"===a.type||"keyup"===a.type?a.keyCode:0}}),Vj=ka(hl),il=E({},Yd,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0}),nh=ka(il),jl=E({},Mc,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,
+ctrlKey:0,shiftKey:0,getModifierState:Fe}),Xj=ka(jl),kl=E({},Yb,{propertyName:0,elapsedTime:0,pseudoElement:0}),Zj=ka(kl),ll=E({},Yd,{deltaX:function(a){return"deltaX"in a?a.deltaX:"wheelDeltaX"in a?-a.wheelDeltaX:0},deltaY:function(a){return"deltaY"in a?a.deltaY:"wheelDeltaY"in a?-a.wheelDeltaY:"wheelDelta"in a?-a.wheelDelta:0},deltaZ:0,deltaMode:0}),bk=ka(ll),Hj=[9,13,27,32],Ge=Ia&&"CompositionEvent"in window,Oc=null;Ia&&"documentMode"in document&&(Oc=document.documentMode);var ek=Ia&&"TextEvent"in
+window&&!Oc,Ug=Ia&&(!Ge||Oc&&8=Oc),Tg=String.fromCharCode(32),Sg=!1,Hb=!1,Kj={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0},oc=null,pc=null,ph=!1;Ia&&(ph=Lj("input")&&(!document.documentMode||9=document.documentMode,Jb=null,Ke=null,rc=null,Je=!1,Kb={animationend:gd("Animation","AnimationEnd"),
+animationiteration:gd("Animation","AnimationIteration"),animationstart:gd("Animation","AnimationStart"),transitionend:gd("Transition","TransitionEnd")},Le={},eh={};Ia&&(eh=document.createElement("div").style,"AnimationEvent"in window||(delete Kb.animationend.animation,delete Kb.animationiteration.animation,delete Kb.animationstart.animation),"TransitionEvent"in window||delete Kb.transitionend.transition);var jh=hd("animationend"),kh=hd("animationiteration"),lh=hd("animationstart"),mh=hd("transitionend"),
+fh=new Map,Zi="abort auxClick cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");
+(function(){for(var a=0;a>>1,d=a[c];if(0>>1;cD(l,e))fD(g,l)?(a[c]=g,a[f]=e,c=f):(a[c]=l,a[h]=e,c=h);else if(fD(g,e))a[c]=g,a[f]=e,c=f;else break a}}return b}
+function D(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}function P(a){for(var b=p(r);null!==b;){if(null===b.callback)E(r);else if(b.startTime<=a)E(r),b.sortIndex=b.expirationTime,O(q,b);else break;b=p(r)}}function Q(a){z=!1;P(a);if(!u)if(null!==p(q))u=!0,R(S);else{var b=p(r);null!==b&&T(Q,b.startTime-a)}}function S(a,b){u=!1;z&&(z=!1,ea(A),A=-1);F=!0;var c=k;try{P(b);for(n=p(q);null!==n&&(!(n.expirationTime>b)||a&&!fa());){var m=n.callback;if("function"===typeof m){n.callback=null;
+k=n.priorityLevel;var d=m(n.expirationTime<=b);b=v();"function"===typeof d?n.callback=d:n===p(q)&&E(q);P(b)}else E(q);n=p(q)}if(null!==n)var g=!0;else{var h=p(r);null!==h&&T(Q,h.startTime-b);g=!1}return g}finally{n=null,k=c,F=!1}}function fa(){return v()-hae?(a.sortIndex=c,O(r,a),null===p(q)&&a===p(r)&&(z?(ea(A),A=-1):z=!0,T(Q,c-e))):(a.sortIndex=d,O(q,a),u||F||(u=!0,R(S)));return a},unstable_cancelCallback:function(a){a.callback=null},unstable_wrapCallback:function(a){var b=
+k;return function(){var c=k;k=b;try{return a.apply(this,arguments)}finally{k=c}}},unstable_getCurrentPriorityLevel:function(){return k},unstable_shouldYield:fa,unstable_requestPaint:function(){},unstable_continueExecution:function(){u||F||(u=!0,R(S))},unstable_pauseExecution:function(){},unstable_getFirstCallbackNode:function(){return p(q)},get unstable_now(){return v},unstable_forceFrameRate:function(a){0>a||125>>1,d=a[c];if(0>>1;cD(l,e))fD(g,l)?(a[c]=g,a[f]=e,c=f):(a[c]=l,a[h]=e,c=h);else if(fD(g,e))a[c]=g,a[f]=e,c=f;else break a}}return b}
+function D(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}function P(a){for(var b=p(r);null!==b;){if(null===b.callback)E(r);else if(b.startTime<=a)E(r),b.sortIndex=b.expirationTime,O(q,b);else break;b=p(r)}}function Q(a){z=!1;P(a);if(!u)if(null!==p(q))u=!0,R(S);else{var b=p(r);null!==b&&T(Q,b.startTime-a)}}function S(a,b){u=!1;z&&(z=!1,ea(A),A=-1);F=!0;var c=k;try{P(b);for(n=p(q);null!==n&&(!(n.expirationTime>b)||a&&!fa());){var m=n.callback;if("function"===typeof m){n.callback=null;
+k=n.priorityLevel;var d=m(n.expirationTime<=b);b=v();"function"===typeof d?n.callback=d:n===p(q)&&E(q);P(b)}else E(q);n=p(q)}if(null!==n)var g=!0;else{var h=p(r);null!==h&&T(Q,h.startTime-b);g=!1}return g}finally{n=null,k=c,F=!1}}function fa(){return v()-hae?(a.sortIndex=c,O(r,a),null===p(q)&&a===p(r)&&(z?(ea(A),A=-1):z=!0,T(Q,c-e))):(a.sortIndex=d,O(q,a),u||F||(u=!0,R(S)));return a},unstable_cancelCallback:function(a){a.callback=null},unstable_wrapCallback:function(a){var b=
+k;return function(){var c=k;k=b;try{return a.apply(this,arguments)}finally{k=c}}},unstable_getCurrentPriorityLevel:function(){return k},unstable_shouldYield:fa,unstable_requestPaint:function(){},unstable_continueExecution:function(){u||F||(u=!0,R(S))},unstable_pauseExecution:function(){},unstable_getFirstCallbackNode:function(){return p(q)},get unstable_now(){return v},unstable_forceFrameRate:function(a){0>a||125
Toggle Dashboard
+ Pipeline Builder
diff --git a/cellmap_flow/dashboard/templates/pipeline_builder_v2.html b/cellmap_flow/dashboard/templates/pipeline_builder_v2.html
new file mode 100644
index 0000000..00d7918
--- /dev/null
+++ b/cellmap_flow/dashboard/templates/pipeline_builder_v2.html
@@ -0,0 +1,2088 @@
+
+
+
+
+
+ CellMapFlow - Pipeline Builder
+
+
+
+
+
+
+
+
+
+
Blockwise:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
🎨
+
Drag items from the sidebar to start building your pipeline
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cellmap_flow/globals.py b/cellmap_flow/globals.py
index b8186af..4ede2e4 100644
--- a/cellmap_flow/globals.py
+++ b/cellmap_flow/globals.py
@@ -1,25 +1,51 @@
-from cellmap_flow.norm.input_normalize import MinMaxNormalizer
-from cellmap_flow.post.postprocessors import DefaultPostprocessor
+from cellmap_flow.norm.input_normalize import MinMaxNormalizer, LambdaNormalizer
+from cellmap_flow.post.postprocessors import DefaultPostprocessor, ThresholdPostprocessor
import os
import yaml
import threading
import numpy as np
import logging
+from typing import Any, List, Optional
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
-# input_norms = [MinMaxNormalizer()]
-# postprocess = [DefaultPostprocessor(0,200,0,1)]
+input_norms = [MinMaxNormalizer(), LambdaNormalizer("x*2-1")]
+postprocess = [DefaultPostprocessor(), ThresholdPostprocessor(threshold=0.5)]
-input_norms = []
-postprocess = []
+# input_norms = []
+# postprocess = []
viewer = None
class Flow:
- _instance = None
+ _instance: Optional["Flow"] = None
+
+ # Class-level type annotations for all instance attributes
+ jobs: List[Any]
+ models_config: List[Any]
+ servers: List[Any]
+ raw: Optional[Any]
+ input_norms: List[Any]
+ postprocess: List[Any]
+ viewer: Optional[Any]
+ dataset_path: Optional[str]
+ model_catalog: dict
+ queue: str
+ charge_group: str
+ nb_cores_master: int
+ nb_cores_worker: int
+ nb_workers: int
+ tmp_dir: Optional[str]
+ blockwise_tasks_dir: Optional[str]
+ neuroglancer_thread: Optional[Any]
+ pipeline_inputs: List[Any]
+ pipeline_outputs: List[Any]
+ pipeline_edges: List[Any]
+ pipeline_normalizers: List[Any]
+ pipeline_models: List[Any]
+ pipeline_postprocessors: List[Any]
def __new__(cls):
if cls._instance is None:
@@ -28,11 +54,11 @@ def __new__(cls):
cls._instance.models_config = []
cls._instance.servers = []
cls._instance.raw = None
- cls._instance.input_norms = [] # or [MinMaxNormalizer(0, 255)]
- cls._instance.postprocess = []
+ cls._instance.input_norms = input_norms
+ cls._instance.postprocess = postprocess
cls._instance.viewer = None
cls._instance.dataset_path = None
- # cls._instance.model_catalog = {}
+ cls._instance.model_catalog = {}
# Uncomment and adjust if you want to load the model catalog:
models_path = os.path.normpath(
os.path.join(
@@ -44,7 +70,20 @@ def __new__(cls):
cls._instance.queue = "gpu_h100"
cls._instance.charge_group = "cellmap"
+ cls._instance.nb_cores_master = 4
+ cls._instance.nb_cores_worker = 12
+ cls._instance.nb_workers = 14
+ cls._instance.tmp_dir = os.path.expanduser("~/.cellmap_flow/blockwise_tmp")
+ cls._instance.blockwise_tasks_dir = os.path.expanduser("~/.cellmap_flow/blockwise_tasks")
cls._instance.neuroglancer_thread = None
+
+ # Pipeline visual state storage
+ cls._instance.pipeline_inputs = []
+ cls._instance.pipeline_outputs = []
+ cls._instance.pipeline_edges = []
+ cls._instance.pipeline_normalizers = []
+ cls._instance.pipeline_models = []
+ cls._instance.pipeline_postprocessors = []
return cls._instance
def to_dict(self):
diff --git a/cellmap_flow/models/model_merger.py b/cellmap_flow/models/model_merger.py
new file mode 100644
index 0000000..8bb020c
--- /dev/null
+++ b/cellmap_flow/models/model_merger.py
@@ -0,0 +1,160 @@
+"""
+Model Merger classes for combining outputs from multiple models.
+
+Similar to PostProcessor pattern, provides subclasses for different merge strategies
+that can be looked up by their __name__ attribute.
+"""
+
+import logging
+import numpy as np
+import inspect
+
+logger = logging.getLogger(__name__)
+
+
+class ModelMerger:
+ """Base class for model merging strategies."""
+
+ def merge(self, model_outputs):
+ """
+ Merge outputs from multiple models.
+
+ Args:
+ model_outputs: List of numpy arrays from different models
+
+ Returns:
+ Merged numpy array
+ """
+ raise NotImplementedError(
+ f"{self.__class__.__name__} must implement merge() method"
+ )
+
+
+class AndModelMerger(ModelMerger):
+ """
+ AND merge strategy: Element-wise minimum.
+ For binary data: logical AND. For continuous data: minimum value.
+ """
+
+ def merge(self, model_outputs):
+ """
+ Apply AND operation: element-wise minimum across all model outputs.
+
+ Args:
+ model_outputs: List of numpy arrays
+
+ Returns:
+ Merged array with minimum values at each position
+ """
+ merged = model_outputs[0].copy()
+ for output in model_outputs[1:]:
+ merged = np.minimum(merged, output)
+ return merged
+
+
+class OrModelMerger(ModelMerger):
+ """
+ OR merge strategy: Element-wise maximum.
+ For binary data: logical OR. For continuous data: maximum value.
+ """
+
+ def merge(self, model_outputs):
+ """
+ Apply OR operation: element-wise maximum across all model outputs.
+
+ Args:
+ model_outputs: List of numpy arrays
+
+ Returns:
+ Merged array with maximum values at each position
+ """
+ merged = model_outputs[0].copy()
+ for output in model_outputs[1:]:
+ merged = np.maximum(merged, output)
+ return merged
+
+
+class SumModelMerger(ModelMerger):
+ """
+ SUM merge strategy: Average of all outputs.
+ Computes the mean across all model outputs.
+ """
+
+ def merge(self, model_outputs):
+ """
+ Apply SUM operation: average all model outputs.
+
+ Args:
+ model_outputs: List of numpy arrays
+
+ Returns:
+ Merged array with average values at each position
+ """
+ merged = np.sum(model_outputs, axis=0) / len(model_outputs)
+ return merged
+
+
+def get_model_mergers_list() -> list[dict]:
+ """
+ Returns a list of dictionaries containing information about all ModelMerger subclasses.
+
+ Returns:
+ List of dicts with keys: 'class_name', 'name', 'description'
+ """
+ merger_classes = ModelMerger.__subclasses__()
+ mergers = []
+ for merger_cls in merger_classes:
+ merger_name = merger_cls.__name__
+ # Extract description from docstring
+ description = (
+ merger_cls.__doc__.strip().split("\n")[0]
+ if merger_cls.__doc__
+ else "No description available"
+ )
+ mergers.append(
+ {
+ "class_name": merger_name,
+ "name": merger_name.replace("ModelMerger", ""), # e.g., "And"
+ "description": description,
+ }
+ )
+ return mergers
+
+
+def get_model_merger(merger_name: str) -> ModelMerger:
+ """
+ Get a ModelMerger instance by name.
+
+ Args:
+ merger_name: Name of the merger class (e.g., 'AndModelMerger', 'And', 'AND')
+
+ Returns:
+ Instance of the requested ModelMerger subclass
+
+ Raises:
+ ValueError: If merger_name doesn't match any available merger class
+ """
+ merger_classes = ModelMerger.__subclasses__()
+
+ # Normalize the input name for comparison
+ normalized_name = merger_name.upper().strip()
+
+ for merger_cls in merger_classes:
+ class_name = merger_cls.__name__
+ short_name = class_name.replace("ModelMerger", "").upper()
+
+ if normalized_name in [class_name.upper(), short_name]:
+ return merger_cls()
+
+ available = [cls.__name__ for cls in merger_classes]
+ raise ValueError(
+ f"Unknown merger: {merger_name}. Available mergers: {available}"
+ )
+
+
+# Convenience mapping for common merge mode strings
+MERGE_MODE_MAP = {
+ "AND": "AndModelMerger",
+ "OR": "OrModelMerger",
+ "SUM": "SumModelMerger",
+}
diff --git a/cellmap_flow/models/models_config.py b/cellmap_flow/models/models_config.py
index 7bf1713..ccd2c59 100644
--- a/cellmap_flow/models/models_config.py
+++ b/cellmap_flow/models/models_config.py
@@ -73,6 +73,15 @@ def output_dtype(self):
)
return np.float32
+ def to_dict(self):
+ """
+ Export model configuration as a dict that can be used with build_model_from_entry.
+
+ Returns:
+ Dictionary containing model type and all init parameters.
+ """
+ raise NotImplementedError("Subclasses must implement to_dict()")
+
class ScriptModelConfig(ModelConfig):
@@ -104,6 +113,15 @@ def _get_config(self):
)
return config
+ def to_dict(self):
+ """Export configuration for use with build_model_from_entry."""
+ result = {"type": "script", "script_path": self.script_path}
+ if self.name is not None:
+ result["name"] = self.name
+ if self.scale is not None:
+ result["scale"] = self.scale
+ return result
+
class DaCapoModelConfig(ModelConfig):
@@ -165,6 +183,17 @@ def _get_channels(task):
return ["x", "y", "z"]
return ["membrane"]
+ def to_dict(self):
+ """Export configuration for use with build_model_from_entry."""
+ result = {
+ "type": "dacapo",
+ "run_name": self.run_name,
+ "iteration": self.iteration,
+ }
+ if self.name is not None:
+ result["name"] = self.name
+ return result
+
class FlyModelConfig(ModelConfig):
@@ -240,6 +269,23 @@ def _get_config(self):
config.axes_names = ["x", "y", "z", "c^"]
return config
+ def to_dict(self):
+ """Export configuration for use with build_model_from_entry."""
+ result = {
+ "type": "fly",
+ "checkpoint_path": self.checkpoint_path,
+ "channels": self.channels,
+ "input_voxel_size": list(self.input_voxel_size),
+ "output_voxel_size": list(self.output_voxel_size),
+ }
+ if self.name is not None:
+ result["name"] = self.name
+ if self.input_size is not None:
+ result["input_size"] = list(self.input_size)
+ if self.output_size is not None:
+ result["output_size"] = list(self.output_size)
+ return result
+
class BioModelConfig(ModelConfig):
def __init__(
@@ -393,6 +439,21 @@ def get_input_slicer(self, input_axes):
for a in input_axes
)
+ def to_dict(self):
+ """Export configuration for use with build_model_from_entry."""
+ result = {
+ "type": "bioimage",
+ "model_name": self.model_name,
+ "voxel_size": list(self.voxel_size) if hasattr(self.voxel_size, '__iter__') else self.voxel_size,
+ }
+ if self.name is not None:
+ result["name"] = self.name
+ if self.voxels_to_process is not None:
+ # Reconstruct edge_length_to_process from voxels_to_process
+ edge_length = round(self.voxels_to_process ** (1/3))
+ result["edge_length_to_process"] = edge_length
+ return result
+
def concat_along_c(arrs, axes_list, channel_axis_name="c"):
"""Concatenate arrays along the channel axis, adding channel dim if missing."""
@@ -518,3 +579,15 @@ def _get_config(self) -> Config:
config.model.to(_get_device())
config.model.eval()
return config
+
+ def to_dict(self):
+ """Export configuration for use with build_model_from_entry."""
+ result = {
+ "type": "cellmap-model",
+ "folder_path": self.cellmap_model.folder_path,
+ }
+ if self.name is not None:
+ result["name"] = self.name
+ if self.scale is not None:
+ result["scale"] = self.scale
+ return result
diff --git a/cellmap_flow/utils/bsub_utils.py b/cellmap_flow/utils/bsub_utils.py
index 3d19f15..796e3ee 100644
--- a/cellmap_flow/utils/bsub_utils.py
+++ b/cellmap_flow/utils/bsub_utils.py
@@ -520,7 +520,7 @@ def start_hosts(
command,
queue,
charge_group,
- job_name=f"{job_name}_server"
+ job_name=f"{job_name}"
)
if wait_for_host:
diff --git a/cellmap_flow/utils/config_utils.py b/cellmap_flow/utils/config_utils.py
index 2e71442..b83ee4e 100644
--- a/cellmap_flow/utils/config_utils.py
+++ b/cellmap_flow/utils/config_utils.py
@@ -69,7 +69,7 @@ def load_config(path: str) -> Dict[str, Any]:
if not config["models"]:
logger.error("YAML 'models' list is empty")
sys.exit(1)
- logger.warning("Using deprecated list format for models. Consider using dict format with model names as keys.")
+ # logger.warning("Using deprecated list format for models. Consider using dict format with model names as keys.")
else:
logger.error("YAML 'models' must be either a dict or list")
sys.exit(1)
@@ -220,6 +220,15 @@ def build_models(model_entries: Dict[str, Dict[str, Any]]) -> List[ModelConfig]:
"""
models = []
+ if isinstance(model_entries, list):
+ entries = {}
+ for entry in model_entries:
+ if "name" not in entry:
+ raise ValueError("Each model entry in the list must have a 'name' field.")
+ entries[entry["name"]] = entry
+ model_entries = entries
+
+
for model_name, entry in model_entries.items():
model = build_model_from_entry(entry, model_name=model_name)
models.append(model)
diff --git a/cellmap_flow/utils/ds.py b/cellmap_flow/utils/ds.py
index 19389b0..bf8dc30 100644
--- a/cellmap_flow/utils/ds.py
+++ b/cellmap_flow/utils/ds.py
@@ -45,8 +45,8 @@ def generate_singlescale_metadata(
z_attrs["multiscales"][0]["datasets"] = [
{
"coordinateTransformations": [
- {"scale": voxel_size, "type": "scale"},
- {"translation": translation, "type": "translation"},
+ {"scale": list(voxel_size), "type": "scale"},
+ {"translation": list(translation), "type": "translation"},
],
"path": arr_name,
}
diff --git a/cellmap_flow/utils/generate_neuroglancer.py b/cellmap_flow/utils/generate_neuroglancer.py
new file mode 100644
index 0000000..fbd621a
--- /dev/null
+++ b/cellmap_flow/utils/generate_neuroglancer.py
@@ -0,0 +1,39 @@
+import os
+import zarr
+import yaml
+import neuroglancer
+from cellmap_flow.utils.scale_pyramid import get_raw_layer
+
+neuroglancer.set_server_bind_address("0.0.0.0")
+
+def generate_neuroglancer(yaml_path):
+ with open(yaml_path, "r") as f:
+ config = yaml.safe_load(f)
+ predictions = [f for f in os.listdir(config["output_path"]) if os.path.isdir(os.path.join(config["output_path"], f))]
+ output_path = os.path.join(config["output_path"])
+ raw_path = config["data_path"]
+
+ viewer = neuroglancer.Viewer()
+ with viewer.txn() as s:
+ s.layers["raw"] = get_raw_layer(raw_path)
+ for pred in predictions:
+ s.layers[pred] = get_raw_layer(os.path.join(output_path, pred))
+
+ viewer_url = str(viewer)
+ print("viewer", viewer_url)
+ input("Press Enter to continue...")
+ return viewer_url
+
+
+def fix_all(yaml_path):
+ with open(yaml_path, "r") as f:
+ config = yaml.safe_load(f)
+ predictions = [os.path.join(config["output_path"], f) for f in os.listdir(config["output_path"]) if os.path.isdir(os.path.join(config["output_path"], f))]
+ for pred_path in predictions:
+ c_path = pred_path
+ while ".zarr" in c_path:
+ print("Fixing ", c_path)
+ zarr.open(c_path, mode="a")
+ c_path = os.path.dirname(c_path)
+
+
\ No newline at end of file
diff --git a/check_app.sh b/check_app.sh
new file mode 100644
index 0000000..8bbdfff
--- /dev/null
+++ b/check_app.sh
@@ -0,0 +1,2 @@
+python -m cellmap_flow.dashboard.app
+# conda activate flow_v2 && python -m cellmap_flow.dashboard.app 2>&1 | head -30 &
\ No newline at end of file
diff --git a/example/example_pipeline.yaml b/example/example_pipeline.yaml
new file mode 100644
index 0000000..8dc8434
--- /dev/null
+++ b/example/example_pipeline.yaml
@@ -0,0 +1,26 @@
+# Example CellMapFlow Pipeline
+# This demonstrates a typical normalization -> inference -> postprocessing workflow
+
+input_normalizers:
+ - name: StandardNormalizer
+ params:
+ mean: 0.5
+ std: 0.1
+
+ - name: MinMaxNormalizer
+ params:
+ min_val: 0.0
+ max_val: 255.0
+
+postprocessors:
+ - name: DefaultPostprocessor
+ params:
+ clip_min: -1.0
+ clip_max: 1.0
+ bias: 1.0
+ multiplier: 127.5
+
+ - name: InstanceSegmentation
+ params:
+ threshold: 0.5
+ min_size: 100
diff --git a/example/model_spec.py b/example/model_spec.py
index 0a1633a..1078e99 100644
--- a/example/model_spec.py
+++ b/example/model_spec.py
@@ -25,6 +25,7 @@ def load_eval_model(num_labels, checkpoint_path):
model.eval()
return model
+# classes = ["mito","er","nuc"," pm"," ves","ld"]
CHECKPOINT_PATH = "/nrs/saalfeld/heinrichl/fly_organelles/run08/model_checkpoint_438000"
output_channels = 8 # 0:all_mem,1:organelle,2:mito,3:er,4:nucleus,5:pm,6:vs,7:ld
model = load_eval_model(output_channels, CHECKPOINT_PATH)
diff --git a/example/model_spec_run07.py b/example/model_spec_run07.py
index 756d809..2728289 100644
--- a/example/model_spec_run07.py
+++ b/example/model_spec_run07.py
@@ -25,8 +25,7 @@ def load_eval_model(num_labels, checkpoint_path):
model.eval()
return model
-
-classes = ["mito","er","nuc"," pm"," ves","ld"]
+classes = ["all_mem","organelle","mito","er","nuc","pm","vesicle","ld"]
CHECKPOINT_PATH = "/nrs/saalfeld/heinrichl/fly_organelles/run07/model_checkpoint_432000"
# output_channels = len(classes)
output_channels = 8
diff --git a/example/model_spec_run07_7hk.py b/example/model_spec_run07_7hk.py
index 6e29478..8404b77 100644
--- a/example/model_spec_run07_7hk.py
+++ b/example/model_spec_run07_7hk.py
@@ -26,7 +26,7 @@ def load_eval_model(num_labels, checkpoint_path):
return model
-classes = ["mito","er","nuc"," pm"," ves","ld"]
+classes = ["all_mem","organelle","mito","er","nuc","pm","vesicle","ld"]
CHECKPOINT_PATH = "/nrs/saalfeld/heinrichl/fly_organelles/run07/model_checkpoint_700000"
# output_channels = len(classes)
output_channels = 8
diff --git a/jrc_mus-salivary-1_mito.yaml b/jrc_mus-salivary-1_mito.yaml
new file mode 100644
index 0000000..5c7c1c8
--- /dev/null
+++ b/jrc_mus-salivary-1_mito.yaml
@@ -0,0 +1,32 @@
+data_path: /nrs/cellmap/data/jrc_mus-salivary-1/jrc_mus-salivary-1.zarr/recon-1/em/fibsem-uint8
+queue: gpu_h100
+charge_group: cellmap
+json_data:
+ input_norm:
+ MinMaxNormalizer:
+ min_value: 0
+ max_value: 250
+ invert: false
+ LambdaNormalizer:
+ expression: x*2-1
+ postprocess:
+ DefaultPostprocessor:
+ clip_min: 0
+ clip_max: 1.0
+ bias: 0.0
+ multiplier: 127.5
+ ThresholdPostprocessor:
+ threshold: 127.5
+models:
+ model_tmp1:
+ type: fly
+ checkpoint: /groups/cellmap/cellmap/zouinkhim/exp_c-elegen/v3/train/runs/20250806_mito_mouse_distance_16nm/model_checkpoint_362000
+ resolution: 16
+ classes:
+ - mito
+ # mito_mouse_16_362000:
+ # type: fly
+ # checkpoint: /groups/cellmap/cellmap/zouinkhim/exp_c-elegen/v3/train/runs/20250806_mito_mouse_distance_16nm/model_checkpoint_362000
+ # resolution: 16
+ # classes:
+ # - mito
\ No newline at end of file
diff --git a/neuroglancer_utils.py b/neuroglancer_utils.py
index 9d27822..ad9e052 100644
--- a/neuroglancer_utils.py
+++ b/neuroglancer_utils.py
@@ -70,7 +70,7 @@ def generate_neuroglancer_url(dataset_path, extras=[]):
host = job.host
color = next(color_cycle)
s.layers[model] = neuroglancer.ImageLayer(
- source=f"n5://{host}/{model}{ARGS_KEY}{st_data}{ARGS_KEY}",
+ source=f"zarr://{host}/{model}{ARGS_KEY}{st_data}{ARGS_KEY}",
shader="""#uicontrol int rChan slider(min=0, max=7, step=1, default=0);
#uicontrol int gChan slider(min=0, max=7, step=1, default=1);
#uicontrol int bChan slider(min=0, max=7, step=1, default=2);
diff --git a/pyproject.toml b/pyproject.toml
index 3a8539d..b60b817 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -27,6 +27,7 @@ dependencies = [
"numcodecs==0.15.0",
"zarr==2.18.4",
"xarray==2024.7.0",
+ # "xarray==2023.12.0",
"h5py",
"s3fs",
"torch",
@@ -36,6 +37,7 @@ dependencies = [
"neuroglancer",
"pydantic",
"funlib.persistence @ git+https://github.com/funkelab/funlib.persistence.git",
+ "daisy"
]
# extras
@@ -88,4 +90,5 @@ pytest = "^8.4.1"
cellmap_flow = "cellmap_flow.cli.cli:main"
cellmap_flow_server = "cellmap_flow.cli.server_cli:cli"
cellmap_flow_yaml = "cellmap_flow.cli.yaml_cli:main"
-cellmap_flow_blockwise = "cellmap_flow.blockwise.cli:cli"
\ No newline at end of file
+cellmap_flow_blockwise = "cellmap_flow.blockwise.cli:cli"
+cellmap_flow_app = "cellmap_flow.dashboard.app"
\ No newline at end of file