Skip to content

Commit 0cbe740

Browse files
authored
Merge pull request #43 from Axiomatic-AI/axtract_helper
now working :)
2 parents 5e5a259 + 1401725 commit 0cbe740

File tree

5 files changed

+193
-65
lines changed

5 files changed

+193
-65
lines changed

src/axiomatic/axtract/axtract_report.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .relation_graph import generate_relation_graph
2-
from .. import EquationExtractionResponse
2+
from .. import EquationProcessingResponse
33
import os
44
import re
55

@@ -190,7 +190,7 @@
190190
"""
191191

192192

193-
def create_report(report_data: EquationExtractionResponse, report_path: str = "./report.html"):
193+
def create_report(report_data: EquationProcessingResponse, report_path: str = "./report.html"):
194194
"""
195195
Creates an HTML report for the extracted equations.
196196
"""

src/axiomatic/axtract/interactive_table.py

Lines changed: 35 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
from IPython.display import display # type: ignore
33
import json # type: ignore
44
import os # type: ignore
5-
from .. import EquationExtractionResponse, VariableRequirement
5+
from .. import EquationProcessingResponse, VariableRequirement
6+
from typing import Dict, Any
67

78

89
def _find_symbol(name, variable_dict):
@@ -33,15 +34,15 @@ def _requirements_from_table(results, variable_dict):
3334
return requirements
3435

3536

36-
def interactive_table(loaded_equations, file_path="./custom_presets.json"):
37+
def interactive_table(loaded_equations: EquationProcessingResponse, file_path: str = "./custom_presets.json"):
3738
"""
3839
Creates an interactive table for IMAGING_TELESCOPE,
3940
PAYLOAD, and user-defined custom templates.
4041
Adds or deletes rows, and can save custom templates persistently in JSON.
4142
4243
Parameters
4344
----------
44-
loaded_equations : EquationExtractionResponse
45+
loaded_equations : EquationProcessingResponse
4546
The extracted equations containing variable information
4647
file_path : str, optional
4748
JSON file path where we load and save user-created custom templates.
@@ -55,49 +56,35 @@ def interactive_table(loaded_equations, file_path="./custom_presets.json"):
5556
# ---------------------------------------------------------------
5657
# 1) Define built-in templates and units directly inside the function
5758
# ---------------------------------------------------------------
58-
IMAGING_TELESCOPE_template = {
59-
"Resolution (panchromatic)": 0,
60-
"Ground sampling distance (panchromatic)": 0,
61-
"Resolution (multispectral)": 0,
62-
"Ground sampling distance (multispectral)": 0,
63-
"Altitude": 0,
64-
"Half field of view": 0,
65-
"Mirror aperture": 0,
66-
"F-number": 0,
67-
"Focal length": 0,
68-
"Pixel size (panchromatic)": 0,
69-
"Pixel size (multispectral)": 0,
70-
"Swath width": 0,
71-
}
7259

7360
IMAGING_TELESCOPE = {
74-
"Resolution (panchromatic)": 1.23529,
75-
"Ground sampling distance (panchromatic)": 0.61765,
76-
"Resolution (multispectral)": 1.81176,
77-
"Ground sampling distance (multispectral)": 0.90588,
61+
"Resolved Ground Detail, Panchromatic": 1.23529,
62+
"Ground Sample Distance, Panchromatic": 0.61765,
63+
"Resolved Ground Detail, Multispectral": 1.81176,
64+
"Ground Sample Distance, Multispectral": 0.90588,
7865
"Altitude": 420000,
79-
"Half field of view": 0.017104227,
80-
"Mirror aperture": 0.85,
81-
"F-number": 6.0,
66+
"Horizontal Field of View": 0.017104227,
67+
"Aperture diameter": 0.85,
68+
"f-number": 6.0,
8269
"Focal length": 5.1,
83-
"Pixel size (panchromatic)": 7.5e-6,
84-
"Pixel size (multispectral)": 11e-6,
85-
"Swath width": 14368.95,
70+
"Pixel pitch": 7.5e-6,
71+
"Pixel pitch of the multispectral sensor": 11e-6,
72+
"Swath Width": 14368.95,
8673
}
8774

