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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,4 @@ Assets/StreamingAssets/EphysLink-*
Assets/Samples/*
Assets/settings_conversion.txt
.github/copilot-instructions.md
.nuget/
4 changes: 2 additions & 2 deletions Assets/Materials/Volume/InPlaneSliceMaterialBlit.mat
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ Material:
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
- _ForwardDirection: {r: 0, g: 0, b: 0, a: 0}
- _LeftDirection: {r: 1, g: 0, b: 0, a: 0}
- _RecordingRegionCenterPosition: {r: 52.480907, g: 228, b: 145.78673, a: 0}
- _RightDirection: {r: 1, g: 0, b: 0, a: 0}
- _RecordingRegionCenterPosition: {r: 328, g: 85.79999, b: 100.18744, a: 0}
- _RightDirection: {r: -0.00000023841858, g: 0, b: -1, a: 0}
- _Scale: {r: 200, g: 200, b: 4.5, a: 0}
- _SpecColor: {r: 0.2, g: 0.2, b: 0.2, a: 1}
- _TipPosition: {r: 0, g: 0, b: 0, a: 0}
Expand Down
5 changes: 5 additions & 0 deletions Assets/Scripts/Models/Scene/ManipulatorState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ public record ManipulatorState

public Vector4 DemoTargetCoordinate;

/// <summary>
/// Indicates if the demo mode is currently running.
/// </summary>
/// <remarks>Not saved. Will always start as off.</remarks>
[NonSerialized]
public bool IsDemoRunning;

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,9 @@ private void Start()

private void Update()
{
// Update visualization probe position if this is a visualization probe.
UpdateVisualizationProbePosition();

// If the user is holding one or more click keys and we are past the hold delay, increment the position
if (clickKeyHeld > 0 && (Time.realtimeSinceStartup - clickKeyPressTime) > keyHoldDelay)
// Set speed to Tap instead of Hold for manipulator keyboard control
Expand Down Expand Up @@ -351,8 +354,8 @@ private void OnDestroy()

private void OnProbeStateChanged(ProbeState state)
{
// Skip if no state.
if (state == null)
// Skip if is visualization probe (updates from local state) no state.
if (IsVisualizationProbe || state == null)
{
return;
}
Expand Down Expand Up @@ -918,6 +921,38 @@ public void DragMovementRelease()

#region Set Probe pos/angles

/// <summary>
/// Apply visualization probe updates if available.
/// This should be called from the Update() method of concrete ProbeController implementations.
/// </summary>
private void UpdateVisualizationProbePosition()
{
if (!IsVisualizationProbe)
return;

// Update position.
transform.position =
BrainAtlasManager.ActiveReferenceAtlas.Atlas2World(
BrainAtlasManager.ActiveAtlasTransform.T2U_Vector(VisualizationLocalAPMLDV)
);


// Update orientation.
transform.rotation = _initialRotation;
transform.RotateAround(_probeTipT.position, transform.up, VisualizationLocalAngles.x);
transform.RotateAround(_probeTipT.position, transform.right, VisualizationLocalAngles.y);
transform.RotateAround(_probeTipT.position, transform.forward, -VisualizationLocalAngles.z);

// Update tip coords.
SetTipWorldU();

// Update recording region info.
ProbeManager.ProbeMoved();

// Update surface coordinates.
ProbeManager.UpdateSurfacePosition();
}

/// <summary>
/// Set the probe position to the current apml/depth/angles values
/// </summary>
Expand Down
31 changes: 31 additions & 0 deletions Assets/Scripts/Pinpoint/Probes/ProbeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,36 @@ public void Register(ProbeManager probeManager)
public bool ManipulatorManualControl;
public bool ManipulatorKeyboardMoveInProgress;

#region Visualization Probe Local Position

/// <summary>
/// Local field for visualization probe position updates from EphysLink.
/// Used to avoid excessive Redux state updates during rapid polling.
/// </summary>
public Vector3 VisualizationLocalAPMLDV { get; set; }

/// <summary>
/// Local field for visualization probe depth.
/// </summary>
public float VisualizationLocalDepth { get; set; }

/// <summary>
/// Local field for visualization probe angles.
/// </summary>
public Vector3 VisualizationLocalAngles { get; set; }

/// <summary>
/// Local field for visualization probe forward vector.
/// </summary>
public Vector3 VisualizationLocalForwardT { get; set; }

/// <summary>
/// Indicates if new visualization data is available and needs to be applied.
/// </summary>
public bool IsVisualizationProbe { get; set; }

#endregion

public abstract Transform ProbeTipT { get; }

public abstract (Vector3 tipCoordWorldU, Vector3 tipRightWorldU, Vector3 tipUpWorldU, Vector3 tipForwardWorldU) GetTipWorldU();
Expand Down Expand Up @@ -69,4 +99,5 @@ public void SetSpaceTransform(CoordinateSpace atlas, CoordinateTransform transfo
}



}
138 changes: 63 additions & 75 deletions Assets/Scripts/Services/EphysLinkService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BestHTTP.SocketIO3;
using BrainAtlas;
Expand All @@ -19,7 +20,6 @@
using Utils;
using Utils.Types;
using Action = System.Action;
using System.Threading;

namespace Services
{
Expand All @@ -34,7 +34,6 @@ public class EphysLinkService

// Visualization loop interval (ms)
private const int VISUALIZATION_UPDATE_INTERVAL_MS = 10; // about 60 Hz

#endregion

#region Services
Expand All @@ -52,15 +51,16 @@ public class EphysLinkService

public string SocketId => _socket.Id;

// Cancellation for the visualization update loop
private CancellationTokenSource _visualizationLoopCts;

#endregion

#region Demo Loop

private readonly HashSet<string> _runningDemoLoops = new();
private const float DEMO_SPEED = 0.5f; // mm/s

// Cancellation for the visualization update loop
private CancellationTokenSource _visualizationLoopCts;

#endregion

public EphysLinkService(StoreService storeService)
Expand Down Expand Up @@ -169,7 +169,9 @@ async Task OnConnectedAsync()
}
catch (Exception ex)
{
HandleError($"{GetErrorConnectingToServerMessage()} Caused exception: {ex.Message}");
HandleError(
$"{GetErrorConnectingToServerMessage()} Caused exception: {ex.Message}"
);
}
}

Expand All @@ -189,7 +191,10 @@ async Task OnConnectedAsync()
// On successful connection, delegate to async task handler.
_socket.Once(
"connect",
() => { _ = OnConnectedAsync(); }
() =>
{
_ = OnConnectedAsync();
}
);

// On error.
Expand Down Expand Up @@ -561,6 +566,10 @@ private static string ToJson<T>(T data)
return JsonUtility.ToJson(data);
}

#endregion

#region Visualization control

// Starts the continuous visualization update loop until the socket disconnects or service disconnects.
private void StartVisualizationLoop()
{
Expand All @@ -573,17 +582,21 @@ private void StartVisualizationLoop()
// Cancels and disposes the visualization update loop.
private void StopVisualizationLoop()
{
if (_visualizationLoopCts != null)
if (_visualizationLoopCts == null)
return;
try
{
try { _visualizationLoopCts.Cancel(); }
catch (ObjectDisposedException) { /* ignore disposed */ }
catch (Exception ex)
{
Debug.Log($"Ignored exception during visualization loop cancellation: {ex}");
}
_visualizationLoopCts.Dispose();
_visualizationLoopCts = null;
_visualizationLoopCts.Cancel();
}
catch (ObjectDisposedException)
{ /* ignore disposed */
}
catch (Exception ex)
{
Debug.Log($"Ignored exception during visualization loop cancellation: {ex}");
}
_visualizationLoopCts.Dispose();
_visualizationLoopCts = null;
}

