Skip to content

Commit b84c92a

Browse files
authored
Merge pull request #105 from drbergman/import-export-intracellular
support import/export of intracellulars
2 parents f4290d4 + 7d16bc4 commit b84c92a

File tree

6 files changed

+159
-29
lines changed

6 files changed

+159
-29
lines changed

src/VCTComponents.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function assembleIntracellular!(cell_to_components_dict::Dict{String,<:Union{Phy
4646
return assembleIntracellular!(cell_to_vec_components_dict; kwargs...)
4747
end
4848

49-
function assembleIntracellular!(cell_to_components_dict::Dict{String,Vector{PhysiCellComponent}}; name::String="assembled")
49+
function assembleIntracellular!(cell_to_components_dict::Dict{String,Vector{PhysiCellComponent}}; name::String="assembled", skip_db_insert::Bool=false)
5050
#! get all components to assign IDs
5151
unique_components = PhysiCellComponent[]
5252
for components in values(cell_to_components_dict)
@@ -145,7 +145,7 @@ function assembleIntracellular!(cell_to_components_dict::Dict{String,Vector{Phys
145145
end
146146

147147
#! make sure the database is updated, variations.db intialized
148-
if initialized
148+
if initialized && !skip_db_insert
149149
insertFolder(:intracellular, splitpath(folder)[end])
150150
end
151151

src/VCTConfiguration.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ end
2222
function retrieveElement(xml_doc::XMLDocument, xml_path::Vector{<:AbstractString}; required::Bool=true)
2323
current_element = root(xml_doc)
2424
for path_element in xml_path
25-
if !occursin(":",path_element)
25+
if !occursin(":", path_element)
2626
current_element = find_element(current_element, path_element)
2727
if isnothing(current_element)
2828
required ? retrieveElementError(xml_path, path_element) : return nothing

src/VCTExport.jl

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@ function prepareFolder(simulation::Simulation, export_folder::AbstractString)
7171

7272
#! intracellulars
7373
if row.intracellular_id[1] != -1
74-
path_to_intracellular = joinpath(locationPath(:intracellular, simulation), "intracellular.xml")
75-
cp(path_to_intracellular, joinpath(export_folder, "config", "intracellular.xml"))
74+
exportIntracellular(simulation, export_folder)
7675
end
7776

7877
#! ic cells
@@ -125,6 +124,52 @@ function prepareFolder(simulation::Simulation, export_folder::AbstractString)
125124
return revertSimulationFolderToCurrentPhysiCell(export_folder, physicell_version), physicell_version
126125
end
127126

127+
function exportIntracellular(simulation::Simulation, export_folder::AbstractString)
128+
path_to_intracellular = joinpath(locationPath(:intracellular, simulation), "intracellular.xml")
129+
xml_doc = openXML(path_to_intracellular)
130+
intracellulars_element = retrieveElement(xml_doc, ["intracellulars"])
131+
intracellular_mapping = Dict{String,Tuple{String,String}}()
132+
for intracellular_element in child_elements(intracellulars_element)
133+
intracellular_id = attribute(intracellular_element, "ID")
134+
intracellular_type = attribute(intracellular_element, "type")
135+
new_root = child_elements(intracellular_element) |> first
136+
new_xml_doc = XMLDocument()
137+
set_root(new_xml_doc, new_root)
138+
path_end = joinpath("config", "intracellular_$(intracellular_type)_$(intracellular_id).xml")
139+
new_path = joinpath(export_folder, path_end)
140+
save_file(new_xml_doc, new_path)
141+
closeXML(new_xml_doc)
142+
intracellular_mapping[intracellular_id] = (intracellular_type, path_end)
143+
end
144+
145+
config_xml = openXML(joinpath(export_folder, "config", "PhysiCell_settings.xml"))
146+
147+
cell_definitions_element = retrieveElement(xml_doc, ["cell_definitions"])
148+
for cell_definition_element in child_elements(cell_definitions_element)
149+
if name(cell_definition_element) != "cell_definition"
150+
continue
151+
end
152+
cell_type = attribute(cell_definition_element, "name")
153+
intracellular_ids_element = find_element(cell_definition_element, "intracellular_ids")
154+
ID_elements = get_elements_by_tagname(intracellular_ids_element, "ID")
155+
@assert length(ID_elements) <= 1 "Do not (yet?) support multiple intracellular models for a single cell type."
156+
intracellular_id = ID_elements |> first |> content
157+
config_cell_def_intracellular_element = retrieveElement(config_xml, ["cell_definitions", "cell_definition:name:$(cell_type)", "phenotype", "intracellular"])
158+
set_attribute(config_cell_def_intracellular_element, "type", intracellular_mapping[intracellular_id][1])
159+
160+
#! get (or create) the sbml_filename element
161+
sbml_filename_element = find_element(config_cell_def_intracellular_element, "sbml_filename")
162+
if isnothing(sbml_filename_element)
163+
sbml_filename_element = new_child(config_cell_def_intracellular_element, "sbml_filename")
164+
end
165+
set_content(sbml_filename_element, intracellular_mapping[intracellular_id][2])
166+
end
167+
168+
closeXML(config_xml)
169+
closeXML(xml_doc)
170+
return
171+
end
172+
128173
function revertSimulationFolderToCurrentPhysiCell(export_folder::AbstractString, physicell_version::AbstractString)
129174
success = revertMain(export_folder, physicell_version)
130175
success &= revertMakefile(export_folder, physicell_version)
@@ -137,7 +182,9 @@ function revertMain(export_folder::AbstractString, physicell_version::AbstractSt
137182
path_to_main = joinpath(export_folder, "main.cpp")
138183
lines = readlines(path_to_main)
139184
idx = findfirst(x -> contains(x, "<getopt.h>"), lines)
140-
popat!(lines, idx)
185+
if !isnothing(idx)
186+
popat!(lines, idx)
187+
end
141188

142189
idx1 = findfirst(x -> contains(x, "// read arguments"), lines)
143190
if isnothing(idx1)
@@ -245,8 +292,9 @@ function revertConfig(export_folder::AbstractString, physicell_version::Abstract
245292
set_content(filename_element, "cell_rules.csv")
246293

247294
#! intracellulars
248-
#! lol, not supported for export yet
295+
#! handled in exportIntracellular
249296

297+
save_file(xml_doc, path_to_config)
250298
closeXML(xml_doc)
251299
return true
252300
end

src/VCTImport.jl

Lines changed: 93 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ struct ImportSources
3333
ic_dc::ImportSource
3434
end
3535

36-
function ImportSources(src::Dict)
36+
function ImportSources(src::Dict, path_to_project::AbstractString)
3737
required = true
3838
config = ImportSource(src, "config", "config", "PhysiCell_settings.xml", "file", required)
3939
main = ImportSource(src, "main", "", "main.cpp", "file", required; input_folder_key = :custom_code)
@@ -42,14 +42,82 @@ function ImportSources(src::Dict)
4242

4343
required = false
4444
rules = ImportSource(src, "rules", "config", "cell_rules.csv", "file", required; pcvct_name="base_rulesets.csv")
45-
intracellular = ImportSource(src, "intracellular", "config", "intracellular.xml", "file", required)
45+
intracellular = prepareIntracellularImport(src, config, path_to_project)
4646
ic_cell = ImportSource(src, "ic_cell", "config", "cells.csv", "file", required)
4747
ic_substrate = ImportSource(src, "ic_substrate", "config", "substrates.csv", "file", required)
4848
ic_ecm = ImportSource(src, "ic_ecm", "config", "ecm.csv", "file", required)
4949
ic_dc = ImportSource(src, "ic_dc", "config", "dcs.csv", "file", required)
5050
return ImportSources(config, main, makefile, custom_modules, rules, intracellular, ic_cell, ic_substrate, ic_ecm, ic_dc)
5151
end
5252

53+
function prepareIntracellularImport(src::Dict, config::ImportSource, path_to_project::AbstractString)
54+
if haskey(src, "intracellular") || isfile(joinpath(path_to_project, "config", "intracellular.xml"))
55+
return ImportSource(src, "intracellular", "config", "intracellular.xml", "file", true)
56+
end
57+
#! now attempt to read the config file and assemble the intracellular file
58+
path_to_xml = joinpath(path_to_project, config.path_from_project)
59+
if !isfile(path_to_xml) #! if the config file is not found, then we cannot proceed with grabbing the intracellular data, just return the default
60+
return ImportSource(src, "intracellular", "config", "intracellular.xml", "file", false)
61+
end
62+
xml_doc = openXML(path_to_xml)
63+
cell_definitions_element = retrieveElement(xml_doc, ["cell_definitions"])
64+
cell_type_to_components_dict = Dict{String,PhysiCellComponent}()
65+
for cell_definition_element in child_elements(cell_definitions_element)
66+
if name(cell_definition_element) != "cell_definition"
67+
continue
68+
end
69+
cell_type = attribute(cell_definition_element, "name")
70+
phenotype_element = find_element(cell_definition_element, "phenotype")
71+
intracellular_element = find_element(phenotype_element, "intracellular")
72+
if isnothing(intracellular_element)
73+
continue
74+
end
75+
type = attribute(intracellular_element, "type")
76+
if type ["roadrunner", "dfba"]
77+
throw(ErrorException("pcvct does not yet support intracellular type $type."))
78+
end
79+
path_to_file = find_element(intracellular_element, "sbml_filename") |> content
80+
temp_component = PhysiCellComponent(type, basename(path_to_file))
81+
#! now we have to rely on the path to the file is correct relative to the parent directory of the config file (that should usually be the case)
82+
path_to_src = joinpath(path_to_project, path_to_file)
83+
path_to_dest = _create_component_dest_file_name(readlines(path_to_src), temp_component)
84+
component = PhysiCellComponent(type, basename(path_to_dest))
85+
if !isfile(path_to_dest)
86+
cp(path_to_src, path_to_dest)
87+
end
88+
89+
cell_type_to_components_dict[cell_type] = component
90+
end
91+
92+
if isempty(cell_type_to_components_dict)
93+
return ImportSource(src, "intracellular", "config", "intracellular.xml", "file", false)
94+
end
95+
96+
intracellular_folder = assembleIntracellular!(cell_type_to_components_dict; name="temp_assembled_from_$(splitpath(path_to_project)[end])", skip_db_insert=true)
97+
mv(joinpath(locationPath(:intracellular, intracellular_folder), "intracellular.xml"), joinpath(path_to_project, "config", "assembled_intracellular_for_import.xml"); force=true)
98+
rm(locationPath(:intracellular, intracellular_folder); force=true, recursive=true)
99+
100+
closeXML(xml_doc)
101+
return ImportSource(src, "intracellular", "config", "assembled_intracellular_for_import.xml", "file", true; pcvct_name="intracellular.xml")
102+
end
103+
104+
function _create_component_dest_file_name(src_lines::Vector{String}, component::PhysiCellComponent)
105+
base_path = joinpath(data_dir, "components", component.path_from_components)
106+
folder = dirname(base_path)
107+
mkpath(folder)
108+
base_filename, file_ext = basename(base_path) |> splitext
109+
n = 0
110+
path_to_dest = joinpath(folder, base_filename * file_ext)
111+
while isfile(path_to_dest)
112+
if src_lines == readlines(path_to_dest)
113+
return path_to_dest
114+
end
115+
n += 1
116+
path_to_dest = joinpath(folder, base_filename * "_$(n)" * file_ext)
117+
end
118+
return path_to_dest
119+
end
120+
53121
mutable struct ImportDestFolder
54122
path_from_inputs::AbstractString
55123
created::Bool
@@ -79,7 +147,7 @@ function ImportDestFolders(path_to_project::AbstractString, dest::Dict)
79147

80148
#! optional folders
81149
rules = ImportDestFolder(path_fn("rules", "rulesets_collections"), created, description)
82-
intracellular = ImportDestFolder(path_fn("intracellular", "intracellular"), created, description)
150+
intracellular = ImportDestFolder(path_fn("intracellular", "intracellulars"), created, description)
83151
ic_cell = ImportDestFolder(path_fn("ic_cell", joinpath("ics", "cells")), created, description)
84152
ic_substrate = ImportDestFolder(path_fn("ic_substrate", joinpath("ics", "substrates")), created, description)
85153
ic_ecm = ImportDestFolder(path_fn("ic_ecm", joinpath("ics", "ecms")), created, description)
@@ -101,7 +169,7 @@ The following keys are recognized: $(join(["`$fn`" for fn in fieldnames(ImportDe
101169
- `extreme_caution::Bool`: If true, will ask for confirmation before deleting any folders created during the import process. Care has been taken to ensure this is unnecessary. Provided for users who want to be extra cautious.
102170
"""
103171
function importProject(path_to_project::AbstractString, src=Dict(), dest=Dict(); extreme_caution::Bool=false)
104-
project_sources = ImportSources(src)
172+
project_sources = ImportSources(src, path_to_project)
105173
import_dest_folders = ImportDestFolders(path_to_project, dest)
106174
success = resolveProjectSources!(project_sources, path_to_project)
107175
if success
@@ -299,9 +367,6 @@ function adaptMain(path_from_inputs::AbstractString)
299367
return true
300368
end
301369

302-
idx = findfirst(x->contains(x, "<fstream>"), lines)
303-
insert!(lines, idx+1, "#include <getopt.h>")
304-
305370
idx1 = findfirst(x->contains(x, "// load and parse settings file(s)"), lines)
306371
if isnothing(idx1)
307372
idx1 = findfirst(x->contains(x, "bool XML_status = false;"), lines)
@@ -314,19 +379,27 @@ function adaptMain(path_from_inputs::AbstractString)
314379
return false
315380
end
316381
end
317-
idx2 = findfirst(x -> contains(x, "// copy config file to"), lines)
318-
if isnothing(idx2)
319-
idx2 = findfirst(x -> contains(x, "system(") && contains(x, "copy_command"), lines)
320-
if isnothing(idx2)
321-
msg = """
322-
Could not identify where the copy command is in the main.cpp file.
323-
Aborting the export process.
324-
"""
325-
println(msg)
326-
return false
327-
end
328-
end
329-
deleteat!(lines, idx1:(idx2-1))
382+
idx_not_xml_status = findfirst(x->contains(x, "!XML_status"), lines)
383+
idx2 = idx_not_xml_status + findfirst(x -> contains(x, "}"), lines[idx_not_xml_status:end]) - 1
384+
# idx2 = findfirst(x -> contains(x, "// copy config file to"), lines)
385+
# if isnothing(idx2)
386+
# idx2 = findfirst(x -> contains(x, "system(") && contains(x, "copy_command"), lines)
387+
# if isnothing(idx2)
388+
# idx2 = findfirst(x -> contains(x, "// OpenMP setup"), lines)
389+
# if isnothing(idx2)
390+
# idx2 = findfirst(x -> contains(x, "omp_set_num_threads("), lines)
391+
# if isnothing(idx2)
392+
# msg = """
393+
# Could not identify where the copy command is in the main.cpp file.
394+
# Aborting the export process.
395+
# """
396+
# println(msg)
397+
# return false
398+
# end
399+
# end
400+
# end
401+
# end
402+
deleteat!(lines, idx1:idx2)
330403

331404
parsing_block = """
332405
// read arguments

test/test-scripts/ImportTests.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,12 @@ success = importProject(path_to_project)
8686

8787
# import the combined sbml project
8888
path_to_project = joinpath("PhysiCell", "sample_projects_intracellular", "combined", "template-combined")
89+
src = Dict("intracellular" => "sample_combined_sbmls.xml")
90+
success = importProject(path_to_project, src)
91+
@test success
92+
93+
path_to_project = joinpath("PhysiCell", "sample_projects_intracellular", "ode", "ode_energy")
94+
success = importProject(path_to_project)
95+
@test success
8996
success = importProject(path_to_project)
9097
@test success

test/test-scripts/IntracellularTests.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,7 @@ macros_lines = pcvct.readMacrosFile(out.trial)
1919
@test "ADDON_ROADRUNNER" in macros_lines
2020

2121
#! more test coverage
22-
intracellular = assembleIntracellular!(cell_to_components_dict; name="template-combined")
23-
@test intracellular == "template-combined" #! should not need to make a new folder, the assembly.toml file should show they match
22+
new_intracellular = assembleIntracellular!(cell_to_components_dict; name="template-combined")
23+
@test intracellular == new_intracellular #! should not need to make a new folder, the assembly.toml file should show they match
24+
25+
export_folder = out |> getSimulationIDs |> first |> exportSimulation

0 commit comments

Comments
 (0)