diff --git a/libs/README.md b/libs/README.md index 80155111..cf05a8c4 100644 --- a/libs/README.md +++ b/libs/README.md @@ -908,11 +908,19 @@ kg_viz.visualize_relationship_matrix(graph, output="html", file_path="relationsh #### Ontology Visualization ```python from semantica.visualization import OntologyVisualizer +from semantica.ontology import OntologyGenerator # Initialize ontology visualizer onto_viz = OntologyVisualizer(color_scheme="default") -# Visualize class hierarchy +# Option 1: Visualize from ontology generator result +ontology_generator = OntologyGenerator() +semantic_model = ontology_generator.generate_ontology(data) + +# Visualize semantic model (handles both ontology and semantic network) +onto_viz.visualize_semantic_model(semantic_model, output="html", file_path="semantic_model.html") + +# Option 2: Visualize class hierarchy directly ontology = { "classes": classes, "properties": properties @@ -921,6 +929,15 @@ ontology = { # Hierarchy tree visualization onto_viz.visualize_hierarchy(ontology, output="html", file_path="ontology_hierarchy.html") +# Option 3: Visualize from semantic network (auto-extracts classes) +from semantica.semantic_extract import SemanticNetworkExtractor +extractor = SemanticNetworkExtractor() +semantic_network = extractor.extract_network(text) + +# Can visualize directly - will extract classes automatically +onto_viz.visualize_hierarchy({"semantic_network": semantic_network}, + output="html", file_path="ontology_from_network.html") + # Property graph visualization onto_viz.visualize_properties(ontology, output="html", file_path="ontology_properties.html") @@ -988,7 +1005,7 @@ from semantica.visualization import SemanticNetworkVisualizer # Initialize semantic network visualizer sem_net_viz = SemanticNetworkVisualizer() -# Visualize semantic network +# Option 1: Visualize SemanticNetwork dataclass object from semantica.semantic_extract import SemanticNetworkExtractor extractor = SemanticNetworkExtractor() semantic_network = extractor.extract_network(text) @@ -996,6 +1013,19 @@ semantic_network = extractor.extract_network(text) # Network graph sem_net_viz.visualize_network(semantic_network, output="html", file_path="semantic_network.html") +# Option 2: Visualize from dictionary format +semantic_network_dict = { + "nodes": [{"id": "n1", "label": "Node 1", "type": "Entity"}], + "edges": [{"source": "n1", "target": "n2", "label": "relatedTo"}] +} +sem_net_viz.visualize_network(semantic_network_dict, output="html", file_path="semantic_network.html") + +# Option 3: Visualize from semantic model (ontology generator result) +from semantica.ontology import OntologyGenerator +generator = OntologyGenerator() +semantic_model = generator.generate_ontology(data) +sem_net_viz.visualize_network(semantic_model.semantic_network, output="html", file_path="semantic_model_network.html") + # Node type distribution sem_net_viz.visualize_node_types(semantic_network, output="html", file_path="node_types.html") diff --git a/libs/semantica/visualization/ontology_visualizer.py b/libs/semantica/visualization/ontology_visualizer.py index 591a06e8..fde1f913 100644 --- a/libs/semantica/visualization/ontology_visualizer.py +++ b/libs/semantica/visualization/ontology_visualizer.py @@ -68,7 +68,8 @@ class OntologyVisualizer: Visualize class hierarchy as tree. Args: - ontology: Ontology dictionary with classes + ontology: Ontology dictionary with classes, or SemanticNetwork object, + or ontology generator result output: Output type ("interactive", "html", "png", "svg", "dot") file_path: Output file path **options: Additional options @@ -78,10 +79,24 @@ class OntologyVisualizer: """ self.logger.info("Visualizing ontology class hierarchy") - classes = ontology.get("classes", []) + # Handle different input formats + if hasattr(ontology, "classes"): + # OntologyGenerator result object + classes = ontology.classes if hasattr(ontology, "classes") else [] + elif isinstance(ontology, dict): + classes = ontology.get("classes", ontology.get("class_definitions", [])) + else: + classes = [] + + # If no classes, try to extract from semantic network + if not classes and isinstance(ontology, dict): + # Check if it's a semantic model or semantic network + semantic_network = ontology.get("semantic_network", ontology.get("network")) + if semantic_network: + classes = self._extract_classes_from_semantic_network(semantic_network) if not classes: - raise ProcessingError("No classes found in ontology") + raise ProcessingError("No classes found in ontology. Please provide classes or a semantic network.") # If output is dot and graphviz is available, use it if output == "dot" and graphviz is not None and file_path: @@ -103,7 +118,7 @@ class OntologyVisualizer: Visualize property graph showing properties and their domains/ranges. Args: - ontology: Ontology dictionary + ontology: Ontology dictionary, SemanticNetwork, or ontology generator result output: Output type file_path: Output file path **options: Additional options @@ -113,8 +128,22 @@ class OntologyVisualizer: """ self.logger.info("Visualizing ontology properties") - properties = ontology.get("properties", []) - classes = ontology.get("classes", []) + # Handle different input formats + if hasattr(ontology, "properties"): + properties = ontology.properties if hasattr(ontology, "properties") else [] + classes = ontology.classes if hasattr(ontology, "classes") else [] + elif isinstance(ontology, dict): + properties = ontology.get("properties", ontology.get("property_definitions", [])) + classes = ontology.get("classes", ontology.get("class_definitions", [])) + else: + properties = [] + classes = [] + + # If no properties, try to extract from semantic network + if not properties and isinstance(ontology, dict): + semantic_network = ontology.get("semantic_network", ontology.get("network")) + if semantic_network: + properties = self._extract_properties_from_semantic_network(semantic_network) if not properties: raise ProcessingError("No properties found in ontology") @@ -350,17 +379,129 @@ class OntologyVisualizer: def _calculate_class_depth(self, cls: Dict[str, Any], all_classes: List[Dict[str, Any]]) -> int: """Calculate depth of class in hierarchy.""" - parent = cls.get("parent") or cls.get("subClassOf") + parent = cls.get("parent") or cls.get("subClassOf") or cls.get("superClassOf") if not parent: return 1 # Find parent class for p_cls in all_classes: - if (p_cls.get("name") or p_cls.get("uri", "")) == parent: + cls_name = p_cls.get("name") or p_cls.get("uri") or p_cls.get("label", "") + if cls_name == parent: return 1 + self._calculate_class_depth(p_cls, all_classes) return 1 + def _extract_classes_from_semantic_network(self, semantic_network: Any) -> List[Dict[str, Any]]: + """Extract class definitions from semantic network.""" + classes = [] + + # Handle SemanticNetwork dataclass + if hasattr(semantic_network, "nodes"): + # Group nodes by type to form classes + type_groups = {} + for node in semantic_network.nodes: + node_type = node.type if hasattr(node, "type") else "Unknown" + if node_type not in type_groups: + type_groups[node_type] = [] + type_groups[node_type].append(node) + + # Create class definitions + for node_type, nodes in type_groups.items(): + classes.append({ + "name": node_type, + "label": node_type, + "uri": f"#{node_type}", + "instances": len(nodes), + "properties": list(set( + prop for node in nodes + for prop in (node.properties.keys() if hasattr(node, "properties") else []) + )) + }) + + # Handle dictionary format + elif isinstance(semantic_network, dict): + nodes = semantic_network.get("nodes", []) + type_groups = {} + for node in nodes: + node_type = node.get("type") if isinstance(node, dict) else ( + node.type if hasattr(node, "type") else "Unknown" + ) + if node_type not in type_groups: + type_groups[node_type] = [] + type_groups[node_type].append(node) + + for node_type, nodes in type_groups.items(): + classes.append({ + "name": node_type, + "label": node_type, + "uri": f"#{node_type}", + "instances": len(nodes) + }) + + return classes + + def visualize_semantic_model( + self, + semantic_model: Any, + output: str = "interactive", + file_path: Optional[Union[str, Path]] = None, + **options + ) -> Optional[Any]: + """ + Visualize semantic model from ontology generator. + + This method extracts and visualizes both the ontology structure + and the underlying semantic network that generated it. + + Args: + semantic_model: Semantic model from OntologyGenerator or semantic network + output: Output type + file_path: Output file path + **options: Additional options + + Returns: + Visualization figure or None + """ + self.logger.info("Visualizing semantic model") + + # Handle OntologyGenerator result + if hasattr(semantic_model, "semantic_network"): + # Visualize the semantic network + from .semantic_network_visualizer import SemanticNetworkVisualizer + sem_net_viz = SemanticNetworkVisualizer(**self.config) + return sem_net_viz.visualize_network( + semantic_model.semantic_network, + output=output, + file_path=file_path, + **options + ) + + # Handle dictionary format with semantic_network + elif isinstance(semantic_model, dict): + if "semantic_network" in semantic_model: + from .semantic_network_visualizer import SemanticNetworkVisualizer + sem_net_viz = SemanticNetworkVisualizer(**self.config) + return sem_net_viz.visualize_network( + semantic_model["semantic_network"], + output=output, + file_path=file_path, + **options + ) + # Otherwise treat as ontology + else: + return self.visualize_structure(semantic_model, output, file_path, **options) + + # Handle direct semantic network + else: + from .semantic_network_visualizer import SemanticNetworkVisualizer + sem_net_viz = SemanticNetworkVisualizer(**self.config) + return sem_net_viz.visualize_network( + semantic_model, + output=output, + file_path=file_path, + **options + ) + def _visualize_hierarchy_plotly( self, hierarchy: Dict[str, List[str]], @@ -522,6 +663,46 @@ class OntologyVisualizer: kg_viz = KGVisualizer(**self.config) return kg_viz._visualize_network_plotly(nodes, edges, output, file_path, **options) + def _extract_properties_from_semantic_network(self, semantic_network: Any) -> List[Dict[str, Any]]: + """Extract property definitions from semantic network.""" + properties = [] + + # Handle SemanticNetwork dataclass + if hasattr(semantic_network, "edges"): + # Extract unique edge labels as properties + property_types = set() + for edge in semantic_network.edges: + edge_label = edge.label if hasattr(edge, "label") else "" + if edge_label and edge_label not in property_types: + property_types.add(edge_label) + properties.append({ + "name": edge_label, + "label": edge_label, + "uri": f"#{edge_label}", + "domain": "Thing", # Default domain + "range": "Thing" # Default range + }) + + # Handle dictionary format + elif isinstance(semantic_network, dict): + edges = semantic_network.get("edges", semantic_network.get("relationships", [])) + property_types = set() + for edge in edges: + edge_label = edge.get("label") if isinstance(edge, dict) else ( + edge.label if hasattr(edge, "label") else "" + ) + if edge_label and edge_label not in property_types: + property_types.add(edge_label) + properties.append({ + "name": edge_label, + "label": edge_label, + "uri": f"#{edge_label}", + "domain": edge.get("domain", "Thing") if isinstance(edge, dict) else "Thing", + "range": edge.get("range", "Thing") if isinstance(edge, dict) else "Thing" + }) + + return properties + def _visualize_structure_plotly( self, nodes: List[Dict[str, Any]], diff --git a/libs/semantica/visualization/semantic_network_visualizer.py b/libs/semantica/visualization/semantic_network_visualizer.py index 084c456b..d46ec3e9 100644 --- a/libs/semantica/visualization/semantic_network_visualizer.py +++ b/libs/semantica/visualization/semantic_network_visualizer.py @@ -44,8 +44,14 @@ class SemanticNetworkVisualizer: """ Visualize semantic network. + Supports multiple input formats: + - SemanticNetwork dataclass object + - Dictionary with 'nodes' and 'edges' keys + - Semantic model from ontology generator + - Entities and relationships lists + Args: - semantic_network: SemanticNetwork object + semantic_network: SemanticNetwork object, dict, or semantic model output: Output type file_path: Output file path **options: Additional options @@ -57,40 +63,103 @@ class SemanticNetworkVisualizer: # Extract nodes and edges from semantic network nodes = [] - if hasattr(semantic_network, "nodes"): + edges = [] + + # Handle SemanticNetwork dataclass + if hasattr(semantic_network, "nodes") and hasattr(semantic_network, "edges"): for node in semantic_network.nodes: nodes.append({ - "id": node.id, - "label": node.label, - "type": node.type, - "metadata": node.metadata + "id": getattr(node, "id", ""), + "label": getattr(node, "label", ""), + "type": getattr(node, "type", "entity"), + "metadata": getattr(node, "metadata", {}) or {}, + "properties": getattr(node, "properties", {}) or {} }) - elif isinstance(semantic_network, dict): - for node in semantic_network.get("nodes", []): - nodes.append({ - "id": node.get("id", ""), - "label": node.get("label", ""), - "type": node.get("type", ""), - "metadata": node.get("metadata", {}) - }) - - edges = [] - if hasattr(semantic_network, "edges"): + for edge in semantic_network.edges: edges.append({ - "source": edge.source, - "target": edge.target, - "label": edge.label, - "metadata": edge.metadata + "source": getattr(edge, "source", ""), + "target": getattr(edge, "target", ""), + "label": getattr(edge, "label", ""), + "type": getattr(edge, "label", ""), + "metadata": getattr(edge, "metadata", {}) or {}, + "properties": getattr(edge, "properties", {}) or {} }) + + # Handle dictionary format elif isinstance(semantic_network, dict): - for edge in semantic_network.get("edges", []): - edges.append({ - "source": edge.get("source", ""), - "target": edge.get("target", ""), - "label": edge.get("label", ""), - "metadata": edge.get("metadata", {}) - }) + # Check if it's a semantic model from ontology generator + if "semantic_network" in semantic_network: + semantic_network = semantic_network["semantic_network"] + + # Extract nodes + network_nodes = semantic_network.get("nodes", []) + for node in network_nodes: + if isinstance(node, dict): + nodes.append({ + "id": node.get("id", node.get("uri", "")), + "label": node.get("label", node.get("name", "")), + "type": node.get("type", node.get("class", "entity")), + "metadata": node.get("metadata", {}), + "properties": node.get("properties", {}) + }) + elif hasattr(node, "id"): + nodes.append({ + "id": getattr(node, "id", ""), + "label": getattr(node, "label", ""), + "type": getattr(node, "type", "entity"), + "metadata": getattr(node, "metadata", {}) or {}, + "properties": getattr(node, "properties", {}) or {} + }) + + # Extract edges + network_edges = semantic_network.get("edges", semantic_network.get("relationships", [])) + for edge in network_edges: + if isinstance(edge, dict): + edges.append({ + "source": edge.get("source", edge.get("subject", "")), + "target": edge.get("target", edge.get("object", "")), + "label": edge.get("label", edge.get("predicate", edge.get("type", ""))), + "type": edge.get("type", edge.get("predicate", "")), + "metadata": edge.get("metadata", {}), + "properties": edge.get("properties", {}) + }) + elif hasattr(edge, "source"): + edges.append({ + "source": getattr(edge, "source", ""), + "target": getattr(edge, "target", ""), + "label": getattr(edge, "label", ""), + "type": getattr(edge, "label", ""), + "metadata": getattr(edge, "metadata", {}) or {}, + "properties": getattr(edge, "properties", {}) or {} + }) + + # Handle entities/relationships format (for semantic models) + elif isinstance(semantic_network, (list, tuple)): + # Assume it's a list of entities/relationships + for item in semantic_network: + if isinstance(item, dict): + if "source" in item or "subject" in item: + edges.append({ + "source": item.get("source", item.get("subject", "")), + "target": item.get("target", item.get("object", "")), + "label": item.get("label", item.get("predicate", item.get("type", ""))), + "type": item.get("type", ""), + "metadata": item.get("metadata", {}) + }) + else: + nodes.append({ + "id": item.get("id", item.get("uri", "")), + "label": item.get("label", item.get("name", "")), + "type": item.get("type", "entity"), + "metadata": item.get("metadata", {}) + }) + + if not nodes and not edges: + raise ProcessingError( + "Could not extract nodes and edges from semantic network. " + "Please provide a SemanticNetwork object, dict with 'nodes'/'edges', or semantic model." + ) # Use KG visualizer for network visualization from .kg_visualizer import KGVisualizer