// The loop body calling UpdateVisualizationProbePosition at a fixed interval.
Expand All @@ -593,7 +606,9 @@ private async Task VisualizationUpdateLoop(CancellationToken token)
{
try
{
var sceneState = _storeService.Store.GetState<SceneState>(SliceNames.SCENE_SLICE);
var sceneState = _storeService.Store.GetState<SceneState>(
SliceNames.SCENE_SLICE
);
await UpdateVisualizationProbePosition(sceneState);
}
catch (OperationCanceledException)
Expand All @@ -616,21 +631,8 @@ private async Task VisualizationUpdateLoop(CancellationToken token)
}
}

#endregion

#region Visualization control

private async Task UpdateVisualizationProbePosition(SceneState sceneState)
{
List<(
string Name,
Vector3 SurfaceAPMLDV,
float Depth,
Vector3 ForwardT,
Vector3 Angles,
Vector2 PitchRange
)> requests = new();

foreach (
var manipulatorState in sceneState.Manipulators.Where(state =>
!string.IsNullOrEmpty(state.VisualizationProbeName)
Expand All @@ -647,12 +649,12 @@ var manipulatorState in sceneState.Manipulators.Where(state =>
) == null
|| visualizationProbeManager == null
)
return;
continue;

// Get the current position of the manipulator.
var positionResponse = await GetPosition(manipulatorState.Id);
if (HasError(positionResponse.Error))
return;
continue;

// Apply reference coordinate offset.
var referenceCoordinateAdjustedManipulatorPosition =
Expand Down Expand Up @@ -700,13 +702,16 @@ var manipulatorState in sceneState.Manipulators.Where(state =>
var transformedAPMLDV = BrainAtlasManager.World2T_Vector(
referenceCoordinateAdjustedWorldPosition
);

// Cancel update if the manipulator's position did not change by a lot.
var probeState = sceneState.Probes.FirstOrDefault(state => state.Name == manipulatorState.VisualizationProbeName);
if (probeState == null || Vector3.SqrMagnitude(transformedAPMLDV - probeState.APMLDV) < 0.0001f)
{
var probeState = sceneState.Probes.FirstOrDefault(state =>
state.Name == manipulatorState.VisualizationProbeName
);
if (
probeState == null
|| Vector3.SqrMagnitude(transformedAPMLDV - probeState.APMLDV) < 0.0001f
)
continue;
}

// Get the current forward vector of the probe.
var forwardT = BrainAtlasManager.ActiveAtlasTransform.U2T_Vector(
Expand All @@ -715,46 +720,29 @@ var manipulatorState in sceneState.Manipulators.Where(state =>
)
);

switch (sceneState.NumberOfAxesOnManipulator)
// Get the ProbeController for this visualization probe
var probeController = visualizationProbeManager.ProbeController;
if (probeController == null)
continue;

var depth = sceneState.NumberOfAxesOnManipulator switch
{
// Set the probe position in the store.
case 3:
requests.Add(
(
manipulatorState.VisualizationProbeName,
transformedAPMLDV,
duraOffsetAdjustment,
forwardT,
manipulatorState.Angles,
_pitchRange
)
);
break;
case 4:
requests.Add(
(
manipulatorState.VisualizationProbeName,
transformedAPMLDV,
referenceCoordinateAdjustedManipulatorPosition.w,
forwardT,
manipulatorState.Angles,
_pitchRange
)
);
break;
default:
throw new ValueOutOfRangeException(
"Number of axes on manipulator is invalid."
);
}
}
3 => duraOffsetAdjustment,
4 => referenceCoordinateAdjustedManipulatorPosition.w,
_ => throw new ValueOutOfRangeException(
"Number of axes on manipulator is invalid."
),
};

// Dispatch all position updates in one go (if any).
if (requests.Any())
_storeService.Store.Dispatch(
SceneActions.BULK_SET_PROBE_POSITION_AND_ANGLES_BY,
requests
);
// Write position data directly to ProbeController's local fields
probeController.VisualizationLocalAPMLDV = transformedAPMLDV;
probeController.VisualizationLocalDepth = depth;
probeController.VisualizationLocalAngles = manipulatorState.Angles;
probeController.VisualizationLocalForwardT = forwardT;

// Mark probe as visualization probe to apply new data in Update().
probeController.IsVisualizationProbe = true;
}
}

public async Task SetManipulatorReferenceCoordinateToCurrentPosition(string manipulatorId)
Expand Down