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
12 changes: 12 additions & 0 deletions config/PhysiCell_settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@
<SVG>
<interval units="min">60</interval>
<enable>true</enable>
<!-- Optional: override SVG colors for specific cell types.
Names must match cell_definition names exactly; an unrecognized name is an error.
Partial coverage is fine: unlisted cell types are assigned colors automatically
from the built-in palette (with any user-claimed colors skipped), then generated
via golden-angle HSV spacing once the palette is exhausted.
If this element is absent entirely, all colors are assigned automatically.
Accepted color formats: CSS named colors (e.g. "red"), #rrggbb/#rgb hex, rgb(r,g,b).
<cell_colors>
<cell_color name="cancer cell">red</cell_color>
<cell_color name="immune cell">cyan</cell_color>
</cell_colors>
-->
</SVG>
<legacy_data>
<enable>false</enable>
Expand Down
211 changes: 184 additions & 27 deletions modules/PhysiCell_pathology.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1707,40 +1707,197 @@ std::string paint_by_density_percentage(double concentration, double max_conc, d
return colormap[ind];
}

// Returns true if the string is a valid SVG/CSS color:
// - CSS named color (case-insensitive)
// - #rgb or #rrggbb hex notation
// - rgb(r,g,b) functional notation (integers 0-255)
static bool is_valid_svg_color( const std::string& s )
{
if( s.empty() ) return false;

// #rgb / #rrggbb
if( s[0] == '#' )
{
if( s.size() != 4 && s.size() != 7 ) return false;
for( size_t i = 1; i < s.size(); i++ )
{
char c = s[i];
if( !( (c>='0'&&c<='9') || (c>='a'&&c<='f') || (c>='A'&&c<='F') ) )
return false;
}
return true;
}

// rgb(r,g,b) — accept optional whitespace, integers 0-255
if( s.size() > 4 && s.substr(0,4) == "rgb(" && s.back() == ')' )
{
std::string inner = s.substr(4, s.size()-5);
// parse three comma-separated integers
int vals[3]; int nread;
char extra;
nread = std::sscanf(inner.c_str(), " %d , %d , %d %c", &vals[0], &vals[1], &vals[2], &extra);
if( nread != 3 ) return false;
for( int i = 0; i < 3; i++ )
if( vals[i] < 0 || vals[i] > 255 ) return false;
return true;
}

// CSS named colors (standard 147 + rebeccapurple)
static const std::unordered_set<std::string> named = {
"aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black",
"blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse",
"chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan",
"darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta",
"darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen",
"darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink",
"deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen",
"fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","green","greenyellow",
"grey","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender",
"lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan",
"lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon",
"lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue",
"lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine",
"mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue",
"mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream",
"mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange",
"orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred",
"papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple",
"red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen",
"seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow",
"springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet",
"wheat","white","whitesmoke","yellow","yellowgreen"
};
// compare lower-case
std::string lower = s;
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
return named.count(lower) > 0;
}

// Convert HSV (h in [0,360), s and v in [0,1]) to an SVG "rgb(r,g,b)" string.
static std::string hsv_to_svg_color( double h, double s, double v )
{
double c = v * s;
double x = c * ( 1.0 - std::fabs( std::fmod( h / 60.0, 2.0 ) - 1.0 ) );
double m = v - c;
double r1, g1, b1;
if ( h < 60 ) { r1 = c; g1 = x; b1 = 0; }
else if( h < 120 ) { r1 = x; g1 = c; b1 = 0; }
else if( h < 180 ) { r1 = 0; g1 = c; b1 = x; }
else if( h < 240 ) { r1 = 0; g1 = x; b1 = c; }
else if( h < 300 ) { r1 = x; g1 = 0; b1 = c; }
else { r1 = c; g1 = 0; b1 = x; }
int r = (int)std::round( (r1 + m) * 255 );
int g = (int)std::round( (g1 + m) * 255 );
int b = (int)std::round( (b1 + m) * 255 );
return "rgb(" + std::to_string(r) + "," + std::to_string(g) + "," + std::to_string(b) + ")";
}

