diff --git a/bioexplorer/backend/plugins/MediaMaker/plugin/MediaMakerPlugin.cpp b/bioexplorer/backend/plugins/MediaMaker/plugin/MediaMakerPlugin.cpp index 0243268db..706d71af1 100644 --- a/bioexplorer/backend/plugins/MediaMaker/plugin/MediaMakerPlugin.cpp +++ b/bioexplorer/backend/plugins/MediaMaker/plugin/MediaMakerPlugin.cpp @@ -319,7 +319,6 @@ void MediaMakerPlugin::_exportColorBuffer() const auto image = frameBuffer.getImage(); ImageBuf rotatedBuf; ImageBufAlgo::flip(rotatedBuf, image); - swapRedBlue32(rotatedBuf); // Determine the output format std::string format = _exportFramesToDiskPayload.format; diff --git a/bioexplorer/backend/science/common/SDFGeometries.cpp b/bioexplorer/backend/science/common/SDFGeometries.cpp index 30212bd7b..d1b4b9a30 100644 --- a/bioexplorer/backend/science/common/SDFGeometries.cpp +++ b/bioexplorer/backend/science/common/SDFGeometries.cpp @@ -56,15 +56,32 @@ Vector4fs SDFGeometries::_getProcessedSectionPoints(const MorphologyRepresentati const Vector4fs& points) { Vector4fs localPoints; - if (representation == MorphologyRepresentation::bezier) + switch (representation) + { + case MorphologyRepresentation::bezier: { const uint64_t nbBezierPoints = std::max(static_cast(3), points.size() / 10); const float step = 1.f / static_cast(nbBezierPoints); for (float i = 0; i <= 1.f; i += step) localPoints.push_back(getBezierPoint(points, i)); + break; } - else + case MorphologyRepresentation::spheres: + { + for (uint64_t i = 0; i < points.size() - 1; ++i) + { + const auto p1 = points[i]; + const auto p2 = points[i + 1]; + const auto spheres = fillConeWithSpheres(p1, p2); + localPoints.insert(localPoints.end(), spheres.begin(), spheres.end()); + } + break; + } + default: + { localPoints = points; + } + } return localPoints; } diff --git a/bioexplorer/backend/science/common/ThreadSafeContainer.cpp b/bioexplorer/backend/science/common/ThreadSafeContainer.cpp index ce8428890..1ab9b6fbb 100644 --- a/bioexplorer/backend/science/common/ThreadSafeContainer.cpp +++ b/bioexplorer/backend/science/common/ThreadSafeContainer.cpp @@ -128,6 +128,29 @@ uint64_t ThreadSafeContainer::addCone(const Vector3f& sourcePosition, const floa userDataOffset}); } +void ThreadSafeContainer::addConeOfSpheres(const Vector3f& sourcePosition, const float sourceRadius, + const Vector3f& targetPosition, const float targetRadius, + const size_t materialId, const uint64_t userDataOffset, + const float constantRadius) +{ + const Vector3f scale = _scale; + const Vector3f scaledSrcPosition = + getAlignmentToGrid(_alignToGrid, Vector3f(_position + _rotation * Vector3d(sourcePosition)) * scale); + const auto scaledSrcRadius = sourceRadius * scale.x; + const Vector3f scaledDstPosition = + getAlignmentToGrid(_alignToGrid, Vector3f(_position + _rotation * Vector3d(targetPosition)) * scale); + const auto scaledDstRadius = targetRadius * scale.x; + + const auto spheres = + fillConeWithSpheres({scaledSrcPosition, scaledSrcRadius}, {scaledDstPosition, scaledDstRadius}, constantRadius); + for (const auto& sphere : spheres) + _addSphere(materialId, {Vector3f(sphere), sphere.w, userDataOffset}); + _bounds.merge(scaledSrcPosition + scaledSrcRadius); + _bounds.merge(scaledSrcPosition - scaledSrcRadius); + _bounds.merge(scaledDstPosition + scaledDstRadius); + _bounds.merge(scaledDstPosition - scaledDstRadius); +} + uint64_t ThreadSafeContainer::addTorus(const Vector3f& position, const float outerRadius, const float innerRadius, const size_t materialId, const uint64_t userDataOffset, const Neighbours& neighbours, const Vector3f displacement) diff --git a/bioexplorer/backend/science/common/ThreadSafeContainer.h b/bioexplorer/backend/science/common/ThreadSafeContainer.h index b997d24a0..7cab9fcbf 100644 --- a/bioexplorer/backend/science/common/ThreadSafeContainer.h +++ b/bioexplorer/backend/science/common/ThreadSafeContainer.h @@ -18,6 +18,8 @@ #include "Types.h" +#include + namespace bioexplorer { namespace common @@ -81,12 +83,12 @@ class ThreadSafeContainer * @return uint64_t Index of the geometry in the model */ uint64_t addCutSphere(const core::Vector3f& position, const float radius, const float cutRadius, - const size_t materialId, const uint64_t userDataOffset = 0, const Neighbours& neighbours = {}, - const core::Vector3f displacement = core::Vector3f()); + const size_t materialId, const uint64_t userDataOffset = NO_USER_DATA, + const Neighbours& neighbours = {}, const core::Vector3f displacement = core::Vector3f()); /** * @brief Add a cone to the thread safe model. If both radii are identical - * and signed-distance field technique is not used, a cylinder is add + * and signed-distance field technique is not used, a cylinder is added * instead of a cone * * @param sourcePosition Base position of the cone @@ -104,9 +106,24 @@ class ThreadSafeContainer */ uint64_t addCone(const core::Vector3f& sourcePosition, const float sourceRadius, const core::Vector3f& targetPosition, const float targetRadius, const size_t materialId, - const bool useSdf, const uint64_t userDataOffset = 0, const Neighbours& neighbours = {}, + const bool useSdf, const uint64_t userDataOffset = NO_USER_DATA, const Neighbours& neighbours = {}, const core::Vector3f displacement = core::Vector3f()); + /** + * @brief Add a cone of spheres to the thread safe model. + * + * @param sourcePosition Base position of the cone + * @param sourceRadius Base radius of the cone + * @param targetPosition Top position of the cone + * @param targetRadius Top radius of the cone + * @param materialId Material identifier + * @param userDataOffset User data to attach to the sphere + * @param constantRadius Use contant sphere radius if true + */ + void addConeOfSpheres(const core::Vector3f& sourcePosition, const float sourceRadius, + const core::Vector3f& targetPosition, const float targetRadius, const size_t materialId, + const uint64_t userDataOffset = NO_USER_DATA, const float constantRadius = 0.f); + /** * @brief Add a torus to the thread safe model * @@ -121,8 +138,8 @@ class ThreadSafeContainer * @return uint64_t Index of the geometry in the model */ uint64_t addTorus(const core::Vector3f& position, const float outerRadius, const float innerRadius, - const size_t materialId, const uint64_t userDataOffset = 0, const Neighbours& neighbours = {}, - const core::Vector3f displacement = core::Vector3f()); + const size_t materialId, const uint64_t userDataOffset = NO_USER_DATA, + const Neighbours& neighbours = {}, const core::Vector3f displacement = core::Vector3f()); /** * @brief Add a vesica to the thread safe model @@ -138,8 +155,8 @@ class ThreadSafeContainer * @return uint64_t Index of the geometry in the model */ uint64_t addVesica(const core::Vector3f& sourcePosition, const core::Vector3f& targetPosition, const float radius, - const size_t materialId, const uint64_t userDataOffset = 0, const Neighbours& neighbours = {}, - const core::Vector3f displacement = core::Vector3f()); + const size_t materialId, const uint64_t userDataOffset = NO_USER_DATA, + const Neighbours& neighbours = {}, const core::Vector3f displacement = core::Vector3f()); /** * @brief Add a vesica to the thread safe model diff --git a/bioexplorer/backend/science/common/Types.h b/bioexplorer/backend/science/common/Types.h index 8759a019f..193107240 100644 --- a/bioexplorer/backend/science/common/Types.h +++ b/bioexplorer/backend/science/common/Types.h @@ -176,6 +176,14 @@ enum class XYZFileFormat xyzr_rgba_ascii = 7 }; const std::string ASCII_FILE_SEPARATOR = ","; + +typedef struct +{ + bool enabled{false}; + bool uniform{false}; + double radius{1.0}; + +} SpheresRepresentation; } // namespace common namespace molecularsystems @@ -440,7 +448,9 @@ enum class MorphologyRepresentation orientation = 3, bezier = 4, contour = 5, - surface = 6 + surface = 6, + spheres = 7, + uniform_spheres = 8 }; enum class MorphologyRealismLevel @@ -1403,7 +1413,9 @@ enum class VasculatureRepresentation section = 1, segment = 2, optimized_segment = 3, - bezier = 4 + bezier = 4, + spheres = 5, + uniform_spheres = 6 }; typedef struct diff --git a/bioexplorer/backend/science/common/Utils.cpp b/bioexplorer/backend/science/common/Utils.cpp index 98e035228..1f7c91efe 100644 --- a/bioexplorer/backend/science/common/Utils.cpp +++ b/bioexplorer/backend/science/common/Utils.cpp @@ -357,10 +357,10 @@ Transformation combineTransformations(const Transformations& transformations) finalMatrix *= matrix; } - glm::vec3 scale; + Vector3f scale; glm::quat rotation; - glm::vec3 translation; - glm::vec3 skew; + Vector3f translation; + Vector3f skew; glm::vec4 perspective; glm::decompose(finalMatrix, scale, rotation, translation, skew, perspective); @@ -449,10 +449,10 @@ double capsuleVolume(const double height, const double radius) Vector3f transformVector3f(const Vector3f& v, const Matrix4f& transformation) { - glm::vec3 scale; + Vector3f scale; glm::quat rotation; - glm::vec3 translation; - glm::vec3 skew; + Vector3f translation; + Vector3f skew; glm::vec4 perspective; glm::decompose(transformation, scale, rotation, translation, skew, perspective); return translation + rotation * v; @@ -613,5 +613,28 @@ Vector3d getAlignmentToGrid(const double gridSize, const Vector3d& position) return gridSize > 0.0 ? Vector3d(Vector3i(position / gridSize) * static_cast(gridSize)) : position; } +Vector4fs fillConeWithSpheres(const Vector4f& center, const Vector4f& apex, const float constantRadius) +{ + const Vector3f p1 = Vector3f(center); + const Vector3f p2 = Vector3f(apex); + const float height = length(p2 - p1); + const Vector3f direction = (p2 - p1) / height; + + Vector4fs spheres; + float t = 0.f; + while (t <= 1.f) + { + Vector3f c = lerp(p1, p2, t); + float radius = constantRadius; + if (radius == 0.f) + radius = center.w + t * (apex.w - center.w); + if (radius > 0.f) + spheres.push_back({c, radius}); + t += (radius * 0.5f / height); // Overlapping by half of their radius + } + + return spheres; +} + } // namespace common } // namespace bioexplorer diff --git a/bioexplorer/backend/science/common/Utils.h b/bioexplorer/backend/science/common/Utils.h index 9be191e4e..58ad9418a 100644 --- a/bioexplorer/backend/science/common/Utils.h +++ b/bioexplorer/backend/science/common/Utils.h @@ -302,5 +302,19 @@ double valueFromDoubles(const doubles& array, const size_t index, const double d */ core::Vector3d getAlignmentToGrid(const double gridSize, const core::Vector3d& position); +/** + * @brief Fills a cone with spheres that progressively decrease in radius. + * + * This function generates spheres that fit perfectly within the profile of a cone defined by two points and two radii. + * The spheres overlap by half of their radius for an optimal fit. + * + * @param center Center of the base of the cone. + * @param apex Apex of the cone + * @param constantRadius Uses constant sphere radius if different from 0 + * @return A vector of spheres, each defined by its center and radius. + */ +core::Vector4fs fillConeWithSpheres(const core::Vector4f& center, const core::Vector4f& apex, + const float constantRadius = 0.f); + } // namespace common } // namespace bioexplorer diff --git a/bioexplorer/backend/science/io/CacheLoader.cpp b/bioexplorer/backend/science/io/CacheLoader.cpp index 898ad2d29..8215d900d 100644 --- a/bioexplorer/backend/science/io/CacheLoader.cpp +++ b/bioexplorer/backend/science/io/CacheLoader.cpp @@ -818,6 +818,8 @@ void CacheLoader::exportToXYZ(const std::string& filename, const XYZFileFormat f const auto& spheresMap = model.getSpheres(); for (const auto& spheres : spheresMap) { + if (spheres.first == BOUNDINGBOX_MATERIAL_ID || spheres.first == SECONDARY_MODEL_MATERIAL_ID) + continue; const auto material = model.getMaterial(spheres.first); for (const auto& sphere : spheres.second) @@ -861,10 +863,10 @@ void CacheLoader::exportToXYZ(const std::string& filename, const XYZFileFormat f const auto& color = material->getDiffuseColor(); const auto opacity = material->getOpacity(); - file << ASCII_FILE_SEPARATOR << static_cast(256.f * color.x) << ASCII_FILE_SEPARATOR - << static_cast(256.f * color.y) << ASCII_FILE_SEPARATOR - << static_cast(256.f * color.z) << ASCII_FILE_SEPARATOR - << static_cast(256.f * opacity); + file << ASCII_FILE_SEPARATOR << static_cast(255.f * color.x) << ASCII_FILE_SEPARATOR + << static_cast(255.f * color.y) << ASCII_FILE_SEPARATOR + << static_cast(255.f * color.z) << ASCII_FILE_SEPARATOR + << static_cast(255.f * opacity); } file << std::endl; break; diff --git a/bioexplorer/backend/science/morphologies/Astrocytes.cpp b/bioexplorer/backend/science/morphologies/Astrocytes.cpp index 98d7858fe..a7815b327 100644 --- a/bioexplorer/backend/science/morphologies/Astrocytes.cpp +++ b/bioexplorer/backend/science/morphologies/Astrocytes.cpp @@ -53,6 +53,11 @@ Astrocytes::Astrocytes(Scene& scene, const AstrocytesDetails& details, const Vec , _scene(scene) { _animationDetails = doublesToCellAnimationDetails(_details.animationParams); + _spheresRepresentation.enabled = _details.morphologyRepresentation == MorphologyRepresentation::spheres || + _details.morphologyRepresentation == MorphologyRepresentation::uniform_spheres; + _spheresRepresentation.uniform = _details.morphologyRepresentation == MorphologyRepresentation::uniform_spheres; + _spheresRepresentation.radius = _spheresRepresentation.uniform ? _details.radiusMultiplier : 0.f; + Timer chrono; _buildModel(callback); PLUGIN_TIMER(chrono.elapsed(), "Astrocytes loaded"); @@ -197,10 +202,17 @@ void Astrocytes::_buildModel(const LoaderProgress& callback, const doubles& radi { const bool useSdf = andCheck(static_cast(_details.realismLevel), static_cast(MorphologyRealismLevel::soma)); - somaGeometryIndex = container.addSphere( - somaPosition, somaRadius, somaMaterialId, useSdf, NO_USER_DATA, {}, - Vector3f(somaRadius * _getDisplacementValue(DisplacementElement::morphology_soma_strength), - somaRadius * _getDisplacementValue(DisplacementElement::morphology_soma_frequency), 0.f)); + + if (_spheresRepresentation.enabled) + _addSomaAsSpheres(somaId, somaMaterialId, sections, somaPosition, Quaterniond(), somaRadius, + NO_USER_DATA, _details.radiusMultiplier, container); + else + somaGeometryIndex = container.addSphere( + somaPosition, somaRadius, somaMaterialId, useSdf, NO_USER_DATA, {}, + Vector3f(somaRadius * _getDisplacementValue(DisplacementElement::morphology_soma_strength), + somaRadius * _getDisplacementValue(DisplacementElement::morphology_soma_frequency), + 0.f)); + if (_details.generateInternals) { const auto useSdf = andCheck(static_cast(_details.realismLevel), @@ -248,7 +260,7 @@ void Astrocytes::_buildModel(const LoaderProgress& callback, const doubles& radi const bool useSdf = andCheck(static_cast(_details.realismLevel), static_cast(MorphologyRealismLevel::dendrite)); uint64_t geometryIndex = 0; - if (section.second.parentId == SOMA_AS_PARENT) + if (section.second.parentId == SOMA_AS_PARENT && !_spheresRepresentation.enabled) { // Section connected to the soma const auto& point = points[0]; @@ -256,17 +268,24 @@ void Astrocytes::_buildModel(const LoaderProgress& callback, const doubles& radi const float dstRadius = _getCorrectedRadius(point.w * 0.5f, _details.radiusMultiplier); const auto dstPosition = _animatedPosition(Vector4d(somaPosition + Vector3d(point), dstRadius), somaId); - geometryIndex = container.addCone( - somaPosition, srcRadius, dstPosition, dstRadius, somaMaterialId, useSdf, userData, - neighbours, - Vector3f(srcRadius * _getDisplacementValue(DisplacementElement::morphology_soma_strength), - srcRadius * _getDisplacementValue(DisplacementElement::morphology_soma_frequency), - 0.f)); - neighbours.insert(geometryIndex); + + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(somaPosition, srcRadius, dstPosition, dstRadius, somaMaterialId, + userData, _spheresRepresentation.radius); + else + { + geometryIndex = container.addCone( + somaPosition, srcRadius, dstPosition, dstRadius, somaMaterialId, useSdf, userData, + neighbours, + Vector3f( + srcRadius * _getDisplacementValue(DisplacementElement::morphology_soma_strength), + srcRadius * _getDisplacementValue(DisplacementElement::morphology_soma_frequency), + 0.f)); + neighbours.insert(geometryIndex); + } } - // If maxDistanceToSoma != 0, then compute actual distance from - // soma + // If maxDistanceToSoma != 0, then compute actual distance from soma double distanceToSoma = 0.0; if (_details.maxDistanceToSoma > 0.0) distanceToSoma = _getDistanceToSoma(sections, section.second); @@ -284,8 +303,7 @@ void Astrocytes::_buildModel(const LoaderProgress& callback, const doubles& radi const auto src = _animatedPosition(Vector4d(somaPosition + Vector3d(srcPoint), srcRadius), somaId); - // Ignore points that are too close the previous one - // (according to respective radii) + // Ignore points that are too close the previous one (according to respective radii) Vector4f dstPoint; float dstRadius; do @@ -309,15 +327,21 @@ void Astrocytes::_buildModel(const LoaderProgress& callback, const doubles& radi const auto dst = _animatedPosition(Vector4d(somaPosition + Vector3d(dstPoint), dstRadius), somaId); - if (!useSdf) + if (!useSdf && !_spheresRepresentation.enabled) geometryIndex = container.addSphere(dst, dstRadius, materialId, useSdf, NO_USER_DATA); - geometryIndex = container.addCone( - src, srcRadius, dst, dstRadius, materialId, useSdf, userData, {geometryIndex}, - Vector3f(srcRadius * - _getDisplacementValue(DisplacementElement::morphology_section_strength), - _getDisplacementValue(DisplacementElement::morphology_section_frequency), 0.f)); - + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(src, srcRadius, dst, dstRadius, materialId, userData, + _spheresRepresentation.radius); + else + { + geometryIndex = container.addCone( + src, srcRadius, dst, dstRadius, materialId, useSdf, userData, {geometryIndex}, + Vector3f(srcRadius * + _getDisplacementValue(DisplacementElement::morphology_section_strength), + _getDisplacementValue(DisplacementElement::morphology_section_frequency), + 0.f)); + } _bounds.merge(srcPoint); if (_details.maxDistanceToSoma > 0.0 && @@ -489,11 +513,18 @@ void Astrocytes::_addEndFoot(ThreadSafeContainer& container, const Vector3d& som const auto srcPosition = _animatedPosition(Vector4d(srcNode.position - shift, srcVasculatureRadius)); const auto dstPosition = _animatedPosition(Vector4d(dstNode.position - shift, dstVasculatureRadius)); - if (!useSdf) + if (!useSdf && !_spheresRepresentation.enabled) container.addSphere(srcPosition, srcEndFootRadius, materialId, useSdf, srcUserData); - geometryIndex = container.addCone(srcPosition, srcEndFootRadius, dstPosition, dstEndFootRadius, - materialId, useSdf, srcUserData, neighbours, displacement); - neighbours = {geometryIndex}; + + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(srcPosition, srcEndFootRadius, dstPosition, dstEndFootRadius, materialId, + srcUserData, _spheresRepresentation.radius); + else + { + geometryIndex = container.addCone(srcPosition, srcEndFootRadius, dstPosition, dstEndFootRadius, + materialId, useSdf, srcUserData, neighbours, displacement); + neighbours = {geometryIndex}; + } ++endFootSegmentIndex; } } diff --git a/bioexplorer/backend/science/morphologies/Morphologies.cpp b/bioexplorer/backend/science/morphologies/Morphologies.cpp index 8f236db7b..0f3631767 100644 --- a/bioexplorer/backend/science/morphologies/Morphologies.cpp +++ b/bioexplorer/backend/science/morphologies/Morphologies.cpp @@ -41,6 +41,76 @@ size_t Morphologies::_getNbMitochondrionSegments() const return 2 + rand() % 5; } +double Morphologies::_addSomaAsSpheres(const uint64_t neuronId, const size_t somaMaterialId, const SectionMap& sections, + const Vector3d& somaPosition, const Quaterniond& somaRotation, + const double somaRadius, const uint64_t somaUserData, + const double radiusMultiplier, ThreadSafeContainer& container) +{ + Vector3ds sectionRoots; + + double minRadius = std::numeric_limits::max(); + double maxRadius = std::numeric_limits::min(); + double sectionRootRadius = std::numeric_limits::max(); + Vector3d baryCenter; + for (const auto& section : sections) + if (section.second.parentId == SOMA_AS_PARENT) + { + const auto& points = section.second.points; + const uint64_t index = std::min(size_t(0), points.size()); + const Vector3d p = points[index]; + sectionRoots.push_back(p); + const double l = length(p); + minRadius = std::min(l, minRadius); + maxRadius = std::max(l, maxRadius); + sectionRootRadius = std::min(sectionRootRadius, static_cast(points[index].w * 0.5)); + baryCenter += p; + } + baryCenter /= sectionRoots.size(); + // sectionRootRadius = sectionRootRadius / sectionRoots.size() * radiusMultiplier; + + const double radius = (minRadius + maxRadius) / 2.0; + const double somaSurface = 4.0 * glm::pi() * pow(radius, 2.0); + const double sphereSurfaceOnSoma = glm::pi() * pow(sectionRootRadius, 2.0); + const uint64_t nbSpheres = somaSurface / sphereSurfaceOnSoma; + + Vector3ds spheres(nbSpheres); + + const double goldenRatio = (1.0 + std::sqrt(5.0)) / 2.0; + const double angleIncrement = 2.0 * M_PI * goldenRatio; + for (uint64_t i = 0; i < nbSpheres; ++i) + { + const double t = static_cast(i) / static_cast(nbSpheres - 1); + const double phi = std::acos(1.0 - 2.0 * t); + const double theta = angleIncrement * static_cast(i); + const double r = minRadius; + spheres[i] = Vector3d(r * sin(phi) * cos(theta), r * sin(phi) * sin(theta), r * cos(phi)); + } + + // Smooth soma according to section root + for (auto& sphere : spheres) + { +#if 1 + const double smoothingFactor = 1.0; + double r = minRadius * smoothingFactor; + for (const auto& sr : sectionRoots) + { + const auto dir = sr - (baryCenter + sphere); + const double angle = dot(normalize(sr), normalize(baryCenter + sphere)); + if (angle >= 0.0) + r += length(dir) * -angle * smoothingFactor; + } +#endif + + const auto src = + _animatedPosition(Vector4d(somaPosition + somaRotation * (baryCenter + normalize(sphere) * r * 0.5), + sectionRootRadius), + neuronId); + container.addSphere(src, sectionRootRadius, somaMaterialId, false, somaUserData); + } + + return somaRadius; +} + void Morphologies::_addSomaInternals(ThreadSafeContainer& container, const size_t baseMaterialId, const Vector3d& somaPosition, const double somaRadius, const double mitochondriaDensity, const bool useSdf, const double radiusMultiplier) @@ -58,7 +128,8 @@ void Morphologies::_addSomaInternals(ThreadSafeContainer& container, const size_ const size_t nucleusMaterialId = baseMaterialId + MATERIAL_OFFSET_NUCLEUS; container.addSphere( - somaPosition, nucleusRadius, nucleusMaterialId, useSdf, NO_USER_DATA, {}, + somaPosition, nucleusRadius, nucleusMaterialId, _spheresRepresentation.enabled ? false : useSdf, NO_USER_DATA, + {}, Vector3f(nucleusRadius * _getDisplacementValue(DisplacementElement::morphology_nucleus_strength), nucleusRadius * _getDisplacementValue(DisplacementElement::morphology_nucleus_frequency), 0.f)); @@ -101,10 +172,14 @@ void Morphologies::_addSomaInternals(ThreadSafeContainer& container, const size_ if (i > 0) { const auto p1 = somaPosition + somaOutterRadius * pointsInSphere[i - 1]; - geometryIndex = container.addCone( - p1, previousRadius, p2, radius, mitochondrionMaterialId, useSdf, NO_USER_DATA, {geometryIndex}, - Vector3f(radius * _getDisplacementValue(DisplacementElement::morphology_mitochondrion_strength), - displacementFrequency, 0.f)); + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(p1, previousRadius, p2, radius, mitochondrionMaterialId, NO_USER_DATA, + _spheresRepresentation.radius); + else + geometryIndex = container.addCone( + p1, previousRadius, p2, radius, mitochondrionMaterialId, useSdf, NO_USER_DATA, {geometryIndex}, + Vector3f(radius * _getDisplacementValue(DisplacementElement::morphology_mitochondrion_strength), + displacementFrequency, 0.f)); mitochondriaVolume += coneVolume(length(p2 - p1), previousRadius, radius); } diff --git a/bioexplorer/backend/science/morphologies/Morphologies.h b/bioexplorer/backend/science/morphologies/Morphologies.h index 3c31e9e97..f2e29bfd7 100644 --- a/bioexplorer/backend/science/morphologies/Morphologies.h +++ b/bioexplorer/backend/science/morphologies/Morphologies.h @@ -56,6 +56,11 @@ class Morphologies : public common::SDFGeometries protected: size_t _getNbMitochondrionSegments() const; + double _addSomaAsSpheres(const uint64_t neuronId, const size_t somaMaterialId, const SectionMap& sections, + const core::Vector3d& somaPosition, const core::Quaterniond& somaRotation, + const double somaRadius, const uint64_t somaUserData, const double radiusMultiplier, + common::ThreadSafeContainer& container); + void _addSomaInternals(common::ThreadSafeContainer& container, const size_t materialId, const core::Vector3d& somaPosition, const double somaRadius, const double mitochondriaDensity, const bool useSdf, const double radiusMultiplier); @@ -63,6 +68,8 @@ class Morphologies : public common::SDFGeometries double _getDistanceToSoma(const SectionMap& sections, const Section& section); size_t _getMaterialFromDistanceToSoma(const double maxDistanceToSoma, const double distanceToSoma) const; + + common::SpheresRepresentation _spheresRepresentation; }; } // namespace morphology } // namespace bioexplorer \ No newline at end of file diff --git a/bioexplorer/backend/science/morphologies/Neurons.cpp b/bioexplorer/backend/science/morphologies/Neurons.cpp index 579e53d5b..8716779eb 100644 --- a/bioexplorer/backend/science/morphologies/Neurons.cpp +++ b/bioexplorer/backend/science/morphologies/Neurons.cpp @@ -38,6 +38,8 @@ #include +#include + using namespace core; namespace bioexplorer @@ -54,7 +56,7 @@ namespace morphology { const uint64_t NB_MYELIN_FREE_SEGMENTS = 4; const double DEFAULT_ARROW_RADIUS_RATIO = 10.0; -const uint64_t DEFAULT_DEBUG_SYNAPSE_DENSITY_RATIO = 5; +const uint64_t DEFAULT_DEBUG_SYNAPSE_DENSITY_RATIO = 1; const double MAX_SOMA_RADIUS = 10.0; std::map reportTypeAsString = {{ReportType::undefined, "undefined"}, @@ -75,6 +77,11 @@ Neurons::Neurons(Scene& scene, const NeuronsDetails& details, const Vector3d& as , _scene(scene) { _animationDetails = doublesToCellAnimationDetails(_details.animationParams); + _spheresRepresentation.enabled = _details.morphologyRepresentation == MorphologyRepresentation::spheres || + _details.morphologyRepresentation == MorphologyRepresentation::uniform_spheres; + _spheresRepresentation.uniform = _details.morphologyRepresentation == MorphologyRepresentation::uniform_spheres; + _spheresRepresentation.radius = _spheresRepresentation.uniform ? _details.radiusMultiplier : 0.f; + srand(_animationDetails.seed); Timer chrono; @@ -473,6 +480,81 @@ SectionSynapseMap Neurons::_buildDebugSynapses(const uint64_t neuronId, const Se return synapses; } +double Neurons::_addSoma(const uint64_t neuronId, const size_t somaMaterialId, const Section& section, + const Vector3d& somaPosition, const Quaterniond& somaRotation, const double somaRadius, + const uint64_t somaUserData, const double voltageScaling, ThreadSafeContainer& container, + Neighbours& somaNeighbours, Neighbours& sectionNeighbours) +{ + double correctedSomaRadius; + uint64_t count = 0.0; + // Sections connected to the soma + if (_details.showMembrane && _details.loadSomas && section.parentId == SOMA_AS_PARENT) + { + auto points = section.points; + for (uint64_t i = 0; i < points.size(); ++i) + { + auto& point = points[i]; + point.x *= voltageScaling; + point.y *= voltageScaling; + point.z *= voltageScaling; + } + + const auto& firstPoint = points[0]; + const auto& lastPoint = points[points.size() - 1]; + auto point = firstPoint; + if (length(lastPoint) < length(firstPoint)) + point = lastPoint; + + const bool useSdf = + andCheck(static_cast(_details.realismLevel), static_cast(MorphologyRealismLevel::soma)); + + const double srcRadius = _getCorrectedRadius(somaRadius * 0.75, _details.radiusMultiplier); + const double dstRadius = _getCorrectedRadius(point.w * 0.5, _details.radiusMultiplier); + + const auto sectionType = static_cast(section.type); + const bool loadSection = (sectionType == NeuronSectionType::axon && _details.loadAxon) || + (sectionType == NeuronSectionType::basal_dendrite && _details.loadBasalDendrites) || + (sectionType == NeuronSectionType::apical_dendrite && _details.loadApicalDendrites); + + if (!loadSection) + return -1.0; + + const Vector3d dst = + _animatedPosition(Vector4d(somaPosition + somaRotation * Vector3d(point), dstRadius), neuronId); + const Vector3f displacement = {Vector3f(_getDisplacementValue(DisplacementElement::morphology_soma_strength), + _getDisplacementValue(DisplacementElement::morphology_soma_frequency), + 0.f)}; + + Vector3d p2 = Vector3d(); + if (voltageScaling == 1.f) + { + const Vector3d segmentDirection = normalize(lastPoint - firstPoint); + const double halfDistanceToSoma = length(Vector3d(point)) * 0.5; + const Vector3d p1 = Vector3d(point) - halfDistanceToSoma * segmentDirection; + p2 = p1 * dstRadius / somaRadius * 0.95; + } + const auto src = _animatedPosition(Vector4d(somaPosition + somaRotation * p2, srcRadius), neuronId); + + correctedSomaRadius = std::max(correctedSomaRadius, length(p2)) * 2.0; + + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(src, srcRadius, dst, dstRadius, somaMaterialId, somaUserData, + _spheresRepresentation.radius); + else + { + const uint64_t geometryIndex = container.addCone(src, srcRadius, dst, dstRadius, somaMaterialId, useSdf, + somaUserData, {}, displacement); + somaNeighbours.insert(geometryIndex); + sectionNeighbours.insert(geometryIndex); + + if (!useSdf) + container.addSphere(dst, dstRadius, somaMaterialId, useSdf, somaUserData); + } + ++count; + } + return count == 0 ? somaRadius : correctedSomaRadius / count; +} + void Neurons::_buildMorphology(ThreadSafeContainer& container, const uint64_t neuronId, const NeuronSoma& soma, const uint64_t neuronIndex, const float* voltages) { @@ -584,9 +666,14 @@ void Neurons::_buildMorphology(ThreadSafeContainer& container, const uint64_t ne } } + // Soma as spheres + double correctedSomaRadius = 0.f; + if (_spheresRepresentation.enabled) + correctedSomaRadius = _addSomaAsSpheres(neuronId, somaMaterialId, sections, somaPosition, somaRotation, + somaRadius, somaUserData, _details.radiusMultiplier, container); + // Sections (dendrites and axon) Neighbours somaNeighbours; - double correctedSomaRadius = 0.f; for (const auto& section : sections) { Neighbours sectionNeighbours; @@ -614,66 +701,13 @@ void Neurons::_buildMorphology(ThreadSafeContainer& container, const uint64_t ne continue; } - uint64_t count = 0.0; - // Sections connected to the soma - if (_details.showMembrane && _details.loadSomas && section.second.parentId == SOMA_AS_PARENT) - { - auto points = section.second.points; - for (uint64_t i = 0; i < points.size(); ++i) - { - auto& point = points[i]; - point.x *= voltageScaling; - point.y *= voltageScaling; - point.z *= voltageScaling; - } - - const auto& firstPoint = points[0]; - const auto& lastPoint = points[points.size() - 1]; - auto point = firstPoint; - if (length(lastPoint) < length(firstPoint)) - point = lastPoint; - - useSdf = andCheck(static_cast(_details.realismLevel), - static_cast(MorphologyRealismLevel::soma)); - - const double srcRadius = _getCorrectedRadius(somaRadius * 0.75, _details.radiusMultiplier); - const double dstRadius = _getCorrectedRadius(point.w * 0.5, _details.radiusMultiplier); - - const auto sectionType = static_cast(section.second.type); - const bool loadSection = - (sectionType == NeuronSectionType::axon && _details.loadAxon) || - (sectionType == NeuronSectionType::basal_dendrite && _details.loadBasalDendrites) || - (sectionType == NeuronSectionType::apical_dendrite && _details.loadApicalDendrites); - - if (!loadSection) - continue; - - const Vector3d dst = - _animatedPosition(Vector4d(somaPosition + somaRotation * Vector3d(point), dstRadius), neuronId); - const Vector3f displacement = { - Vector3f(_getDisplacementValue(DisplacementElement::morphology_soma_strength), - _getDisplacementValue(DisplacementElement::morphology_soma_frequency), 0.f)}; - - Vector3d p2 = Vector3d(); - if (voltageScaling == 1.f) - { - const Vector3d segmentDirection = normalize(lastPoint - firstPoint); - const double halfDistanceToSoma = length(Vector3d(point)) * 0.5; - const Vector3d p1 = Vector3d(point) - halfDistanceToSoma * segmentDirection; - p2 = p1 * dstRadius / somaRadius * 0.95; - } - const auto src = _animatedPosition(Vector4d(somaPosition + somaRotation * p2, srcRadius), neuronId); - - correctedSomaRadius = std::max(correctedSomaRadius, length(p2)) * 2.0; - const uint64_t geometryIndex = container.addCone(src, srcRadius, dst, dstRadius, somaMaterialId, useSdf, - somaUserData, {}, displacement); - somaNeighbours.insert(geometryIndex); - sectionNeighbours.insert(geometryIndex); - if (!useSdf) - container.addSphere(dst, dstRadius, somaMaterialId, useSdf, somaUserData); - ++count; - } - correctedSomaRadius = count == 0 ? somaRadius : correctedSomaRadius / count; + correctedSomaRadius = + _spheresRepresentation.enabled + ? somaRadius + : _addSoma(neuronId, somaMaterialId, section.second, somaPosition, somaRotation, somaRadius, + somaUserData, voltageScaling, container, somaNeighbours, sectionNeighbours); + if (correctedSomaRadius < 0.f) + continue; float parentRadius = section.second.points[0].w; if (sections.find(section.second.parentId) != sections.end()) @@ -689,7 +723,7 @@ void Neurons::_buildMorphology(ThreadSafeContainer& container, const uint64_t ne distanceToSoma, sectionNeighbours, voltageScaling); } - if (_details.loadSomas) + if (_details.loadSomas && !_spheresRepresentation.enabled) { if (_details.showMembrane) { @@ -959,14 +993,19 @@ void Neurons::_addSection(ThreadSafeContainer& container, const uint64_t neuronI } } - if (!useSdf) - container.addSphere(dst, dstRadius, materialId, useSdf, userData); - - const uint64_t geometryIndex = - container.addCone(src, srcRadius, dst, dstRadius, materialId, useSdf, userData, {}, displacement); + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(src, srcRadius, dst, dstRadius, materialId, userData, + _spheresRepresentation.radius); + else + { + if (!useSdf) + container.addSphere(dst, dstRadius, materialId, useSdf, userData); - previousGeometryIndex = geometryIndex; - sectionNeighbours = {geometryIndex}; + const uint64_t geometryIndex = + container.addCone(src, srcRadius, dst, dstRadius, materialId, useSdf, userData, {}, displacement); + previousGeometryIndex = geometryIndex; + sectionNeighbours = {geometryIndex}; + } // Stop if distance to soma in greater than the specified max value _maxDistanceToSoma = std::max(_maxDistanceToSoma, distanceToSoma + sectionLength); @@ -1046,7 +1085,7 @@ void Neurons::_addSectionInternals(ThreadSafeContainer& container, const uint64_ if (mitochondrionSegment != 0) neighbours = {geometryIndex}; - if (!useSdf) + if (!useSdf && !_spheresRepresentation.enabled) container.addSphere(somaPosition + somaRotation * position, radius, mitochondrionMaterialId, NO_USER_DATA); @@ -1060,15 +1099,21 @@ void Neurons::_addSectionInternals(ThreadSafeContainer& container, const uint64_ const auto dstPosition = _animatedPosition(Vector4d(somaPosition + somaRotation * previousPosition, previousRadius), neuronId); - geometryIndex = container.addCone( - srcPosition, radius, dstPosition, previousRadius, mitochondrionMaterialId, useSdf, NO_USER_DATA, - neighbours, - Vector3f(radius * - _getDisplacementValue(DisplacementElement::morphology_mitochondrion_strength) * - 2.0, - radius * - _getDisplacementValue(DisplacementElement::morphology_mitochondrion_frequency), - 0.f)); + + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(srcPosition, radius, dstPosition, previousRadius, + mitochondrionMaterialId, NO_USER_DATA, + _spheresRepresentation.radius); + else + geometryIndex = container.addCone( + srcPosition, radius, dstPosition, previousRadius, mitochondrionMaterialId, useSdf, + NO_USER_DATA, neighbours, + Vector3f(radius * + _getDisplacementValue(DisplacementElement::morphology_mitochondrion_strength) * + 2.0, + radius * + _getDisplacementValue(DisplacementElement::morphology_mitochondrion_frequency), + 0.f)); mitochondriaVolume += coneVolume(length(position - previousPosition), radius, previousRadius); } @@ -1147,13 +1192,19 @@ void Neurons::_addAxonMyelinSheath(ThreadSafeContainer& container, const uint64_ _animatedPosition(Vector4d(somaPosition + somaRotation * Vector3d(dstPoint), dstRadius), neuronId); currentLength += length(dstPosition - previousPosition); - if (!useSdf) + if (!useSdf && !_spheresRepresentation.enabled) container.addSphere(dstPosition, srcRadius, myelinSteathMaterialId, NO_USER_DATA); - const auto geometryIndex = - container.addCone(dstPosition, dstRadius, previousPosition, previousRadius, myelinSteathMaterialId, - useSdf, NO_USER_DATA, neighbours, displacement); - neighbours.insert(geometryIndex); + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(dstPosition, dstRadius, previousPosition, previousRadius, + myelinSteathMaterialId, NO_USER_DATA, _spheresRepresentation.radius); + else + { + const auto geometryIndex = + container.addCone(dstPosition, dstRadius, previousPosition, previousRadius, myelinSteathMaterialId, + useSdf, NO_USER_DATA, neighbours, displacement); + neighbours.insert(geometryIndex); + } previousPosition = dstPosition; previousRadius = dstRadius; } @@ -1188,23 +1239,39 @@ void Neurons::_addSpine(ThreadSafeContainer& container, const uint64_t userData, auto middle = (target + origin) / 2.0; const double d = length(target - origin) / 2.0; middle += Vector3f(d * rnd1(), d * rnd1(), d * rnd1()); - const float spineMiddleRadius = spineSmallRadius + d * 0.1 * rnd1(); + const float spineMiddleRadius = std::max(0.01, spineSmallRadius + d * 0.1 * rnd1()); Neighbours neighbours; const bool useSdf = andCheck(static_cast(_details.realismLevel), static_cast(MorphologyRealismLevel::spine)); - if (!useSdf) + if (!useSdf && !_spheresRepresentation.enabled) + { container.addSphere(target, spineLargeRadius, SpineMaterialId, useSdf, userData); - neighbours.insert(container.addSphere(middle, spineMiddleRadius, SpineMaterialId, useSdf, userData, neighbours, - spineDisplacement)); + neighbours.insert(container.addSphere(middle, spineMiddleRadius, SpineMaterialId, useSdf, userData, neighbours, + spineDisplacement)); + } + if (middle != origin) - container.addCone(origin, spineSmallRadius, middle, spineMiddleRadius, SpineMaterialId, useSdf, userData, - neighbours, spineDisplacement); + { + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(origin, spineSmallRadius, middle, spineMiddleRadius, SpineMaterialId, userData, + _spheresRepresentation.radius); + else + container.addCone(origin, spineSmallRadius, middle, spineMiddleRadius, SpineMaterialId, useSdf, userData, + neighbours, spineDisplacement); + } + if (middle != target) - container.addCone(middle, spineMiddleRadius, target, spineLargeRadius, SpineMaterialId, useSdf, userData, - neighbours, spineDisplacement); + { + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(middle, spineMiddleRadius, target, spineLargeRadius, SpineMaterialId, userData, + _spheresRepresentation.radius); + else + container.addCone(middle, spineMiddleRadius, target, spineLargeRadius, SpineMaterialId, useSdf, userData, + neighbours, spineDisplacement); + } ++_nbSpines; } diff --git a/bioexplorer/backend/science/morphologies/Neurons.h b/bioexplorer/backend/science/morphologies/Neurons.h index 6becfed69..3bdb50939 100644 --- a/bioexplorer/backend/science/morphologies/Neurons.h +++ b/bioexplorer/backend/science/morphologies/Neurons.h @@ -73,6 +73,11 @@ class Neurons : public Morphologies void _buildSomasOnly(core::Model& model, common::ThreadSafeContainer& container, const NeuronSomaMap& somas); + double _addSoma(const uint64_t neuronId, const size_t somaMaterialId, const Section& section, + const core::Vector3d& somaPosition, const core::Quaterniond& somaRotation, const double somaRadius, + const uint64_t somaUserData, const double voltageScaling, common::ThreadSafeContainer& container, + common::Neighbours& somaMeighbours, common::Neighbours& sectionNeighbours); + void _buildOrientations(common::ThreadSafeContainer& container, const NeuronSomaMap& somas); void _buildMorphology(common::ThreadSafeContainer& container, const uint64_t neuronId, const NeuronSoma& soma, diff --git a/bioexplorer/backend/science/vasculature/Vasculature.cpp b/bioexplorer/backend/science/vasculature/Vasculature.cpp index 7e8912a33..a2a93b916 100644 --- a/bioexplorer/backend/science/vasculature/Vasculature.cpp +++ b/bioexplorer/backend/science/vasculature/Vasculature.cpp @@ -47,6 +47,10 @@ Vasculature::Vasculature(Scene& scene, const VasculatureDetails& details, const , _scene(scene) { _animationDetails = doublesToCellAnimationDetails(_details.animationParams); + _spheresRepresentation.enabled = _details.representation == VasculatureRepresentation::spheres || + _details.representation == VasculatureRepresentation::uniform_spheres; + _spheresRepresentation.uniform = _details.representation == VasculatureRepresentation::uniform_spheres; + _spheresRepresentation.radius = _spheresRepresentation.uniform ? _details.radiusMultiplier : 0.f; Timer chrono; _buildModel(callback); @@ -91,6 +95,7 @@ void Vasculature::_addGraphSection(ThreadSafeContainer& container, const Geometr const auto direction = dst - src; const float radius = std::min(length(direction) / 5.0, _getCorrectedRadius(maxRadius, _details.radiusMultiplier)); + container.addSphere(src, radius * 0.2, materialId, useSdf, userData); container.addCone(src, radius * 0.2, Vector3f(src + direction * 0.79), radius * 0.2, materialId, useSdf, userData); container.addCone(dst, 0.0, Vector3f(src + direction * 0.8), radius, materialId, useSdf, userData); @@ -109,15 +114,21 @@ void Vasculature::_addSimpleSection(ThreadSafeContainer& container, const Geomet const auto useSdf = andCheck(static_cast(_details.realismLevel), static_cast(VasculatureRealismLevel::section)); - if (!useSdf) + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(srcPoint, srcRadius, dstPoint, dstRadius, materialId, userData, + _spheresRepresentation.radius); + else { - container.addSphere(srcPoint, srcRadius, materialId, useSdf, userData); - container.addSphere(dstPoint, dstRadius, materialId, useSdf, userData); - } + if (!useSdf) + { + container.addSphere(srcPoint, srcRadius, materialId, useSdf, userData); + container.addSphere(dstPoint, dstRadius, materialId, useSdf, userData); + } - container.addCone(srcPoint, srcRadius, dstPoint, dstRadius, materialId, useSdf, userData, {}, - Vector3f(_getDisplacementValue(DisplacementElement::vasculature_segment_strength), - _getDisplacementValue(DisplacementElement::vasculature_segment_frequency), 0.f)); + container.addCone(srcPoint, srcRadius, dstPoint, dstRadius, materialId, useSdf, userData, {}, + Vector3f(_getDisplacementValue(DisplacementElement::vasculature_segment_strength), + _getDisplacementValue(DisplacementElement::vasculature_segment_frequency), 0.f)); + } } void Vasculature::_addDetailedSection(ThreadSafeContainer& container, const GeometryNodes& nodes, @@ -204,23 +215,30 @@ void Vasculature::_addDetailedSection(ThreadSafeContainer& container, const Geom const auto srcPosition = _animatedPosition(Vector4d(srcNode.position, srcRadius)); if (i == 0) - container.addSphere(srcPosition, srcRadius, materialId, useSdf, userData); + { + if (!_spheresRepresentation.enabled) + container.addSphere(srcPosition, srcRadius, materialId, useSdf, userData); + } else { const float dstRadius = _getCorrectedRadius((userData < radii.size() ? radii[userData] : dstNode.radius), _details.radiusMultiplier); const auto dstPosition = _animatedPosition(Vector4d(dstNode.position, dstRadius)); - geometryIndex = - container.addCone(srcPosition, srcRadius, dstPosition, dstRadius, materialId, useSdf, userData, - neighbours, - Vector3f(_getDisplacementValue(DisplacementElement::vasculature_segment_strength), - _getDisplacementValue(DisplacementElement::vasculature_segment_frequency), - 0.f)); - neighbours = {geometryIndex}; - - if (!useSdf) - neighbours.insert(container.addSphere(srcPosition, srcRadius, materialId, useSdf, userData)); + if (_spheresRepresentation.enabled) + container.addConeOfSpheres(srcPosition, srcRadius, dstPosition, dstRadius, materialId, userData, + _spheresRepresentation.radius); + else + { + geometryIndex = container.addCone( + srcPosition, srcRadius, dstPosition, dstRadius, materialId, useSdf, userData, neighbours, + Vector3f(_getDisplacementValue(DisplacementElement::vasculature_segment_strength), + _getDisplacementValue(DisplacementElement::vasculature_segment_frequency), 0.f)); + neighbours = {geometryIndex}; + + if (!useSdf) + neighbours.insert(container.addSphere(srcPosition, srcRadius, materialId, useSdf, userData)); + } } dstNode = srcNode; diff --git a/bioexplorer/backend/science/vasculature/Vasculature.h b/bioexplorer/backend/science/vasculature/Vasculature.h index dfe5e2272..08d9f4033 100644 --- a/bioexplorer/backend/science/vasculature/Vasculature.h +++ b/bioexplorer/backend/science/vasculature/Vasculature.h @@ -77,6 +77,7 @@ class Vasculature : public common::SDFGeometries void _buildModel(const core::LoaderProgress& callback, const doubles& radii = doubles()); const details::VasculatureDetails _details; + common::SpheresRepresentation _spheresRepresentation; core::Scene& _scene; uint64_t _nbNodes{0}; }; diff --git a/bioexplorer/pythonsdk/bioexplorer/bio_explorer.py b/bioexplorer/pythonsdk/bioexplorer/bio_explorer.py index 81670430a..4c15addc5 100644 --- a/bioexplorer/pythonsdk/bioexplorer/bio_explorer.py +++ b/bioexplorer/pythonsdk/bioexplorer/bio_explorer.py @@ -628,7 +628,7 @@ def add_sars_cov_2( rna_sequence = RNASequence( source=os.path.join(rna_folder, "sars-cov-2.rna"), protein_source=os.path.join(pdb_folder, "7bv1.pdb"), - shape=self.RNA_SHAPE_TREFOIL_KNOT, + shape=self.rna_shape.TREFOIL_KNOT, shape_params=params, values_range=Vector2(-8.0 * math.pi, 8.0 * math.pi), curve_params=Vector3(1.51, 1.12, 1.93), @@ -1428,9 +1428,9 @@ def add_rna_sequence(self, assembly_name, name, rna_sequence): values_range = Vector2(0.0, 2.0 * math.pi) if rna_sequence.values_range is None: # Defaults - if rna_sequence.shape == self.RNA_SHAPE_TORUS: + if rna_sequence.shape == self.rna_shape.TORUS: values_range = Vector2(0.0, 2.0 * math.pi) - elif rna_sequence.shape == self.RNA_SHAPE_TREFOIL_KNOT: + elif rna_sequence.shape == self.rna_shape.TREFOIL_KNOT: values_range = Vector2(0.0, 4.0 * math.pi) else: values_range = rna_sequence.values_range @@ -1438,9 +1438,9 @@ def add_rna_sequence(self, assembly_name, name, rna_sequence): curve_params = [1.0, 1.0, 1.0] if rna_sequence.shape_params is None: # Defaults - if rna_sequence.shape == self.RNA_SHAPE_TORUS: + if rna_sequence.shape == self.rna_shape.TORUS: curve_params = Vector3(0.5, 10.0, 0.0) - elif rna_sequence.shape == self.RNA_SHAPE_TREFOIL_KNOT: + elif rna_sequence.shape == self.rna_shape.TREFOIL_KNOT: curve_params = Vector3(2.5, 2.0, 2.2) else: diff --git a/bioexplorer/pythonsdk/bioexplorer/enums.py b/bioexplorer/pythonsdk/bioexplorer/enums.py index 525190e5a..4ce0c32ba 100644 --- a/bioexplorer/pythonsdk/bioexplorer/enums.py +++ b/bioexplorer/pythonsdk/bioexplorer/enums.py @@ -190,6 +190,8 @@ class VascularRepresentation(Enum): SEGMENT = 2 OPTIMIZED_SEGMENT = 3 BEZIER = 4 + SPHERES = 5 + UNIFORM_SPHERES = 6 class VascularColorScheme(Enum): @@ -217,6 +219,8 @@ class MorphologyRepresentation(Enum): BEZIER = 4 CONTOUR = 5 SURFACE = 6 + SPHERES = 7 + UNIFORM_SPHERES = 8 class MorphologyRealismLevel(Enum):