Python client for ScopeOne's language-neutral Local API. It controls a running ScopeOne app and can be used by scripts, notebooks, or Python-based agent tool adapters.
- Python package project root:
ScopeOneCore/python/scopeone - Package source:
ScopeOneCore/python/scopeone/src/scopeone - Public facade:
scopeone.core - External backend client:
scopeone.client - Session wrapper:
scopeone.session - Shared-memory frame helpers:
scopeone.shm
import time
import numpy as np
from scopeone import ScopeOne
scopeone = ScopeOne()
print(scopeone.version())
scopeone.load_config(r"C:\path\to\MMConfig.cfg")
print(scopeone.status())
print(scopeone.capabilities())
print(scopeone.state_snapshot())
print(scopeone.camera_ids())
print(scopeone.loaded_devices())
scopeone.start_preview("Camera")
live = scopeone.latest_raw_frame("Camera")
preview_image = np.flipud(live.image)
layer_key = scopeone.show_frame(live, image=preview_image, layer_id="python_live", name="Python Live")
scopeone.set_layer_layout("overlay")
scopeone.set_layer_display(layer_key, colormap="Fire", opacity_percent=80)
scopeone.auto_layer_levels(layer_key)
print(scopeone.get_layer_histogram(layer_key)["mean"])
print(scopeone.get_line_profile(layer_key, 0, 0, 100, 100))
synthetic = np.zeros_like(live.image)
scopeone.show_image(synthetic, layer_id="python_synthetic", name="Python Synthetic")
markup_id = scopeone.create_rect_markup(layer_key, 10, 10, 100, 80, "Region")
scopeone.remove_markup(markup_id)
property_names = scopeone.device_property_names("Camera")
print(property_names[:10])
for prop in scopeone.device_properties("Camera")[:5]:
print(prop["name"], prop["value"], prop["type"])
if "Exposure" in property_names:
print(scopeone.get_property("Camera", "Exposure", from_cache=False))
# scopeone.set_property("Camera", "Exposure", "10")
print(scopeone.read_exposure("Camera"))
# scopeone.set_exposure(10.0, "Camera")
# scopeone.set_roi("Camera", 0, 0, 256, 256)
# scopeone.clear_roi("Camera")
scopeone.set_processing_bit_depth(16)
module_index = scopeone.add_processing_module("gaussian_blur", {"kernel_size": 5, "sigma": 1.2})
print(scopeone.processing_modules())
scopeone.set_processing_module_parameters(module_index, {"kernel_size": 7})
document = scopeone.experiment_document()
document["plan"]["framesPerBurst"] = 10
document = scopeone.validate_experiment(document)
scopeone.save_experiment(r"C:\data\experiment.scopeone.json", document)
experiment = scopeone.start_experiment(document)
while experiment.status()["state"] == "Running":
time.sleep(0.05)
print(experiment.experiment_id(), experiment.status())
experiment.close()
session = scopeone.record(frames=10, camera="Camera")
stage = session.process_frame("Camera", 0, end_module_index=0)
edited_stage = np.clip(stage.image * 1.2, 0, (1 << stage.bits_per_sample) - 1)
result = scopeone.continue_pipeline(stage, image=edited_stage)
scopeone.show_frame(result, layer_id="python_result", name="Python Result")
paths = scopeone.save_frame(result, r"C:\data", base_name="python_result", format="tiff")
session_paths = session.save(r"C:\data", base_name="raw_session", format="tiff")
session.close()
scopeone.stop_preview("Camera")
scopeone.unload_config()
scopeone.close()The standalone C++ ScopeOneMcpServer included with the desktop app is the default integration for MCP-compatible agents. It does not use this Python package. This package remains available for scripts, notebooks, and custom Python-based adapters that intentionally use the Local API directly.
capabilities() returns operationGroups, longRunningOperations, hardwareMutationOperations, filesystemMutationOperations, and destructiveOperations. state_snapshot() returns application and configuration identity, hardware inventory, preview state, processing modules, image layers, markups, live acquisition and writer progress, retained experiments, and recording sessions. It intentionally does not poll every hardware property; use get_property(..., from_cache=False), position reads, exposure reads, or ROI reads when an action requires a fresh hardware value.
The Python client assigns a numeric requestId to every control request. The desktop app echoes it, and the client rejects a mismatched response. Raw protocol clients may provide their own string or numeric requestId.
A control connection is synchronous and processes one request at a time. Agent adapters should serialize calls made through one ScopeOne instance, especially frame operations because all clients share the same exported frame mapping.
ScopeOne.load_config(config_path)ScopeOne.unload_config()ScopeOne.close()ScopeOne.version()ScopeOne.status()ScopeOne.capabilities()ScopeOne.state_snapshot()ScopeOne.camera_ids()ScopeOne.loaded_devices()ScopeOne.start_preview(camera="All")ScopeOne.stop_preview(camera="All")ScopeOne.list_layers()ScopeOne.get_layer_histogram(layer_key)ScopeOne.get_pixel_value(layer_key, x, y)ScopeOne.get_line_profile(layer_key, x1, y1, x2, y2)ScopeOne.detect_particles(layer_key, threshold, min_area, max_area, max_particles=1000, export_mask=False, publish_mask=False)ScopeOne.layer_options()ScopeOne.set_layer_layout(layout)ScopeOne.set_visible_layers(layer_keys)ScopeOne.set_layer_display(layer_key, visible=None, opacity_percent=None, gamma=None, colormap=None, blending=None, levels=None)ScopeOne.auto_layer_levels(layer_key)ScopeOne.full_layer_levels(layer_key)ScopeOne.set_layer_auto_stretch(layer_key, enabled)ScopeOne.get_source_display_transform(source_id)ScopeOne.set_source_display_transform(source_id, offset_x=None, offset_y=None, zoom_percent=None, flip_x=None, flip_y=None)ScopeOne.reset_source_display_transform(source_id)ScopeOne.move_layer(layer_key, offset)ScopeOne.config_groups()ScopeOne.configs(group)ScopeOne.current_config(group)ScopeOne.set_config(group, config)ScopeOne.remove_static_layer(layer_key)ScopeOne.clear_static_layers()ScopeOne.create_line_markup(layer_key, x1, y1, x2, y2, label="", role="generic")ScopeOne.create_rect_markup(layer_key, x, y, width, height, label="", role="generic")ScopeOne.list_markups(layer_key=None)ScopeOne.update_markup(markup_id, label=None, visible=None, selected=None, **geometry)ScopeOne.remove_markup(markup_id)ScopeOne.clear_markups(layer_key=None)ScopeOne.device_properties(device, from_cache=True)ScopeOne.device_property_names(device)ScopeOne.get_property(device, property, from_cache=True)ScopeOne.set_property(device, property, value)ScopeOne.read_exposure(camera="All")ScopeOne.set_exposure(exposure_ms, camera="All")ScopeOne.get_roi(camera)ScopeOne.set_roi(camera, x, y, width, height)ScopeOne.set_half_roi(camera)ScopeOne.clear_roi(camera="All")ScopeOne.xy_stage_devices()ScopeOne.z_stage_devices()ScopeOne.current_xy_stage_device()ScopeOne.current_focus_device()ScopeOne.read_xy_position(device=None)ScopeOne.read_z_position(device=None)ScopeOne.move_xy_relative(dx, dy, device=None)ScopeOne.move_z_relative(dz, device=None)ScopeOne.move_xy_to(x, y, device=None)ScopeOne.move_z_to(z, device=None)ScopeOne.start_stage_mosaic(camera_id, xy_stage_id, rows=1, columns=1, pixel_size_um=1.0, step_x_um=0.0, step_y_um=0.0, settle_ms=150, return_to_start=True, gallery_save_dir=None)ScopeOne.stage_mosaic_status()ScopeOne.cancel_stage_mosaic()ScopeOne.processing_state()ScopeOne.processing_modules()ScopeOne.set_processing_bit_depth(bit_depth)ScopeOne.set_realtime_processing(enabled)ScopeOne.start_processing()ScopeOne.stop_processing()ScopeOne.add_processing_module(kind, parameters=None)ScopeOne.remove_processing_module(index)ScopeOne.set_processing_module_parameters(index, parameters)ScopeOne.reset_processing_module_state(index)ScopeOne.experiment_document()ScopeOne.validate_experiment(document)ScopeOne.save_experiment(file_path, document)ScopeOne.load_experiment(file_path)ScopeOne.start_experiment(document)ScopeOne.experiment_status(experiment_id)ScopeOne.cancel_experiment(experiment_id)ExperimentSession.experiment_id()ExperimentSession.status()ExperimentSession.document()ExperimentSession.cancel()ExperimentSession.close()ScopeOne.frame_mapping_info()ScopeOne.write_frame_mapping(image, camera="python", bits_per_sample=None, frame_index=0, source_roi=None)ScopeOne.process_frame_mapping(camera=None, start_module_index=None, end_module_index=None)ScopeOne.process_image(image, camera="python", bits_per_sample=None, start_module_index=None, end_module_index=None)ScopeOne.latest_raw_frame(camera)ScopeOne.layer_frame(layer_key)ScopeOne.show_frame_mapping_as_layer(layer_id="python_result", name="Python Result", camera=None)ScopeOne.save_frame_mapping(save_dir, base_name, format="tiff", compression=False, compression_level=6, camera=None)ScopeOne.continue_pipeline(frame, image=None)ScopeOne.show_frame(frame, image=None, layer_id="python_result", name="Python Result")ScopeOne.show_image(image, layer_id="python_result", name="Python Result", camera="python", bits_per_sample=None)ScopeOne.save_frame(frame, save_dir, base_name, image=None, format="tiff", compression=False, compression_level=6)ScopeOne.save_image(image, save_dir, base_name, format="tiff", compression=False, compression_level=6, camera="python", bits_per_sample=None)ScopeOne.record(frames, camera="All", timeout_ms=120000, mda_interval_ms=0.0, z_positions=None, positions=None, order=None)RecordingSession.camera_ids()RecordingSession.frame_count(camera=None)RecordingSession.frame(camera, index)RecordingSession.process_frame(camera, index, start_module_index=None, end_module_index=None)RecordingSession.frames(camera)RecordingSession.save(save_dir, base_name, format="tiff", compression=False, compression_level=6)RecordingSession.close()FrameResult.write(image=None)
FrameResult exposes common metadata as typed attributes: camera, width, height, pixel_format, bits_per_sample, frame_index, timestamp_ns, source_roi, module_index, next_module_index, and start_module_index.
FrameResult.write(image) clips and casts the edited pixels to the frame format, writes them into the shared mapping, and updates frame.image to the written array.
ScopeOne uses one local control pipe for JSON commands and one shared-memory block for frame transfer.
- Control endpoint on Windows:
\\.\pipe\ScopeOne.Api.local - Control endpoint on Linux/Unix:
<tempdir>/ScopeOne.Api.local - Message framing: 4-byte little-endian unsigned payload size, followed by UTF-8 JSON.
- Maximum JSON payload: 64 MiB.
- Requests may include a string or numeric
requestId; every response echoes it. - Success response:
{"type": "<request type>", "requestId": 1, "ok": true, ...} - Error response:
{"type": "<request type>", "requestId": 1, "ok": false, "error": "..."}
ping: health check.version: responseversionfor ScopeOne andcoreVersionfor ScopeOneCore.status: responseversion,coreVersion, cameras, devices, running previews, processing state, layer keys, Stage Mosaic status, recording progress, and writer status.capabilities: responsecapabilitieswith operation groups and hardware, filesystem, destructive, and long-running operation classifications.state_snapshot: responsesnapshotwith application and Core versions, configuration, hardware inventory, preview, processing, scene, live acquisition and writer progress, experiment, and session state.frame_mapping_info: responsemappingName,mappingSize,headerBytes,maxPayloadBytes, and supportedpixelFormats.load_config: fieldsconfigPath; responsecameraIds.unload_config: unload current Micro-Manager config.camera_ids: responsecameraIds.loaded_devices: responsedevices.start_preview: fieldscamera, accepts a camera id or"All".stop_preview: fieldscamera, accepts a camera id or"All".list_layers: responselayers.get_layer_histogram: fieldslayerKey; responsehistogramwith summary statistics and 256bins.get_pixel_value: fieldslayerKey,x,y; responsevalue.get_line_profile: fieldslayerKey,x1,y1,x2,y2; responsevalues.detect_particles: fieldslayerKey,threshold,minArea,maxArea, optionalmaxParticles,exportMask, andpublishMask; responseparticleCount, effective thresholds, truncation state, particle measurements, optional shared-memorymaskmetadata, and optionalmaskLayerKey.layer_options: responselayouts,colormaps,blendingModes.set_layer_layout: fieldslayout, acceptsside_by_sideoroverlay.set_visible_layers: fieldslayerKeys; responsevisibleLayers.set_layer_display: fieldslayerKey, optionalvisible,opacityPercent,gamma,colormap,blending, and display level fieldsminLevel,maxLevel,maxPossible.auto_layer_levels: fieldslayerKey; computes and applies histogram-based display levels.full_layer_levels: fieldslayerKey; restores the full intensity range.set_layer_auto_stretch: fieldslayerKey,enabled; continuously updates display levels from new histograms.get_source_display_transform: fieldssourceId; returns source offset, zoom, and flip state.set_source_display_transform: fieldssourceIdand optionaloffsetX,offsetY,zoomPercent,flipX,flipY.reset_source_display_transform: fieldssourceId; restores zero offset, 100 percent zoom, and no flips.move_layer: fieldslayerKey,offset; response orderedlayers.config_groups: responsegroups.configs: fieldsgroup; responseconfigs.current_config: fieldsgroup; responseconfig.set_config: fieldsgroup,config; responseconfig.remove_static_layer: fieldslayerKey.clear_static_layers: remove all static preview layers.create_line_markup: fieldslayerKey,x1,y1,x2,y2, optionallabelandrole; responsemarkupId.create_rect_markup: fieldslayerKey,x,y,width,height, optionallabelandrole; responsemarkupId.list_markups: optional fieldlayerKey; responsemarkupswithid,type,role,layerKey,layerKind,sourceId,coordinateSpace,label,visible,selected, and geometry fields.update_markup: fieldsmarkupIdand optionallabel,visible,selected, plus line fieldsx1,y1,x2,y2or rect fieldsx,y,width,height; responsemarkup.remove_markup: fieldsmarkupId.clear_markups: optional fieldlayerKey.device_property_names: fieldsdevice; responsenames.device_properties: fieldsdevice,fromCache; responseproperties.get_property: fieldsdevice,property,fromCache; responsevalue.set_property: fieldsdevice,property,value.read_exposure: optional fieldcamera; responseexposureMs.set_exposure: fieldsexposureMs, optional fieldcamera; response may include the read-backexposureMs.get_roi: fieldscamera; responsex,y,width,height.set_roi: fieldscamera,x,y,width,height; response may include read-back ROI geometry.set_half_roi: fieldscamera; response includes read-back ROI geometry.clear_roi: optional fieldcamera, defaults toAll.xy_stage_devices: responsedevices.z_stage_devices: responsedevices.current_xy_stage_device: responsedevice.current_focus_device: responsedevice.read_xy_position: optional fielddevice; responsex,y.read_z_position: optional fielddevice; responsez.move_xy_relative: fieldsdevice,dx,dy.move_z_relative: fieldsdevice,dz.move_xy_to: fieldsdevice,x,y.move_z_to: fieldsdevice,z.start_stage_mosaic: fieldscameraId,xyStageId, and optionalrows,columns,pixelSizeUm,stepXUm,stepYUm,settleMs,returnToStart, andgallerySaveDir; starts asynchronous mosaic acquisition and returnsstatus.gallerySaveDirbecomes the default directory if the resulting Gallery session is saved later.stage_mosaic_status: responsestatuswithstate, tile progress, message, and completed session ID.cancel_stage_mosaic: cancels the running mosaic and returns its finalstatus.processing_modules: responsebitDepth,realTime, andmodules.set_processing_bit_depth: fieldsbitDepth, accepts8or16.set_realtime_processing: fieldsenabled.add_processing_module: fieldskind, optionalparameters; responseindex.remove_processing_module: fieldsindex.set_processing_module_parameters: fieldsindex,parameters.reset_processing_module_state: fieldsindex.experiment_document: returns the shared schema version 1 document, initializing a Draft from current cameras, processing, layers, and markups when needed; responsedocument.validate_experiment: fielddocument; response contains the canonical validateddocument.save_experiment: fieldsfilePath,document; validates and atomically saves the document.load_experiment: fieldfilePath; replaces the shared UI document when no experiment is running and responds withdocument.start_experiment: fielddocument; starts a validated Draft asynchronously and responds withexperimentId,state, anddocument.experiment_status: fieldexperimentId; responsestate,cancelRequested,document, liveprogressandwriterstate while active, and completed recording session details when available.cancel_experiment: fieldexperimentId; requests cancellation and returns the current experiment status.record: fieldsframes,camera,timeoutMs,mdaIntervalMs,zPositions,positions,order; responsesessionId,cameraIds.session_info: fieldssessionId; responsecameraIds,frameCount,frameCounts.session_close: fieldssessionId; releases the recorded session held by the app.session_frame: fieldssessionId,camera,index; responsemappingName,mappingSize, and frame metadata.latest_raw_frame: fieldscamera; responsemappingName,mappingSize, and frame metadata.layer_frame: fieldlayerKey; exports the current raw, processed, static, mosaic, or Gallery layer frame and returns shared-memory metadata.session_process_frame: fieldssessionId,camera,index, optionalstartModuleIndexorendModuleIndex; responsemappingName,mappingSize, frame metadata, and optional stage metadata.process_frame_mapping: optional fieldscamera,startModuleIndexorendModuleIndex; responsemappingName,mappingSize, frame metadata, and optional stage metadata.show_frame_mapping_as_layer: optional fieldscamera,layerId,name; imports the current shared memory frame as a preview layer and returnslayerKey.save_frame_mapping: fieldssaveDir,baseName,format,compression,compressionLevel, optional fieldcamera; imports the current shared memory frame and saves it as a one-frame output.session_save: fieldssessionId,saveDir,baseName,format,compression,compressionLevel; responsepaths.
{
"type": "record",
"frames": 1,
"camera": "Camera",
"timeoutMs": 120000,
"mdaIntervalMs": 0.0,
"zPositions": [0.0, 1.0],
"positions": [[0.0, 0.0]],
"order": ["time", "z", "xy"]
}record returns sessionId and cameraIds. If zPositions or positions is non-empty, recording uses the MDA snap path. If both are empty, recording uses the preview/raw-frame path.
For timed MDA with more than one time point, order must begin with time so event start times remain monotonic.
The initially created document is a complete editable Draft with in-memory recording enabled by default. Set plan.streamToDisk, plan.saveDir, and plan.baseName together for streamed output. Experiment documents are parsed strictly: every schema field is required, unknown fields and unsupported schema versions are rejected, and start_experiment accepts only Draft documents whose camera IDs are currently available. start_experiment is non-blocking; use the returned ExperimentSession or the direct status and cancel methods to control the run. Call ExperimentSession.close() after completion to release retained recording frames while keeping document status available.
Processing module editing follows the desktop UI rules: stop real-time processing before changing bit depth, adding/removing modules, updating module parameters, or resetting module state. add_processing_module accepts fft, background_calibration, spatiotemporal_binning, gaussian_blur, and differential_rolling.
session_frame writes the selected frame into shared memory:
- Mapping name:
ScopeOne.Api.frame - Header layout:
scopeone::core::SharedFrameHeader - Pixel data starts at
scopeone::core::kSharedFrameHeaderSize - Python reads this through
scopeone.shm.frame_to_ndarray() - Responses include
camera,width,height,stride, stringpayloadBytes,pixelFormat,bitsPerSample, stringframeIndex, stringtimestampNs,sourceRoiX,sourceRoiY,sourceRoiWidth,sourceRoiHeight, andsourceRoiValid. - Session frames can come from memory, saved TIFF stacks, or saved binary streams.
ScopeOne.latest_raw_frame(...),ScopeOne.layer_frame(...),RecordingSession.frame(...),RecordingSession.frames(...),RecordingSession.process_frame(...), andScopeOne.process_frame_mapping(...)returnFrameResultobjects.
latest_raw_frame exports the current live raw frame for Python processing, while layer_frame accepts any key returned by list_layers. Particle detection can return its mask as a FrameResult with export_mask=True or publish the mask directly with publish_mask=True. session_process_frame reads a stored session frame, processes it through the current pipeline, and writes the result into the same shared memory block. Use endModuleIndex to stop after one stage. The response then includes moduleIndex and nextModuleIndex. Pass the edited numpy array to ScopeOne.continue_pipeline(frame, image=edited_image) to write it back and continue with the next pipeline stage. Pass a frame and optional edited image to ScopeOne.show_frame(frame, image=edited_image) to display it in the ScopeOne preview as a static layer. Use ScopeOne.save_frame(frame, save_dir, base_name, image=edited_image) to save the current Python/C++ result directly. FrameResult.write() requires the shared mapping to still contain the same frame metadata, so request the frame again after another frame export overwrites the mapping. process_frame_mapping, show_frame_mapping_as_layer, and save_frame_mapping reuse the last exported or explicitly imported camera id when camera is omitted. Provide camera the first time a mapping was written by an external client.
Python can also publish a new numpy image without first requesting a ScopeOne frame. ScopeOne.write_frame_mapping(...) writes a 2D mono array into the shared frame mapping and returns a FrameResult; ScopeOne.show_image(...), ScopeOne.process_image(...), and ScopeOne.save_image(...) are convenience wrappers around that path.
The server returns ok: false and a short actionable error string. Recording errors distinguish startup failure, timeout before session data, MDA captured no frames, preview-frame recording captured no frames, and invalid session/frame indexes.
ScopeOne()connects to a running ScopeOne control server over the local platform endpoint.ScopeOne.connect("local")is an alias for the default local connection.- Start the ScopeOne app before using the Python API.
- Python 3.10 or newer is required.
- Frame reads use a shared-memory export block plus
scopeone.shmparsing helpers. - The package itself depends on
numpyandpywin32.Pillowis only needed if you want notebook/image display helpers in your own code. - Runnable examples are kept in
examples/example_minimal.ipynb.
Build the desktop app when needed:
.\scripts\build.ps1 --target all