std::vector<std::string> paint_by_number_cell_coloring( Cell* pCell )
{
static std::vector< std::string > colors(0);
static bool setup_done = false;
static std::vector<std::string> colors;
static bool setup_done = false;
if( setup_done == false )
Comment thread
drbergman marked this conversation as resolved.
{
colors.push_back( "grey" ); // default color will be grey
// Built-in palette for the first 13 cell types.
static const std::vector<std::string> builtin = {
"grey", "red", "yellow", "green", "blue",
"magenta", "orange", "lime", "cyan",
"hotpink", "peachpuff", "darkseagreen", "lightskyblue"
};

colors.push_back( "red" );
colors.push_back( "yellow" );
colors.push_back( "green" );
colors.push_back( "blue" );

colors.push_back( "magenta" );
colors.push_back( "orange" );
colors.push_back( "lime" );
colors.push_back( "cyan" );

colors.push_back( "hotpink" );
colors.push_back( "peachpuff" );
colors.push_back( "darkseagreen" );
colors.push_back( "lightskyblue" );
int n = (int)cell_definitions_by_index.size();

if( PhysiCell_settings.svg_cell_colors_specified )
{
// Validate: every user-supplied name must match a cell definition.
for( auto& kv : PhysiCell_settings.svg_cell_colors_by_name )
{
if( cell_definitions_by_name.find(kv.first) == cell_definitions_by_name.end() )
{
std::cerr << "ERROR (paint_by_number_cell_coloring): <cell_color name=\""
<< kv.first << "\"> does not match any cell definition." << std::endl;
exit(-1);
}
}
// Validate: every supplied color value must be a recognized SVG color.
for( auto& kv : PhysiCell_settings.svg_cell_colors_by_name )
{
if( !is_valid_svg_color(kv.second) )
{
std::cerr << "ERROR (paint_by_number_cell_coloring): <cell_color name=\""
<< kv.first << "\"> has unrecognized color value \""
<< kv.second << "\"." << std::endl;
exit(-1);
}
}
Comment thread
drbergman marked this conversation as resolved.
// Build the pool of default colors not already claimed by the user (case-insensitive).
std::unordered_set<std::string> claimed;
for( auto& kv : PhysiCell_settings.svg_cell_colors_by_name )
{
std::string lc = kv.second;
std::transform(lc.begin(), lc.end(), lc.begin(), ::tolower);
Comment thread
drbergman marked this conversation as resolved.
claimed.insert(lc);
}
std::vector<std::string> pool;
for( auto& c : builtin )
{
if( claimed.count(c) == 0 )
{ pool.push_back(c); }
}
Comment thread
drbergman marked this conversation as resolved.

setup_done = true;
// Assign colors: user-specified first, then pool, then HSV-generated.
colors.resize(n);
int pool_idx = 0;
int extra_idx = 0;
for( int i = 0; i < n; i++ )
{
const std::string& name = cell_definitions_by_index[i]->name;
auto it = PhysiCell_settings.svg_cell_colors_by_name.find(name);
if( it != PhysiCell_settings.svg_cell_colors_by_name.end() )
{
colors[i] = it->second;
}
else if( pool_idx < (int)pool.size() )
{
colors[i] = pool[pool_idx++];
}
else
{
double hue = std::fmod( extra_idx * 137.508, 360.0 );
colors[i] = hsv_to_svg_color( hue, 0.65, 0.85 );
extra_idx++;
}
}
}
else
{
// No user colors: use built-in palette; generate via golden-angle HSV for extras.
colors.resize(n);
int n_extra = 0;
for( int i = 0; i < n; i++ )
{
if( i < (int)builtin.size() )
{ colors[i] = builtin[i]; }
else
{
double hue = std::fmod( n_extra * 137.508, 360.0 );
colors[i] = hsv_to_svg_color( hue, 0.65, 0.85 );
n_extra++;
}
}
Comment thread
drbergman marked this conversation as resolved.
}

setup_done = true;
}
// start all black
std::vector<std::string> output = { "black", "black", "black", "black" };
// paint by number -- by cell type
std::string interior_color = "white";
if( pCell->type < 13 )

// start all black

std::vector<std::string> output = { "black", "black", "black", "black" };

// paint by number -- by cell type

std::string interior_color = "white";
if( pCell->type >= 0 && pCell->type < (int)colors.size() )
{ interior_color = colors[ pCell->type ]; }
Comment thread
drbergman marked this conversation as resolved.

output[0] = interior_color; // set cytoplasm color
Expand Down
4 changes: 3 additions & 1 deletion modules/PhysiCell_pathology.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@
###############################################################################
*/

#include <vector>
#include <algorithm>
#include <string>
#include <unordered_set>
#include <vector>

#ifndef __PhysiCell_pathology__
#define __PhysiCell_pathology__
Expand Down
17 changes: 15 additions & 2 deletions modules/PhysiCell_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,22 @@ void PhysiCell_Settings::read_from_pugixml( void )
enable_full_saves = xml_get_bool_value( node , "enable" );
node = node.parent();

node = xml_find_node( node , "SVG" );
node = xml_find_node( node , "SVG" );
SVG_save_interval = xml_get_double_value( node , "interval" );
enable_SVG_saves = xml_get_bool_value( node , "enable" );
enable_SVG_saves = xml_get_bool_value( node , "enable" );

pugi::xml_node node_cell_colors = node.child("cell_colors");
if( node_cell_colors )
{
svg_cell_colors_specified = true;
for( pugi::xml_node cc = node_cell_colors.child("cell_color"); cc; cc = cc.next_sibling("cell_color") )
{
std::string cname = cc.attribute("name").as_string();
std::string color = cc.child_value();
if( !cname.empty() && !color.empty() )
{ svg_cell_colors_by_name[cname] = color; }
}
}

pugi::xml_node node_plot_substrate;
node_plot_substrate = xml_find_node( node , "plot_substrate" );
Expand Down
8 changes: 6 additions & 2 deletions modules/PhysiCell_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
#include <vector>
#include <random>
#include <chrono>
#include <map>
#include <unordered_map>

#include "./PhysiCell_pugixml.h"
Expand Down Expand Up @@ -115,8 +116,11 @@ class PhysiCell_Settings

bool disable_automated_spring_adhesions = false;

double SVG_save_interval = 60;
bool enable_SVG_saves = true;
double SVG_save_interval = 60;
bool enable_SVG_saves = true;

bool svg_cell_colors_specified = false;
std::map<std::string, std::string> svg_cell_colors_by_name;

bool enable_substrate_plot = false;
std::string substrate_to_monitor = "oxygen";
Expand Down
Loading