8875
IMAGING_TELESCOPE_UNITS = {
89-
"Resolution (panchromatic)": "m",
90-
"Ground sampling distance (panchromatic)": "m",
91-
"Resolution (multispectral)": "m",
92-
"Ground sampling distance (multispectral)": "m",
76+
"Resolved Ground Detail, Panchromatic": "m",
77+
"Ground Sample Distance, Panchromatic": "m",
78+
"Resolved Ground Detail, Multispectral": "m",
79+
"Ground Sample Distance, Multispectral": "m",
9380
"Altitude": "m",
94-
"Half field of view": "rad",
95-
"Mirror aperture": "m",
96-
"F-number": "dimensionless",
81+
"Horizontal Field of View": "rad",
82+
"Aperture diameter": "m",
83+
"f-number": "dimensionless",
9784
"Focal length": "m",
98-
"Pixel size (panchromatic)": "m",
99-
"Pixel size (multispectral)": "m",
100-
"Swath width": "m",
85+
"Pixel pitch": "m",
86+
"Pixel pitch of the multispectral sensor": "m",
87+
"Swath Width": "m",
10188
}
10289

10390
PAYLOAD_1 = {
@@ -120,7 +107,6 @@ def interactive_table(loaded_equations, file_path="./custom_presets.json"):
120107
preset_options_dict = {
121108
"Select a template": [],
122109
"IMAGING TELESCOPE": list(IMAGING_TELESCOPE.keys()),
123-
"IMAGING TELESCOPE template": list(IMAGING_TELESCOPE_template.keys()),
124110
"PAYLOAD": list(PAYLOAD_1.keys()),
125111
}
126112

@@ -170,7 +156,7 @@ def save_custom_presets(custom_data, file_path):
170156
name_label_width = ["150px"]
171157

172158
# Dictionary to keep track of row widget references
173-
value_widgets = {}
159+
value_widgets: Dict[str, Any] = {}
174160

175161
# ---------------------------------------------------------------
176162
# 6) display_table(change): Re-populate rows when user selects a template
@@ -272,11 +258,6 @@ def submit_values(_):
272258
result["values"] = updated_values
273259
requirements_result[0] = _requirements_from_table(result, variable_dict)
274260

275-
# Display a confirmation message
276-
with message_output:
277-
message_output.clear_output()
278-
print("Requirements submitted successfully!")
279-
280261
return requirements_result[0]
281262

282263
# ---------------------------------------------------------------
@@ -382,14 +363,14 @@ def save_requirements(_):
382363
return requirements_result
383364

384365

385-
def _create_variable_dict(equation_response: EquationExtractionResponse) -> dict:
366+
def _create_variable_dict(equation_response: EquationProcessingResponse) -> dict:
386367
"""
387-
Creates a variable dictionary from an EquationExtractionResponse object
368+
Creates a variable dictionary from an EquationProcessingResponse object
388369
for use with the interactive_table function.
389370
390371
Parameters
391372
----------
392-
equation_response : EquationExtractionResponse
373+
equation_response : EquationProcessingResponse
393374
The equation extraction response containing equations and their symbols
394375
395376
Returns
@@ -406,9 +387,13 @@ def _create_variable_dict(equation_response: EquationExtractionResponse) -> dict
406387

407388
# Iterate through all equations and their symbols
408389
for equation in equation_response.equations:
409-
for symbol in equation.latex_symbols:
390+
wolfram_symbols = equation.wolfram_symbols
391+
latex_symbols = [equation.latex_symbols[i].key for i in range(len(equation.latex_symbols))]
392+
names = [equation.latex_symbols[i].value for i in range(len(equation.latex_symbols))]
393+
394+
for symbol, name in zip(wolfram_symbols, names):
410395
# Only add if not already present (avoid duplicates)
411-
if symbol.key not in variable_dict:
412-
variable_dict[symbol.key] = {"name": symbol.value}
396+
if symbol not in variable_dict:
397+
variable_dict[symbol] = {"name": name}
413398

414399
return variable_dict

src/axiomatic/axtract/relation_graph.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from typing import List
2-
from .. import EquationExtraction
2+
from ..types.response_equation import ResponseEquation
33
from pyvis.network import Network # type: ignore
44

55

@@ -20,7 +20,7 @@ def normalize_latex_symbol(symbol: str) -> str:
2020
return symbol
2121

2222

23-
def generate_relation_graph(equations: List[EquationExtraction]) -> str:
23+
def generate_relation_graph(equations: List[ResponseEquation]) -> str:
2424
"""
2525
Generates HTML code for a bipartite graph visualization.
2626
Green nodes represent equations, red nodes represent variables.
@@ -41,7 +41,7 @@ def generate_relation_graph(equations: List[EquationExtraction]) -> str:
4141
# Add equation nodes (green) and variable nodes (red)
4242
for eq in equations:
4343
# Add equation node with unique identifier
44-
eq_name = f"Eq: {eq.name} ({eq.id})" # Add ID to make each node unique
44+
eq_name = f"Eq: {eq.name}" # Add ID to make each node unique
4545
net.add_node(
4646
eq_name,
4747
label=eq.name,

src/axiomatic/axtract/validation_results.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
def display_full_results(validation_results, requirements=None, show_hypergraph=True):
88
"""Display equation validation results optimized for dark theme notebooks."""
9-
validations = validation_results.get("validations", {})
9+
# If validation_results is already a dict, use it directly
10+
validations = validation_results if isinstance(validation_results, dict) else validation_results.validations
1011

1112
matching = []
1213
non_matching = []
@@ -15,13 +16,16 @@ def display_full_results(validation_results, requirements=None, show_hypergraph=
1516
equation_data = {
1617
"name": eq_name,
1718
"latex": value.get("original_format", ""),
18-
"lhs": value.get("lhs_value"),
19-
"rhs": value.get("rhs_value"),
20-
"diff": abs(value.get("lhs_value", 0) - value.get("rhs_value", 0)),
21-
"percent_diff": abs(value.get("lhs_value", 0) - value.get("rhs_value", 0))
22-
/ max(abs(value.get("rhs_value", 0)), 1e-10)
19+
"lhs": float(value.get("lhs_value", 0)),
20+
"rhs": float(value.get("rhs_value", 0)),
21+
"diff": abs(float(value.get("lhs_value", 0)) - float(value.get("rhs_value", 0))),
22+
"percent_diff": abs(float(value.get("lhs_value", 0)) - float(value.get("rhs_value", 0)))
23+
/ max(abs(float(value.get("rhs_value", 0))), 1e-10)
2324
* 100,
24-
"used_values": value.get("used_values", {}),
25+
"used_values": {
26+
k: float(v.split("*^")[0]) * (10 ** float(v.split("*^")[1])) if "*^" in v else float(v)
27+
for k, v in value.get("used_values", {}).items()
28+
},
2529
}
2630
if value.get("is_valid"):
2731
matching.append(equation_data)

src/axiomatic/client.py

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
import requests # type: ignore
55
import os
66
import time
7-
import json
8-
from typing import Dict
7+
from typing import Dict, Optional, Sequence
98

109
from .base_client import BaseClient, AsyncBaseClient
11-
from .types.parse_response import ParseResponse
10+
from . import ParseResponse, EquationProcessingResponse
11+
from .axtract.axtract_report import create_report
12+
from .axtract.validation_results import display_full_results
13+
from .types.variable_requirement import VariableRequirement
14+
from .types.equation_validation_result import EquationValidationResult
1215

1316

1417
class Axiomatic(BaseClient):
@@ -112,6 +115,142 @@ def load_parsed_pdf(self, path: str) -> ParseResponse:
112115
inline_equations=inline_equations,
113116
)
114117

118+
class AxtractHelper:
119+
_ax_client: Axiomatic
120+
121+
def __init__(self, ax_client: Axiomatic):
122+
"""Initialize the AxtractHelper with an Axiomatic client.
123+
124+
Args:
125+
ax_client (Axiomatic): The Axiomatic client instance to use for API calls
126+
"""
127+
self._ax_client = ax_client
128+
129+
def create_report(self, response: EquationProcessingResponse, path: str):
130+
"""Create a report from equation extraction results.
131+
132+
Args:
133+
response (EquationExtractionResponse): The extracted equations and their metadata
134+
path (str): Directory path where the report should be saved
135+
"""
136+
create_report(response, path)
137+
138+
def analyze_equations(
139+
self,
140+
file_path: Optional[str] = None,
141+
url_path: Optional[str] = None,
142+
parsed_paper: Optional[ParseResponse] = None,
143+
) -> Optional[EquationProcessingResponse]:
144+
"""Analyze and extract equations from a scientific document.
145+
146+
This method supports three input methods:
147+
1. Local PDF file path
148+
2. URL to a PDF (with special handling for arXiv URLs)
149+
3. Pre-parsed paper data
150+
151+
Args:
152+
file_path (Optional[str]): Path to a local PDF file
153+
url_path (Optional[str]): URL to a PDF file (supports arXiv links)
154+
parsed_paper (Optional[ParseResponse]): Pre-parsed paper data
155+
156+
Returns:
157+
Optional[EquationExtractionResponse]: Extracted equations and their metadata.
158+
Returns None if no valid input is provided.
159+
160+
Examples:
161+
# From local file
162+
client.analyze_equations(file_path="path/to/paper.pdf")
163+
164+
# From URL
165+
client.analyze_equations(url_path="https://arxiv.org/pdf/2203.00001.pdf")
166+
167+
# From parsed paper
168+
client.analyze_equations(parsed_paper=parsed_data)
169+
"""
170+
if file_path:
171+
with open(file_path, "rb") as pdf_file:
172+
parsed_document = self._ax_client.document.parse(file=pdf_file)
173+
print("We are almost there")
174+
response = self._ax_client.document.equation.process(
175+
markdown=parsed_document.markdown,
176+
interline_equations=parsed_document.interline_equations,
177+
inline_equations=parsed_document.inline_equations
178+
)
179+
180+
elif url_path:
181+
if "arxiv" in url_path and "abs" in url_path:
182+
url_path = url_path.replace("abs", "pdf")
183+
url_file = requests.get(url_path)
184+
from io import BytesIO
185+
pdf_stream = BytesIO(url_file.content)
186+
parsed_document = self._ax_client.document.parse(file=url_file.content)
187+
print("We are almost there")
188+
response = self._ax_client.document.equation.process(
189+
markdown=parsed_document.markdown,
190+
interline_equations=parsed_document.interline_equations,
191+
inline_equations=parsed_document.inline_equations
192+
)
193+
194+
elif parsed_paper:
195+
response = EquationProcessingResponse.model_validate(
196+
self._ax_client.document.equation.process(**parsed_paper.model_dump()).model_dump()
197+
)
198+
199+
else:
200+
print("Please provide either a file path or a URL to analyze.")
201+
return None
202+
203+
return response
204+
205+
def validate_equations(
206+
self,
207+
requirements: Sequence[VariableRequirement],
208+
loaded_equations: EquationProcessingResponse,
209+
include_internal_model: bool = False,
210+
) -> EquationValidationResult:
211+
"""Validate equations against a set of variable requirements.
212+
213+
Args:
214+
requirements: List of variable requirements to validate
215+
loaded_equations: Previously processed equations to validate
216+
show_hypergraph: Whether to display the validation results graph (default: True)
217+
include_internal_model: Whether to include internal model equations in validation (default: False)
218+
219+
Returns:
220+
EquationValidationResult containing the validation results
221+
"""
222+
# equations_dict = loaded_equations.model_dump() if hasattr(loaded_equations, 'model_dump') else loaded_equations.dict()
223+
224+
api_response = self._ax_client.document.equation.validate(
225+
variables=requirements,
226+
paper_equations=loaded_equations,
227+
include_internal_model=include_internal_model
228+
)
229+
230+
return api_response
231+
232+
233+
def display_full_results(self, api_response: EquationValidationResult, user_choice):
234+
display_full_results(api_response, user_choice)
235+
236+
237+
def set_numerical_requirements(self, extracted_equations: EquationProcessingResponse):
238+
"""Launch an interactive interface for setting numerical requirements for equations.
239+
240+
This method opens an interactive table interface where users can specify
241+
requirements for variables found in the extracted equations.
242+
243+
Args:
244+
extracted_equations: The equations to set requirements for
245+
246+
Returns:
247+
The requirements set through the interactive interface
248+
"""
249+
from .axtract.interactive_table import interactive_table
250+
251+
result = interactive_table(extracted_equations)
252+
return result
253+
115254

116255
class ToolsHelper:
117256
_ax_client: Axiomatic

0 commit comments

Comments
 (0)