Skip to content

Processing Contractility data #6

@iarrillagag

Description

@iarrillagag

Dear Axion Developers,

I hope you're doing well. I'm currently processing contractility data using MATLAB and comparing it to the plots generated directly by AxIS Navigator. I noticed that in AxIS, the signals typically start and end at a baseline of zero, which makes the traces look smooth and well-aligned for visual inspection.

However, when plotting the same data in MATLAB using the AxisFile API, the signals often start and end at different baseline levels, which causes some drift or offset—especially visible in zoomed plots.

I'm wondering:
1. Is this baseline alignment (zero at start and end) something AxIS automatically applies for visualization?
2. If so, is there a specific method you recommend to reproduce this kind of correction in MATLAB for visual consistency?

I’ve tried applying a linear baseline correction between the median of the first and last 0.5 seconds of the signal, and it seems to work visually. Just wanted to confirm if that aligns with what AxIS does internally or if I'm doing something wrong with my MATLAB code.

Thanks so much for your help!

Best,

Iñigo Arrillaga
Biomedical Engineering – Tecnun – UNAV

Here is the code: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Proyecto Fin de Grado - Ingeniería Biomédica %%%%
%%% Universidad de Navarra - Tecnun %%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Instructions:
% ------------
% In our case, it is a MEAS plate containing 24 wells (4
% rows: A, B, C, and D x 6 columns: 1-6). Each well contains
% 16 electrodes (4 x 4), for a total of 384 electrodes.
% Total recording duration: 246.98 seconds
%
% The objective is to compare contractility signals (%ΔZ)
% obtained with the % Maestro Edge system (Axion BioSystems),
% both before and after filtering automatically applied using
% the Artifact Eliminator module in AxIS Navigator.
%
% Parameters such as DC and voltage development time will be
% identified. They will then be compared by day and experimental
% conditions.
%
% Author: Iñigo Arrillaga García
% Date: 25/05/2025
%
clear; close all; clc;
warning('off', 'all');

%% 0 - Initialization.
% '20250313_pacedcontractility_4dpd_placageltrex(000).raw'
% '20250313_pacedcontractility_4dpd_placageltrex(000)(001)_ArtifactEliminator.raw'

% Raw files names input:
filename_raw = input('Enter the name of the original, unfiltered file: ');
filename_filt = input('Enter the name of the file filtered by Artifact Eliminator: ');

% File verification:
if ~isfile(filename_raw) || ~isfile(filename_filt)
error('One of the .raw files is not in the current folder.');
end

% Load of Axion structures:
FileData_raw = AxisFile(filename_raw);
FileData_filt = AxisFile(filename_filt);

% Signal segment input to be analyzed (in seconds):
%intervalo = input('\nEnter the time interval to analyze (e.g. [0 24]): ');
intervalo = [0 inf];

% Initialization of original and filtered structures:
AllData = struct();
FilteredData = struct();

%% 1 - Data loading per well and electrode.
fprintf('\nLoading original and Artifact Eliminator-filtered signals:\n');
for row = 1:4
for col = 1:6
wellName = sprintf('%c%d', 'A' + (row - 1), col);
fprintf(' - Processing %s\n', wellName);
orig = cell(4,4);
filt = cell(4,4);

    for er = 1:4
        for ec = 1:4
            electrodeName = sprintf('%d%d', ec, er);
            try
                % Original Signal:
                data_raw = FileData_raw.RawContractilityData.LoadData(wellName, electrodeName, intervalo);
                wf_raw = data_raw{row, col, ec, er};
                [t_raw, z_raw] = wf_raw.GetTimeContractilityVector();

                % Filtered Signal:
                data_filt = FileData_filt.RawContractilityData.LoadData(wellName, electrodeName, intervalo);
                wf_filt = data_filt{row, col, ec, er};
                [~, z_filt] = wf_filt.GetTimeContractilityVector();

            catch
                t_raw = []; z_raw = []; z_filt = [];
            end
            orig{er, ec} = struct('t', t_raw, 'z', z_raw, 'name', electrodeName);
            filt{er, ec} = struct('t', t_raw, 'z', z_filt, 'name', electrodeName);
        end
    end
    AllData.(wellName) = orig;
    FilteredData.(wellName) = filt;
end

end

%% 2 - Separate Display of Original & Filtered Signals per well.
fprintf('\nSignals per well Plot:\n');
wellRow = input('Introduce well row (1=A, 2=B, ...): ');
wellCol = input('Introduce well column (1–6): ');
wellName = sprintf('%c%d', 'A' + (wellRow - 1), wellCol);

pozo_orig = AllData.(wellName);
pozo_filt = FilteredData.(wellName);

% Size and location of centered windows
screenSize = get(0, 'ScreenSize');
figWidth = 1000;
figHeight = 700;
xOffset = 60;
yCenter = screenSize(4)/2 - figHeight/2;

centrar = input('Do you want to visually center the signals at start and end (like AxIS)? (1 = yes, 0 = no): ');

%% Consideration 1.1 – INITIAL Zoom – ORIGINAL Signal (0–1 s)
fprintf('\nInitial Zoom – Original signals (0–1 s)\n');
pozo = AllData.(wellName);
figure;
sgtitle(sprintf('Well %s – Original Signals Zoom(0–1 s)', wellName), 'FontWeight', 'bold');

