From 6468bfc641952346fc52e6b61300aacd07bb2f77 Mon Sep 17 00:00:00 2001 From: Tom McGrath Date: Thu, 29 Jun 2023 11:49:22 -0500 Subject: [PATCH 1/3] add way to programmatically generate .pgm files --- src/Autosampler.jl | 102 ++------------------------------- src/progfile.jl | 139 +++++++++++++++++++++++++++++++++++++++++++++ src/seqfile.jl | 97 +++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+), 98 deletions(-) create mode 100644 src/progfile.jl create mode 100644 src/seqfile.jl diff --git a/src/Autosampler.jl b/src/Autosampler.jl index c23d488..ff194c8 100644 --- a/src/Autosampler.jl +++ b/src/Autosampler.jl @@ -4,103 +4,9 @@ using CSV, DataFrames, ImagineFormat, ImagineInterface using Random, Mmap export stim_randomize, update_imagine +export chromeleon_program -""" - stim_randomize(filein, trials::Int, fileout=insertfilename(filein, "_sequence")) +include("seqfile.jl") +include("progfile.jl") -Generate a randomize sequence of stimuli. `filein` is a string containing the name -of a CSV file that describes the unique stimuli (see format described in the README). -`trials` specifies the number of pseudo-random sequences used to generate the -final stimulus sequence. -`fileout` is an optional argument used to control the name of the output file; -the default is to insert "_sequence" right before the extension of `filein`. -""" -function stim_randomize(filein::AbstractString, trials::Int; fileout=insertfilename(filein, "_sequence"), kwargs...) - open(fileout, "w") do io - stim_randomize(io, filein, trials; kwargs...) - end - return fileout -end - -function stim_randomize(io::IO, filein::AbstractString, trials::Int; header=false, kwargs...) - df = CSV.File(filein) |> DataFrame - println("Headers found: ", String.(names(df))) - n = size(df, 1) - println(n, " stimuli found") - # $ is a disallowed character since we will use it as a separator - for i = 1:n - occursin('\$', df[i,1]) && error("\$ is disallowed in stimulus names") - end - p = Int[] - for i = 1:trials - append!(p, randperm(n)) - end - CSV.write(io, df[p, :]; header=header, kwargs...) - flush(io) -end - -function update_imagine(imaginefile, sequencefile; um_per_pixel=nothing, aifile=replaceext(imaginefile, ".ai"), difile=replaceext(imaginefile, ".di"), updatedfile=imaginefile, csvheader=0, kwargs...) - header = ImagineFormat.parse_header(imaginefile) - haskey(header, "stimulus sequence") && error(imaginefile, " already has a `stimulus sequence` entry") - if um_per_pixel === nothing - um_per_pixel = header["um per pixel"] - end - um_per_pixel < 0 && error("must specify um_per_pixel") - header["um per pixel"] = um_per_pixel - - df = CSV.File(sequencefile; header=csvheader, kwargs...) |> DataFrame - n = size(df, 1) - - # Scan the AI file for stimulus triggers - ai = parse_ai(aifile, header) - aistim = getname(ai, "stimuli") - stimhi = find_pulse_starts(aistim; sampmap=:volts) - stimlo = find_pulse_stops(aistim; sampmap=:volts) - if occursin("camera frame TTL", header["label list"]) - aiframe = getname(ai, "camera frame TTL") - framestarts = find_pulse_starts(aiframe; sampmap=:volts) - elseif occursin("camera1 frame monitor", header["di label list"]) - di = parse_di(difile, header) - diframe = getname(di, "camera1 frame monitor") - framestarts = find_pulse_starts(diframe) #; sampmap=:volts) - end - - # Record as frameidx after the stimulus trigger - fps = header["frames per stack"] - framehi, framelo = Int[], Int[] - for (frameidxs, scanidxs) in ((framehi, stimhi), (framelo, stimlo)) - for i in scanidxs - idx = last(searchsorted(framestarts, i)) - push!(frameidxs, idx) - end - end - - # Add to header - header["stimulus sequence"] = join(df[:,1], '\$') - header["stimulus scan hi"] = stimhi - header["stimulus scan lo"] = stimlo - header["stimulus frame hi"] = framehi - header["stimulus frame lo"] = framelo - - if updatedfile == imaginefile - mv(imaginefile, imaginefile*".orig") - sleep(0.25) - isfile(imaginefile) && error("failed to move the old file") - end - ImagineFormat.save_header(updatedfile, header; misc=( - "stimulus sequence", "stimulus scan hi", "stimulus scan lo", - "stimulus frame hi", "stimulus frame lo")) - return header -end - -function insertfilename(filename, tail) - basename, ext = splitext(filename) - return basename*tail*ext -end - -function replaceext(filename, ext) - basename, _ = splitext(filename) - return basename*ext -end - -end # module +end \ No newline at end of file diff --git a/src/progfile.jl b/src/progfile.jl new file mode 100644 index 0000000..7e98f68 --- /dev/null +++ b/src/progfile.jl @@ -0,0 +1,139 @@ +# These parameters were chosen from .pgm files used by former Holy lab members +# Most parameters were kept the same between users, with the exception of the flowrate "Flow" and wait time "Delay" +# Some of these parameters may be extraneous when using "InjectMode = UserProg". +const default_params = Dict{String,String}( + "Flow" => "0.540", # mL/min + "Delay" => "0.500", # min + + "PreflushVolume" => "5.0", # μL + + "TempCtrl" => "On", + "TemperatureNominal" => "30.0", # [°C] + "TemperatureLowerLimit" => "27.0", # [°C] + "TemperatureUpperLimit" => "33.0", # [°C] + "ReadyTempDelta" => "2.0", # [°C] + "PressureLowerLimit" => "0", # [bar] + "PressureUpperLimit" => "350", # [bar] + "MaximumFlowRampDown" => "6.000", # [ml/min²] + "MaximumFlowRampUp" => "6.000", # [ml/min²] + "%A.Equate" => "%A", + "DrawSpeed" => "10.000", # [µl/s] + "DrawDelay" => "1000", # [ms] + "DispSpeed" => "20.000", # [µl/s] + "DispenseDelay" => "0", + "WasteSpeed" => "20.000", # [µl/s] + "SampleHeight" => "2.000", # [mm] + "InjectWash" => "AfterDraw", + "WashVolume" => "100.000", # [µl] + "WashSpeed" => "30.000", # [µl/s] + "LoopWashFactor" => "1.000", + "PunctureOffset" => "0.0", # [mm] + "PumpDevice" => "\"Pump\"", + "SyncWithPump" => "Off", + "Pump_Pressure.Step" => "0.01", # [s] + "Pump_Pressure.Average" => "Off", + "Curve" => "5", +) + +""" + chromeleon_program(fileout::AbstractString, params::Dict{String,String}=default_params) + +Generate a Chromeleon program file (.pgm) to be used for timing injections performed by the autosampler +with an external TTL input (e.g. one from Imagine). +`fileout` is a string containing the name of the program file to be written. +`params` specifies the parameters to be used in the program file. Default settings based on settings used +by current and former Holy lab members are provided in `default_params`. +For typical use, only the flowrate `params["Flow"]` and wait time `params["Delay"]` need to be changed between experiments. +""" +function chromeleon_program(fileout::AbstractString, params::Dict{String,String}=default_params) + split(fileout, ".")[end] == "pgm" || error("fileout must have .pgm extension") + open(fileout, "w") do io + chromeleon_program(io, params) + end + return fileout +end + +function chromeleon_program(io::IO, params=default_params) + write(io, + "\tTempCtrl =\t$(params["TempCtrl"])\n", + "\tTemperature.Nominal =\t$(params["TemperatureNominal"]) [°C]\n", + "\tTemperature.LowerLimit =\t$(params["TemperatureLowerLimit"]) [°C]\n", + "\tTemperature.UpperLimit =\t$(params["TemperatureUpperLimit"]) [°C]\n", + "\tReadyTempDelta =\t$(params["ReadyTempDelta"]) [°C]\n", + "\tPressure.LowerLimit =\t$(params["PressureLowerLimit"]) [bar]\n", + "\tPressure.UpperLimit =\t$(params["PressureUpperLimit"]) [bar]\n", + "\tMaximumFlowRampDown =\t$(params["MaximumFlowRampDown"]) [ml/min²]\n", + "\tMaximumFlowRampUp =\t$(params["MaximumFlowRampUp"]) [ml/min²]\n", + "\t%A.Equate =\t$(params["%A.Equate"])\n", + "\tDrawSpeed =\t$(params["DrawSpeed"]) [µl/s]\n", + "\tDrawDelay =\t$(params["DrawDelay"]) [ms]\n", + "\tDispSpeed =\t$(params["DispSpeed"]) [µl/s]\n", + "\tDispenseDelay =\t$(params["DispenseDelay"]) [ms]\n", + "\tWasteSpeed =\t$(params["WasteSpeed"]) [µl/s]\n", + "\tSampleHeight =\t$(params["SampleHeight"]) [mm]\n", + "\tInjectWash =\t$(params["InjectWash"])\n", + "\tWashVolume =\t$(params["WashVolume"]) [µl]\n", + "\tWashSpeed =\t$(params["WashSpeed"]) [µl/s]\n", + "\tLoopWashFactor =\t$(params["LoopWashFactor"])\n", + "\tPunctureOffset =\t$(params["PunctureOffset"]) [mm]\n", + "\tPumpDevice =\t$(params["PumpDevice"])\n", + "\tSyncWithPump =\t$(params["SyncWithPump"])\n", + "\tPump_Pressure.Step =\t$(params["Pump_Pressure.Step"]) [s]\n", + "\tPump_Pressure.Average =\t$(params["Pump_Pressure.Average"])\n", + "\tCurve =\t$(params["Curve"])\n\n", + + "\tFlow =\t$(params["Flow"]) [ml/min]\n\n", + + "\t; Wait for the Ready signals from the pump and autsosampler\n", + " 0.000\tWait\tPump.Ready and Sampler.Ready and PumpModule.Ready\n\n", + + "\tInjectMode =\tUserProg\n\n", + + "\t; Wait for the stimulus input to have a Low signal, to prevent trigger of two injections from a single signal\n", + "\tUdpWaitInput\tInput=Inp1, State=Low\n\n", + + "\t; Preflush the injection needle\n", + "\tUdpInjectValve\tPosition=Inject\n", + "\tUdpSyringeValve\tPosition=Needle\n", + "\tUdpDraw\tFrom=SampleVial, Volume=$(params["PreflushVolume"]), SyringeSpeed=GlobalSpeed, SampleHeight=Globalheight\n", + "\tUdpMixWait\tDuration=$(parse(Float64, params["DrawDelay"])/1000) ; Pause to avoid air intake from aspirating the sample too quickly\n\n", + + "\t; Fill the Sample Loop with the plate position and volume specified by the sequence file\n", + "\tUdpInjectValve\tPosition=Load\n", + "\tUdpDraw\tFrom=SampleVial, Volume=Volume, SyringeSpeed=GlobalSpeed, SampleHeight=Globalheight\n", + "\tUdpMixWait\tDuration=$(parse(Float64, params["DrawDelay"])/1000) ; Pause to avoid air intake from aspirating the sample too quickly\n\n", + + "\t; Wait for the signal from Image before performing the injection\n", + "\tUdpWaitInput\tInput=Inp1, State=High\n\n", + + "\t; Inject the sample and signal to Chromeleon that the injection has been performed\n", + "\tUdpInjectValve\tPosition=Inject\n", + "\tUdpInjectMarker\n\n", + + "\t; Send timestamp signal to imagine\n", + "\tRelay_4.State\tOn\n\n", + + "\t; Start recording the pump pressure (not used for optical records, but I'm not sure if it is safe to omit this step)\n", + "\tPump_Pressure.AcqOn\n\n", + + "\t; Wash the needle\n", + "\tUdpSyringeValve\tPosition=Waste\n", + "\tUdpMoveSyringeHome\tSyringeSpeed=GlobalSpeed\n", + "\tUdpMixNeedleWash\tVolume=$(params["WashVolume"])\n\n", + + " $(params["Delay"])\tPump_Pressure.AcqOff ; Stop pressure acquisition\n", + "\t; Note the time of the above command (in minutes).\n", + "\t; This time interval may be important for fully emptying/washing the Sample Loop.\n", + "\t; If set too long, it might also cause Chromeleon to \"miss\" a signal from Imagine.\n\n", + + "\t; Check your flowrate for the pump (\"Flow\" above), sample volume in your sequence file,\n", + "\t; and inter-trial delay for your Imagine waveforms to avoid issues.\n\n", + + "\t; Turn off Relay 4\n", + "\tRelay_4.State\tOff\n\n", + + "\tEnd\n", + ) + flush(io) +end + diff --git a/src/seqfile.jl b/src/seqfile.jl new file mode 100644 index 0000000..15ce01d --- /dev/null +++ b/src/seqfile.jl @@ -0,0 +1,97 @@ +""" + stim_randomize(filein, trials::Int, fileout=insertfilename(filein, "_sequence")) + +Generate a randomize sequence of stimuli. `filein` is a string containing the name +of a CSV file that describes the unique stimuli (see format described in the README). +`trials` specifies the number of pseudo-random sequences used to generate the +final stimulus sequence. +`fileout` is an optional argument used to control the name of the output file; +the default is to insert "_sequence" right before the extension of `filein`. +""" +function stim_randomize(filein::AbstractString, trials::Int; fileout=insertfilename(filein, "_sequence"), kwargs...) + open(fileout, "w") do io + stim_randomize(io, filein, trials; kwargs...) + end + return fileout +end + +function stim_randomize(io::IO, filein::AbstractString, trials::Int; header=false, kwargs...) + df = CSV.File(filein) |> DataFrame + println("Headers found: ", String.(names(df))) + n = size(df, 1) + println(n, " stimuli found") + # $ is a disallowed character since we will use it as a separator + for i = 1:n + occursin('\$', df[i,1]) && error("\$ is disallowed in stimulus names") + end + p = Int[] + for i = 1:trials + append!(p, randperm(n)) + end + CSV.write(io, df[p, :]; header=header, kwargs...) + flush(io) +end + +function update_imagine(imaginefile, sequencefile; um_per_pixel=nothing, aifile=replaceext(imaginefile, ".ai"), difile=replaceext(imaginefile, ".di"), updatedfile=imaginefile, csvheader=0, kwargs...) + header = ImagineFormat.parse_header(imaginefile) + haskey(header, "stimulus sequence") && error(imaginefile, " already has a `stimulus sequence` entry") + if um_per_pixel === nothing + um_per_pixel = header["um per pixel"] + end + um_per_pixel < 0 && error("must specify um_per_pixel") + header["um per pixel"] = um_per_pixel + + df = CSV.File(sequencefile; header=csvheader, kwargs...) |> DataFrame + n = size(df, 1) + + # Scan the AI file for stimulus triggers + ai = parse_ai(aifile, header) + aistim = getname(ai, "stimuli") + stimhi = find_pulse_starts(aistim; sampmap=:volts) + stimlo = find_pulse_stops(aistim; sampmap=:volts) + if occursin("camera frame TTL", header["label list"]) + aiframe = getname(ai, "camera frame TTL") + framestarts = find_pulse_starts(aiframe; sampmap=:volts) + elseif occursin("camera1 frame monitor", header["di label list"]) + di = parse_di(difile, header) + diframe = getname(di, "camera1 frame monitor") + framestarts = find_pulse_starts(diframe) #; sampmap=:volts) + end + + # Record as frameidx after the stimulus trigger + fps = header["frames per stack"] + framehi, framelo = Int[], Int[] + for (frameidxs, scanidxs) in ((framehi, stimhi), (framelo, stimlo)) + for i in scanidxs + idx = last(searchsorted(framestarts, i)) + push!(frameidxs, idx) + end + end + + # Add to header + header["stimulus sequence"] = join(df[:,1], '\$') + header["stimulus scan hi"] = stimhi + header["stimulus scan lo"] = stimlo + header["stimulus frame hi"] = framehi + header["stimulus frame lo"] = framelo + + if updatedfile == imaginefile + mv(imaginefile, imaginefile*".orig") + sleep(0.25) + isfile(imaginefile) && error("failed to move the old file") + end + ImagineFormat.save_header(updatedfile, header; misc=( + "stimulus sequence", "stimulus scan hi", "stimulus scan lo", + "stimulus frame hi", "stimulus frame lo")) + return header +end + +function insertfilename(filename, tail) + basename, ext = splitext(filename) + return basename*tail*ext +end + +function replaceext(filename, ext) + basename, _ = splitext(filename) + return basename*ext +end From 313d37857a6397cc58a6d1450bb0710b65b3f28d Mon Sep 17 00:00:00 2001 From: Tom McGrath Date: Thu, 29 Jun 2023 20:53:30 -0500 Subject: [PATCH 2/3] fix omissions, add clarity to output .pgm --- src/progfile.jl | 167 ++++++++++++++++++++++++++---------------------- 1 file changed, 89 insertions(+), 78 deletions(-) diff --git a/src/progfile.jl b/src/progfile.jl index 7e98f68..20ad02a 100644 --- a/src/progfile.jl +++ b/src/progfile.jl @@ -54,85 +54,96 @@ function chromeleon_program(fileout::AbstractString, params::Dict{String,String} end function chromeleon_program(io::IO, params=default_params) + # Newlines use the \r\n convention for compatibility with Windows/Notepad write(io, - "\tTempCtrl =\t$(params["TempCtrl"])\n", - "\tTemperature.Nominal =\t$(params["TemperatureNominal"]) [°C]\n", - "\tTemperature.LowerLimit =\t$(params["TemperatureLowerLimit"]) [°C]\n", - "\tTemperature.UpperLimit =\t$(params["TemperatureUpperLimit"]) [°C]\n", - "\tReadyTempDelta =\t$(params["ReadyTempDelta"]) [°C]\n", - "\tPressure.LowerLimit =\t$(params["PressureLowerLimit"]) [bar]\n", - "\tPressure.UpperLimit =\t$(params["PressureUpperLimit"]) [bar]\n", - "\tMaximumFlowRampDown =\t$(params["MaximumFlowRampDown"]) [ml/min²]\n", - "\tMaximumFlowRampUp =\t$(params["MaximumFlowRampUp"]) [ml/min²]\n", - "\t%A.Equate =\t$(params["%A.Equate"])\n", - "\tDrawSpeed =\t$(params["DrawSpeed"]) [µl/s]\n", - "\tDrawDelay =\t$(params["DrawDelay"]) [ms]\n", - "\tDispSpeed =\t$(params["DispSpeed"]) [µl/s]\n", - "\tDispenseDelay =\t$(params["DispenseDelay"]) [ms]\n", - "\tWasteSpeed =\t$(params["WasteSpeed"]) [µl/s]\n", - "\tSampleHeight =\t$(params["SampleHeight"]) [mm]\n", - "\tInjectWash =\t$(params["InjectWash"])\n", - "\tWashVolume =\t$(params["WashVolume"]) [µl]\n", - "\tWashSpeed =\t$(params["WashSpeed"]) [µl/s]\n", - "\tLoopWashFactor =\t$(params["LoopWashFactor"])\n", - "\tPunctureOffset =\t$(params["PunctureOffset"]) [mm]\n", - "\tPumpDevice =\t$(params["PumpDevice"])\n", - "\tSyncWithPump =\t$(params["SyncWithPump"])\n", - "\tPump_Pressure.Step =\t$(params["Pump_Pressure.Step"]) [s]\n", - "\tPump_Pressure.Average =\t$(params["Pump_Pressure.Average"])\n", - "\tCurve =\t$(params["Curve"])\n\n", - - "\tFlow =\t$(params["Flow"]) [ml/min]\n\n", - - "\t; Wait for the Ready signals from the pump and autsosampler\n", - " 0.000\tWait\tPump.Ready and Sampler.Ready and PumpModule.Ready\n\n", - - "\tInjectMode =\tUserProg\n\n", - - "\t; Wait for the stimulus input to have a Low signal, to prevent trigger of two injections from a single signal\n", - "\tUdpWaitInput\tInput=Inp1, State=Low\n\n", - - "\t; Preflush the injection needle\n", - "\tUdpInjectValve\tPosition=Inject\n", - "\tUdpSyringeValve\tPosition=Needle\n", - "\tUdpDraw\tFrom=SampleVial, Volume=$(params["PreflushVolume"]), SyringeSpeed=GlobalSpeed, SampleHeight=Globalheight\n", - "\tUdpMixWait\tDuration=$(parse(Float64, params["DrawDelay"])/1000) ; Pause to avoid air intake from aspirating the sample too quickly\n\n", - - "\t; Fill the Sample Loop with the plate position and volume specified by the sequence file\n", - "\tUdpInjectValve\tPosition=Load\n", - "\tUdpDraw\tFrom=SampleVial, Volume=Volume, SyringeSpeed=GlobalSpeed, SampleHeight=Globalheight\n", - "\tUdpMixWait\tDuration=$(parse(Float64, params["DrawDelay"])/1000) ; Pause to avoid air intake from aspirating the sample too quickly\n\n", - - "\t; Wait for the signal from Image before performing the injection\n", - "\tUdpWaitInput\tInput=Inp1, State=High\n\n", - - "\t; Inject the sample and signal to Chromeleon that the injection has been performed\n", - "\tUdpInjectValve\tPosition=Inject\n", - "\tUdpInjectMarker\n\n", - - "\t; Send timestamp signal to imagine\n", - "\tRelay_4.State\tOn\n\n", - - "\t; Start recording the pump pressure (not used for optical records, but I'm not sure if it is safe to omit this step)\n", - "\tPump_Pressure.AcqOn\n\n", - - "\t; Wash the needle\n", - "\tUdpSyringeValve\tPosition=Waste\n", - "\tUdpMoveSyringeHome\tSyringeSpeed=GlobalSpeed\n", - "\tUdpMixNeedleWash\tVolume=$(params["WashVolume"])\n\n", - - " $(params["Delay"])\tPump_Pressure.AcqOff ; Stop pressure acquisition\n", - "\t; Note the time of the above command (in minutes).\n", - "\t; This time interval may be important for fully emptying/washing the Sample Loop.\n", - "\t; If set too long, it might also cause Chromeleon to \"miss\" a signal from Imagine.\n\n", - - "\t; Check your flowrate for the pump (\"Flow\" above), sample volume in your sequence file,\n", - "\t; and inter-trial delay for your Imagine waveforms to avoid issues.\n\n", - - "\t; Turn off Relay 4\n", - "\tRelay_4.State\tOff\n\n", - - "\tEnd\n", + "\tTempCtrl =\t$(params["TempCtrl"])\r\n", + "\tTemperature.Nominal =\t$(params["TemperatureNominal"]) [°C]\r\n", + "\tTemperature.LowerLimit =\t$(params["TemperatureLowerLimit"]) [°C]\r\n", + "\tTemperature.UpperLimit =\t$(params["TemperatureUpperLimit"]) [°C]\r\n", + "\tReadyTempDelta =\t$(params["ReadyTempDelta"]) [°C]\r\n", + "\tPressure.LowerLimit =\t$(params["PressureLowerLimit"]) [bar]\r\n", + "\tPressure.UpperLimit =\t$(params["PressureUpperLimit"]) [bar]\r\n", + "\tMaximumFlowRampDown =\t$(params["MaximumFlowRampDown"]) [ml/min²]\r\n", + "\tMaximumFlowRampUp =\t$(params["MaximumFlowRampUp"]) [ml/min²]\r\n", + "\t%A.Equate =\t$(params["%A.Equate"])\r\n", + "\tDrawSpeed =\t$(params["DrawSpeed"]) [µl/s]\r\n", + "\tDrawDelay =\t$(params["DrawDelay"]) [ms]\r\n", + "\tDispSpeed =\t$(params["DispSpeed"]) [µl/s]\r\n", + "\tDispenseDelay =\t$(params["DispenseDelay"]) [ms]\r\n", + "\tWasteSpeed =\t$(params["WasteSpeed"]) [µl/s]\r\n", + "\tSampleHeight =\t$(params["SampleHeight"]) [mm]\r\n", + "\tInjectWash =\t$(params["InjectWash"])\r\n", + "\tWashVolume =\t$(params["WashVolume"]) [µl]\r\n", + "\tWashSpeed =\t$(params["WashSpeed"]) [µl/s]\r\n", + "\tLoopWashFactor =\t$(params["LoopWashFactor"])\r\n", + "\tPunctureOffset =\t$(params["PunctureOffset"]) [mm]\r\n", + "\tPumpDevice =\t$(params["PumpDevice"])\r\n", + "\tSyncWithPump =\t$(params["SyncWithPump"])\r\n", + "\tPump_Pressure.Step =\t$(params["Pump_Pressure.Step"]) [s]\r\n", + "\tPump_Pressure.Average =\t$(params["Pump_Pressure.Average"])\r\n", + "\tCurve =\t$(params["Curve"])\r\n\r\n", + + "\tFlow =\t$(params["Flow"]) [ml/min]\r\n\r\n", + + "\t; Wait for the Ready signals from the pump and autsosampler\r\n", + " 0.000\tWait\tPump.Ready and Sampler.Ready and PumpModule.Ready\r\n\r\n", + + "\tInjectMode =\tUserProg\r\n\r\n", + + "\tThe following section of code (containing all beginning with 'Udp') is not evaluated by Chromeleon as it is read.\r\n", + "\tInstead, the commands are carried out when the 'Inject' command is read.\r\n\r\n", + + "\t; USER DEFINED INJECTION STARTS HERE\r\n\r\n", + + "\t; Wait for the stimulus input to have a Low signal, to prevent trigger of two injections from a single signal\r\n", + "\tUdpWaitInput\tInput=Inp1, State=Low\r\n\r\n", + + "\t; Preflush the injection needle\r\n", + "\tUdpInjectValve\tPosition=Inject\r\n", + "\tUdpSyringeValve\tPosition=Needle\r\n", + "\tUdpDraw\tFrom=SampleVial, Volume=$(params["PreflushVolume"]), SyringeSpeed=GlobalSpeed, SampleHeight=Globalheight\r\n", + "\tUdpMixWait\tDuration=$(parse(Float64, params["DrawDelay"])/1000) ; Pause to avoid air intake from aspirating the sample too quickly\r\n\r\n", + + "\t; Fill the Sample Loop with the plate position and volume specified by the sequence file\r\n", + "\tUdpInjectValve\tPosition=Load\r\n", + "\tUdpDraw\tFrom=SampleVial, Volume=Volume, SyringeSpeed=GlobalSpeed, SampleHeight=Globalheight\r\n", + "\tUdpMixWait\tDuration=$(parse(Float64, params["DrawDelay"])/1000) ; Pause to avoid air intake from aspirating the sample too quickly\r\n\r\n", + + "\t; Wait for the signal from Image before performing the injection\r\n", + "\tUdpWaitInput\tInput=Inp1, State=High\r\n\r\n", + + "\t; Inject the sample and signal to Chromeleon that the injection has been performed\r\n", + "\tUdpInjectValve\tPosition=Inject\r\n", + "\tUdpInjectMarker\r\n\r\n", + + "\t; Wash the needle\r\n", + "\tUdpSyringeValve\tPosition=Waste\r\n", + "\tUdpMoveSyringeHome\tSyringeSpeed=GlobalSpeed\r\n", + "\tUdpMixNeedleWash\tVolume=$(params["WashVolume"])\r\n\r\n", + + "\t; USER DEFINED INJECTION ENDS HERE\r\n\r\n", + + "\t; Perform the user-defined injection (see above)\r\n", + "\tInject\r\n\r\n", + + "\t; Send timestamp signal to imagine\r\n", + "\tRelay_4.State\tOn\r\n\r\n", + + "\t; Start recording the pump pressure (not used for optical records, but I'm not sure if it is safe to omit this step)\r\n", + "\tPump_Pressure.AcqOn\r\n\r\n", + + " $(params["Delay"])\tPump_Pressure.AcqOff ; Stop pressure acquisition\r\n", + "\t; Note the time of the above command (in minutes).\r\n", + "\t; This time interval may be important for fully emptying/washing the Sample Loop.\r\n", + "\t; If set too long, it might also cause Chromeleon to \"miss\" a signal from Imagine.\r\n\r\n", + + "\t; Check your flowrate for the pump (\"Flow\" above), sample volume in your sequence file,\r\n", + "\t; and inter-trial delay for your Imagine waveforms to avoid issues.\r\n\r\n", + + "\t; Turn off Relay 4\r\n", + "\tRelay_4.State\tOff\r\n\r\n", + + "\tEnd\r\n", ) flush(io) end From 10e07b70481944bbb5f8ab05d74bc4886f7254ab Mon Sep 17 00:00:00 2001 From: Tom McGrath Date: Fri, 30 Jun 2023 11:12:48 -0500 Subject: [PATCH 3/3] fix quotes/comments in generated .pgm --- src/progfile.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/progfile.jl b/src/progfile.jl index 20ad02a..15ea8de 100644 --- a/src/progfile.jl +++ b/src/progfile.jl @@ -16,7 +16,7 @@ const default_params = Dict{String,String}( "PressureUpperLimit" => "350", # [bar] "MaximumFlowRampDown" => "6.000", # [ml/min²] "MaximumFlowRampUp" => "6.000", # [ml/min²] - "%A.Equate" => "%A", + "%A.Equate" => "\"%A\"", "DrawSpeed" => "10.000", # [µl/s] "DrawDelay" => "1000", # [ms] "DispSpeed" => "20.000", # [µl/s] @@ -90,8 +90,8 @@ function chromeleon_program(io::IO, params=default_params) "\tInjectMode =\tUserProg\r\n\r\n", - "\tThe following section of code (containing all beginning with 'Udp') is not evaluated by Chromeleon as it is read.\r\n", - "\tInstead, the commands are carried out when the 'Inject' command is read.\r\n\r\n", + "\t; The following section of code (containing all beginning with 'Udp') is not evaluated by Chromeleon as it is read.\r\n", + "\t; Instead, the commands are carried out when the 'Inject' command is read.\r\n\r\n", "\t; USER DEFINED INJECTION STARTS HERE\r\n\r\n",