for er = 1:4
for ec = 1:4
idx = (er - 1) * 4 + ec;
subplot(4,4,idx);
s = pozo{er, ec};

    if ~isempty(s.t)
        mask = s.t >= 0 & s.t <= 1;
        t_zoom = s.t(mask);
        z_zoom = s.z(mask);

        if centrar
            mask_start = s.t >= 0 & s.t <= 0.5;
            mask_end = s.t >= s.t(end)-0.5 & s.t <= s.t(end);

            if sum(mask_start) >= 3 && sum(mask_end) >= 3
                base_ini = median(s.z(mask_start));
                base_fin = median(s.z(mask_end));
                correction = linspace(base_ini, base_fin, numel(s.z))';
                zc_full = s.z - correction;
                z_zoom = zc_full(mask);
            end
        end

        plot(t_zoom, z_zoom, 'b');
        title(sprintf('Electrode %d%d', ec, er));
        xlim([0 1]);
    else
        title(sprintf('Electrode %d%d (empty)', ec, er));
    end
    xlabel('t (s)'); ylabel('%ΔZ'); grid on;
end

end

%% Consideration 1.2 – INITIAL Zoom – FILTERED Signal (0–1 s)
fprintf('\nInitial Zoom – Filtered signals (0–1 s)\n');
pozo_f = FilteredData.(wellName);
figure;
sgtitle(sprintf('Well %s – Filtered Signals Zoom(0–1 s)', wellName), 'FontWeight', 'bold');

for er = 1:4
for ec = 1:4
idx = (er - 1) * 4 + ec;
subplot(4,4,idx);
s = pozo_f{er, ec};

    if ~isempty(s.t)
        mask = s.t >= 0 & s.t <= 1;
        t_zoom = s.t(mask);
        z_zoom = s.z(mask);

        if centrar
            mask_start = s.t >= 0 & s.t <= 0.5;
            mask_end = s.t >= s.t(end)-0.5 & s.t <= s.t(end);

            if sum(mask_start) >= 3 && sum(mask_end) >= 3
                base_ini = median(s.z(mask_start));
                base_fin = median(s.z(mask_end));
                correction = linspace(base_ini, base_fin, numel(s.z))';
                zc_full = s.z - correction;
                z_zoom = zc_full(mask);
            end
        end

        plot(t_zoom, z_zoom, 'r');
        title(sprintf('Electrode %d%d', ec, er));
        xlim([0 1]);
    else
        title(sprintf('Electrode %d%d (empty)', ec, er));
    end
    xlabel('t (s)'); ylabel('%ΔZ'); grid on;
end

end

%% Consideration 1.3 – FINAL Zoom – ORIGINAL Signal (245.98–246.98 s)
fprintf('\nFinal Zoom – Original Signal (245.98–246.98 s)\n');
pozo = AllData.(wellName);
figure;
sgtitle(sprintf('Well %s – Original (245.98–246.98 s)', wellName), 'FontWeight', 'bold');

for er = 1:4
for ec = 1:4
idx = (er - 1) * 4 + ec;
subplot(4,4,idx);
s = pozo{er, ec};

    if ~isempty(s.t)
        mask = s.t >= 245.98 & s.t <= 246.98;
        t_zoom = s.t(mask);
        z_zoom = s.z(mask);

        if centrar
            mask_start = s.t >= 0 & s.t <= 0.5;
            mask_end = s.t >= s.t(end)-0.5 & s.t <= s.t(end);

            if sum(mask_start) >= 3 && sum(mask_end) >= 3
                base_ini = median(s.z(mask_start));
                base_fin = median(s.z(mask_end));
                correction = linspace(base_ini, base_fin, numel(s.z))';
                zc_full = s.z - correction;
                z_zoom = zc_full(mask);
            end
        end

        plot(t_zoom, z_zoom, 'b');
        title(sprintf('Electrode %d%d', ec, er));
        xlim([245.98 246.98]);
    else
        title(sprintf('Electrode %d%d (empty)', ec, er));
    end
    xlabel('t (s)'); ylabel('%ΔZ'); grid on;
end

end

%% Consideration 1.4 – FINAL Zoom – FILTERED Signal (245.98–246.98 s)
fprintf('\nFinal Zoom – Filtered Signal (245.98–246.98 s)\n');
pozo_f = FilteredData.(wellName);
figure;
sgtitle(sprintf('Well %s – Filtered (245.98–246.98 s)', wellName), 'FontWeight', 'bold');

for er = 1:4
for ec = 1:4
idx = (er - 1) * 4 + ec;
subplot(4,4,idx);
s = pozo_f{er, ec};

    if ~isempty(s.t)
        mask = s.t >= 245.98 & s.t <= 246.98;
        t_zoom = s.t(mask);
        z_zoom = s.z(mask);

        if centrar
            mask_start = s.t >= 0 & s.t <= 0.5;
            mask_end = s.t >= s.t(end)-0.5 & s.t <= s.t(end);

            if sum(mask_start) >= 3 && sum(mask_end) >= 3
                base_ini = median(s.z(mask_start));
                base_fin = median(s.z(mask_end));
                correction = linspace(base_ini, base_fin, numel(s.z))';
                zc_full = s.z - correction;
                z_zoom = zc_full(mask);
            end
        end

        plot(t_zoom, z_zoom, 'r');
        title(sprintf('Electrode %d%d', ec, er));
        xlim([245.98 246.98]);
    else
        title(sprintf('Electrode %d%d (empty)', ec, er));
    end
    xlabel('t (s)'); ylabel('%ΔZ'); grid on;
end

end

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions