raylim Claude commited on
Commit
a2b6947
·
unverified ·
1 Parent(s): 49fbf68

Add comprehensive sex and tissue site parameter support

Browse files

This commit enhances the Aeon inference pipeline with complete support for sex and tissue site parameters across the entire application:

- Add sex and tissue site encoding functions with CSV-based mapping
- Update Aeon model to load correct target mappings from metadata
- Add CLI arguments for sex and tissue site in gradio_app.py
- Add validation for sex and tissue site in settings
- Update dependencies: add lightning, seaborn, and statsmodels
- Fix tissue site map file path references

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

pyproject.toml CHANGED
@@ -10,11 +10,14 @@ readme = "README.md"
10
  requires-python = ">=3.10"
11
  dependencies = [
12
  "gradio>=5.49.0",
 
13
  "loguru>=0.7.3",
14
  "memory-profiler>=0.61.0",
15
  "mussel[torch-gpu]",
16
  "paladin",
 
17
  "spaces>=0.30.0",
 
18
  ]
19
 
20
  [project.scripts]
 
10
  requires-python = ">=3.10"
11
  dependencies = [
12
  "gradio>=5.49.0",
13
+ "lightning>=2.6.0",
14
  "loguru>=0.7.3",
15
  "memory-profiler>=0.61.0",
16
  "mussel[torch-gpu]",
17
  "paladin",
18
+ "seaborn>=0.13.2",
19
  "spaces>=0.30.0",
20
+ "statsmodels>=0.14.6",
21
  ]
22
 
23
  [project.scripts]
src/mosaic/analysis.py CHANGED
@@ -181,7 +181,7 @@ def _run_aeon_inference(features, site_type, num_workers, sex=None, tissue_site_
181
  logger.info("Running Aeon for cancer subtype inference")
182
  aeon_results, _ = run_aeon(
183
  features=features,
184
- model_path="data/aeon_model.pkl",
185
  metastatic=(site_type == "Metastatic"),
186
  batch_size=8,
187
  num_workers=num_workers,
@@ -520,17 +520,15 @@ def analyze_slide(
520
  logger.warning(traceback.format_exc())
521
 
522
  # Convert sex and tissue_site to indices for Aeon model
 
 
523
  sex_idx = None
524
- if sex and sex != "Unknown":
525
- sex_idx = 0 if sex == "Male" else 1
526
-
527
  tissue_site_idx = None
528
- if tissue_site and tissue_site != "Unknown":
529
- from mosaic.inference.data import get_tissue_site_map
530
- tissue_site_map = get_tissue_site_map()
531
- tissue_site_idx = tissue_site_map.get(tissue_site)
532
- if tissue_site_idx is None:
533
- logger.warning(f"Unknown tissue site: {tissue_site}")
534
 
535
  if is_logged_in:
536
  logger.info("Using 300s GPU allocation (logged-in user)")
 
181
  logger.info("Running Aeon for cancer subtype inference")
182
  aeon_results, _ = run_aeon(
183
  features=features,
184
+ model_path="data/aeon_model_new.pkl",
185
  metastatic=(site_type == "Metastatic"),
186
  batch_size=8,
187
  num_workers=num_workers,
 
520
  logger.warning(traceback.format_exc())
521
 
522
  # Convert sex and tissue_site to indices for Aeon model
523
+ from mosaic.inference.data import encode_sex, encode_tissue_site
524
+
525
  sex_idx = None
526
+ if sex:
527
+ sex_idx = encode_sex(sex)
528
+
529
  tissue_site_idx = None
530
+ if tissue_site:
531
+ tissue_site_idx = encode_tissue_site(tissue_site)
 
 
 
 
532
 
533
  if is_logged_in:
534
  logger.info("Using 300s GPU allocation (logged-in user)")
src/mosaic/gradio_app.py CHANGED
@@ -21,6 +21,7 @@ from mosaic.ui.utils import (
21
  validate_settings,
22
  IHC_SUBTYPES,
23
  SETTINGS_COLUMNS,
 
24
  )
25
  from mosaic.analysis import analyze_slide
26
 
@@ -43,10 +44,10 @@ def download_and_process_models():
43
  "data/paladin_model_map.csv",
44
  )
45
  cancer_subtypes = model_map["cancer_subtype"].unique().tolist()
46
- cancer_subtype_name_map = {
 
47
  f"{get_oncotree_code_name(code)} ({code})": code for code in cancer_subtypes
48
- }
49
- cancer_subtype_name_map["Unknown"] = "UNK"
50
  reversed_cancer_subtype_name_map = {
51
  value: key for key, value in cancer_subtype_name_map.items()
52
  }
@@ -99,6 +100,19 @@ def main():
99
  default="Primary",
100
  help="Site type of the slide (for single slide processing)",
101
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  parser.add_argument(
103
  "--cancer-subtype",
104
  type=str,
@@ -144,6 +158,8 @@ def main():
144
  [
145
  args.slide_path,
146
  args.site_type,
 
 
147
  args.cancer_subtype,
148
  args.ihc_subtype,
149
  args.segmentation_config,
@@ -156,6 +172,8 @@ def main():
156
  args.slide_path,
157
  args.segmentation_config,
158
  args.site_type,
 
 
159
  args.cancer_subtype,
160
  cancer_subtype_name_map,
161
  args.ihc_subtype,
@@ -191,6 +209,8 @@ def main():
191
  slide_path = row["Slide"]
192
  seg_config = row["Segmentation Config"]
193
  site_type = row["Site Type"]
 
 
194
  cancer_subtype = row["Cancer Subtype"]
195
  ihc_subtype = row.get("IHC Subtype", "")
196
  logger.info(
@@ -200,6 +220,8 @@ def main():
200
  slide_path,
201
  seg_config,
202
  site_type,
 
 
203
  cancer_subtype,
204
  cancer_subtype_name_map,
205
  ihc_subtype,
 
21
  validate_settings,
22
  IHC_SUBTYPES,
23
  SETTINGS_COLUMNS,
24
+ SEX_OPTIONS,
25
  )
26
  from mosaic.analysis import analyze_slide
27
 
 
44
  "data/paladin_model_map.csv",
45
  )
46
  cancer_subtypes = model_map["cancer_subtype"].unique().tolist()
47
+ cancer_subtype_name_map = {"Unknown": "UNK"}
48
+ cancer_subtype_name_map.update({
49
  f"{get_oncotree_code_name(code)} ({code})": code for code in cancer_subtypes
50
+ })
 
51
  reversed_cancer_subtype_name_map = {
52
  value: key for key, value in cancer_subtype_name_map.items()
53
  }
 
100
  default="Primary",
101
  help="Site type of the slide (for single slide processing)",
102
  )
103
+ parser.add_argument(
104
+ "--sex",
105
+ type=str,
106
+ choices=SEX_OPTIONS,
107
+ default="Unknown",
108
+ help="Sex of the patient (for single slide processing)",
109
+ )
110
+ parser.add_argument(
111
+ "--tissue-site",
112
+ type=str,
113
+ default="Unknown",
114
+ help="Tissue site of the slide (for single slide processing)",
115
+ )
116
  parser.add_argument(
117
  "--cancer-subtype",
118
  type=str,
 
158
  [
159
  args.slide_path,
160
  args.site_type,
161
+ args.sex,
162
+ args.tissue_site,
163
  args.cancer_subtype,
164
  args.ihc_subtype,
165
  args.segmentation_config,
 
172
  args.slide_path,
173
  args.segmentation_config,
174
  args.site_type,
175
+ args.sex,
176
+ args.tissue_site,
177
  args.cancer_subtype,
178
  cancer_subtype_name_map,
179
  args.ihc_subtype,
 
209
  slide_path = row["Slide"]
210
  seg_config = row["Segmentation Config"]
211
  site_type = row["Site Type"]
212
+ sex = row.get("Sex", "Unknown")
213
+ tissue_site = row.get("Tissue Site", "Unknown")
214
  cancer_subtype = row["Cancer Subtype"]
215
  ihc_subtype = row.get("IHC Subtype", "")
216
  logger.info(
 
220
  slide_path,
221
  seg_config,
222
  site_type,
223
+ sex,
224
+ tissue_site,
225
  cancer_subtype,
226
  cancer_subtype_name_map,
227
  ihc_subtype,
src/mosaic/inference/aeon.py CHANGED
@@ -18,6 +18,8 @@ from mosaic.inference.data import (
18
  TileFeatureTensorDataset,
19
  INT_TO_CANCER_TYPE_MAP,
20
  CANCER_TYPE_TO_INT_MAP,
 
 
21
  )
22
 
23
  from loguru import logger
@@ -57,7 +59,7 @@ def run(
57
  sex=None, tissue_site_idx=None
58
  ):
59
  """Run Aeon model inference for cancer subtype prediction.
60
-
61
  Args:
62
  features: NumPy array of tile features extracted from the WSI
63
  model_path: Path to the pickled Aeon model file
@@ -67,7 +69,7 @@ def run(
67
  use_cpu: Force CPU usage instead of GPU
68
  sex: Patient sex (0=Male, 1=Female), optional
69
  tissue_site_idx: Tissue site index (0-56), optional
70
-
71
  Returns:
72
  tuple: (results_df, part_embedding)
73
  - results_df: DataFrame with cancer subtypes and confidence scores
@@ -82,6 +84,23 @@ def run(
82
  model.to(device)
83
  model.eval()
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  site_type = SiteType.METASTASIS if metastatic else SiteType.PRIMARY
86
 
87
  # For UI, InferenceDataset will just be a single slide. Sample id is not relevant.
@@ -105,14 +124,14 @@ def run(
105
  if "TISSUE_SITE" in batch:
106
  batch["TISSUE_SITE"] = batch["TISSUE_SITE"].to(device)
107
  y = model(batch)
108
- y["logits"][:, col_indices_to_drop] = -1e6
109
 
110
  batch_size = y["logits"].shape[0]
111
  assert batch_size == 1
112
 
113
  softmax = torch.nn.functional.softmax(y["logits"][0], dim=0)
114
  argmax = torch.argmax(softmax, dim=0)
115
- class_assignment = INT_TO_CANCER_TYPE_MAP[argmax.item()]
116
  max_confidence = softmax[argmax].item()
117
  mean_confidence = torch.mean(softmax).item()
118
 
@@ -123,7 +142,7 @@ def run(
123
 
124
  part_embedding = y["whole_part_representation"][0].cpu()
125
 
126
- for cancer_subtype, j in sorted(CANCER_TYPE_TO_INT_MAP.items()):
127
  confidence = softmax[j].item()
128
  results.append((cancer_subtype, confidence))
129
  results.sort(key=lambda row: row[1], reverse=True)
@@ -162,6 +181,19 @@ def parse_args():
162
  parser.add_argument(
163
  "--metastatic", action="store_true", help="Tissue is from a metastatic site"
164
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  parser.add_argument("--batch-size", type=int, default=BATCH_SIZE, help="Batch size")
166
  parser.add_argument(
167
  "--num-workers", type=int, default=NUM_WORKERS, help="Number of workers"
@@ -183,6 +215,17 @@ def main():
183
 
184
  features = torch.load(opt.features_path)
185
 
 
 
 
 
 
 
 
 
 
 
 
186
  results_df, part_embedding = run(
187
  features=features,
188
  model_path=opt.model_path,
@@ -190,6 +233,8 @@ def main():
190
  batch_size=opt.batch_size,
191
  num_workers=opt.num_workers,
192
  use_cpu=opt.use_cpu,
 
 
193
  )
194
 
195
  results_df.to_csv(output_path, index=False)
 
18
  TileFeatureTensorDataset,
19
  INT_TO_CANCER_TYPE_MAP,
20
  CANCER_TYPE_TO_INT_MAP,
21
+ encode_sex,
22
+ encode_tissue_site,
23
  )
24
 
25
  from loguru import logger
 
59
  sex=None, tissue_site_idx=None
60
  ):
61
  """Run Aeon model inference for cancer subtype prediction.
62
+
63
  Args:
64
  features: NumPy array of tile features extracted from the WSI
65
  model_path: Path to the pickled Aeon model file
 
69
  use_cpu: Force CPU usage instead of GPU
70
  sex: Patient sex (0=Male, 1=Female), optional
71
  tissue_site_idx: Tissue site index (0-56), optional
72
+
73
  Returns:
74
  tuple: (results_df, part_embedding)
75
  - results_df: DataFrame with cancer subtypes and confidence scores
 
84
  model.to(device)
85
  model.eval()
86
 
87
+ # Load the correct mapping from metadata for this model
88
+ from pathlib import Path
89
+ import json
90
+
91
+ metadata_dir = Path("data/metadata")
92
+ with open(metadata_dir / "target_dict.tsv") as f:
93
+ target_dict_str = f.read().strip().replace("'", '"')
94
+ target_dict = json.loads(target_dict_str)
95
+
96
+ histologies = target_dict['histologies']
97
+ INT_TO_CANCER_TYPE_MAP_LOCAL = {i: histology for i, histology in enumerate(histologies)}
98
+ CANCER_TYPE_TO_INT_MAP_LOCAL = {v: k for k, v in INT_TO_CANCER_TYPE_MAP_LOCAL.items()}
99
+
100
+ # Calculate col_indices_to_drop using local mapping
101
+ cancer_types_to_drop = ["UDMN", "ADNOS", "CUP", "CUPNOS", "NOT"]
102
+ col_indices_to_drop_local = [CANCER_TYPE_TO_INT_MAP_LOCAL[x] for x in cancer_types_to_drop if x in CANCER_TYPE_TO_INT_MAP_LOCAL]
103
+
104
  site_type = SiteType.METASTASIS if metastatic else SiteType.PRIMARY
105
 
106
  # For UI, InferenceDataset will just be a single slide. Sample id is not relevant.
 
124
  if "TISSUE_SITE" in batch:
125
  batch["TISSUE_SITE"] = batch["TISSUE_SITE"].to(device)
126
  y = model(batch)
127
+ y["logits"][:, col_indices_to_drop_local] = -1e6
128
 
129
  batch_size = y["logits"].shape[0]
130
  assert batch_size == 1
131
 
132
  softmax = torch.nn.functional.softmax(y["logits"][0], dim=0)
133
  argmax = torch.argmax(softmax, dim=0)
134
+ class_assignment = INT_TO_CANCER_TYPE_MAP_LOCAL[argmax.item()]
135
  max_confidence = softmax[argmax].item()
136
  mean_confidence = torch.mean(softmax).item()
137
 
 
142
 
143
  part_embedding = y["whole_part_representation"][0].cpu()
144
 
145
+ for cancer_subtype, j in sorted(CANCER_TYPE_TO_INT_MAP_LOCAL.items()):
146
  confidence = softmax[j].item()
147
  results.append((cancer_subtype, confidence))
148
  results.sort(key=lambda row: row[1], reverse=True)
 
181
  parser.add_argument(
182
  "--metastatic", action="store_true", help="Tissue is from a metastatic site"
183
  )
184
+ parser.add_argument(
185
+ "--sex",
186
+ type=str,
187
+ choices=["Male", "Female", "Unknown"],
188
+ default=None,
189
+ help="Patient sex (Male or Female)",
190
+ )
191
+ parser.add_argument(
192
+ "--tissue-site",
193
+ type=str,
194
+ default=None,
195
+ help="Tissue site name",
196
+ )
197
  parser.add_argument("--batch-size", type=int, default=BATCH_SIZE, help="Batch size")
198
  parser.add_argument(
199
  "--num-workers", type=int, default=NUM_WORKERS, help="Number of workers"
 
215
 
216
  features = torch.load(opt.features_path)
217
 
218
+ # Encode sex and tissue site if provided
219
+ sex_encoded = None
220
+ if opt.sex:
221
+ sex_encoded = encode_sex(opt.sex)
222
+ logger.info(f"Using sex: {opt.sex} (encoded as {sex_encoded})")
223
+
224
+ tissue_site_idx = None
225
+ if opt.tissue_site:
226
+ tissue_site_idx = encode_tissue_site(opt.tissue_site)
227
+ logger.info(f"Using tissue site: {opt.tissue_site} (encoded as {tissue_site_idx})")
228
+
229
  results_df, part_embedding = run(
230
  features=features,
231
  model_path=opt.model_path,
 
233
  batch_size=opt.batch_size,
234
  num_workers=opt.num_workers,
235
  use_cpu=opt.use_cpu,
236
+ sex=sex_encoded,
237
+ tissue_site_idx=tissue_site_idx,
238
  )
239
 
240
  results_df.to_csv(output_path, index=False)
src/mosaic/inference/data.py CHANGED
@@ -215,8 +215,8 @@ def get_tissue_site_map():
215
  if _TISSUE_SITE_MAP is None:
216
  from pathlib import Path
217
  import pandas as pd
218
-
219
- csv_path = Path(__file__).parent.parent.parent / "data" / "tissue_site_original_to_idx.csv"
220
  df = pd.read_csv(csv_path)
221
 
222
  _TISSUE_SITE_MAP = {}
@@ -236,16 +236,41 @@ def get_tissue_site_options():
236
  return sorted(set(site_map.keys()))
237
 
238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  def encode_sex(sex):
240
  """Convert sex to numeric encoding.
241
-
242
  Args:
243
- sex: "Male" or "Female"
244
-
245
  Returns:
246
- int: 0 for Male, 1 for Female
247
  """
248
- return 1 if sex == "Female" else 0
 
249
 
250
 
251
  def encode_tissue_site(site_name):
 
215
  if _TISSUE_SITE_MAP is None:
216
  from pathlib import Path
217
  import pandas as pd
218
+
219
+ csv_path = Path(__file__).parent.parent.parent.parent / "data" / "tissue_site_original_to_idx.csv"
220
  df = pd.read_csv(csv_path)
221
 
222
  _TISSUE_SITE_MAP = {}
 
236
  return sorted(set(site_map.keys()))
237
 
238
 
239
+ _SEX_MAP = None
240
+
241
+
242
+ def get_sex_map():
243
+ """Get the sex to index mapping from CSV file.
244
+
245
+ Returns:
246
+ dict: Mapping of sex values to indices (0-2)
247
+ """
248
+ global _SEX_MAP
249
+ if _SEX_MAP is None:
250
+ from pathlib import Path
251
+ import pandas as pd
252
+
253
+ csv_path = Path(__file__).parent.parent.parent.parent / "data" / "sex_original_to_idx.csv"
254
+ df = pd.read_csv(csv_path)
255
+
256
+ _SEX_MAP = {}
257
+ for _, row in df.iterrows():
258
+ _SEX_MAP[row['SEX']] = int(row['idx'])
259
+
260
+ return _SEX_MAP
261
+
262
+
263
  def encode_sex(sex):
264
  """Convert sex to numeric encoding.
265
+
266
  Args:
267
+ sex: "Male", "Female", or "Unknown" (case insensitive)
268
+
269
  Returns:
270
+ int: 0 for Male, 1 for Female, 2 for Unknown
271
  """
272
+ sex_map = get_sex_map()
273
+ return sex_map.get(sex, 2) # Default to 2 (Unknown) if not found
274
 
275
 
276
  def encode_tissue_site(site_name):
src/mosaic/ui/utils.py CHANGED
@@ -35,13 +35,13 @@ tissue_site_list = None
35
 
36
  def get_tissue_sites():
37
  """Get the list of tissue sites from the tissue site map file.
38
-
39
  Returns:
40
  List of tissue site names
41
  """
42
  global tissue_site_list
43
  if tissue_site_list is None:
44
- current_dir = Path(__file__).parent.parent.parent
45
  tissue_site_map_path = current_dir / "data" / "tissue_site_original_to_idx.csv"
46
  df = pd.read_csv(tissue_site_map_path)
47
  # Get unique tissue sites and sort them
@@ -149,6 +149,8 @@ def validate_settings(settings_df, cancer_subtype_name_map, cancer_subtypes, rev
149
  """
150
  settings_df.columns = SETTINGS_COLUMNS
151
  warnings = []
 
 
152
  for idx, row in settings_df.iterrows():
153
  slide_name = row["Slide"]
154
  subtype = row["Cancer Subtype"]
@@ -166,6 +168,16 @@ def validate_settings(settings_df, cancer_subtype_name_map, cancer_subtypes, rev
166
  f"Slide {slide_name}: Unknown site type. Valid types are: Metastatic, Primary. "
167
  )
168
  settings_df.at[idx, "Site Type"] = "Primary"
 
 
 
 
 
 
 
 
 
 
169
  if (
170
  "Breast" not in settings_df.at[idx, "Cancer Subtype"]
171
  and row["IHC Subtype"] != ""
 
35
 
36
  def get_tissue_sites():
37
  """Get the list of tissue sites from the tissue site map file.
38
+
39
  Returns:
40
  List of tissue site names
41
  """
42
  global tissue_site_list
43
  if tissue_site_list is None:
44
+ current_dir = Path(__file__).parent.parent.parent.parent
45
  tissue_site_map_path = current_dir / "data" / "tissue_site_original_to_idx.csv"
46
  df = pd.read_csv(tissue_site_map_path)
47
  # Get unique tissue sites and sort them
 
149
  """
150
  settings_df.columns = SETTINGS_COLUMNS
151
  warnings = []
152
+ tissue_sites = get_tissue_sites()
153
+
154
  for idx, row in settings_df.iterrows():
155
  slide_name = row["Slide"]
156
  subtype = row["Cancer Subtype"]
 
168
  f"Slide {slide_name}: Unknown site type. Valid types are: Metastatic, Primary. "
169
  )
170
  settings_df.at[idx, "Site Type"] = "Primary"
171
+ if row["Sex"] not in SEX_OPTIONS:
172
+ warnings.append(
173
+ f"Slide {slide_name}: Unknown sex. Valid options are: {', '.join(SEX_OPTIONS)}. "
174
+ )
175
+ settings_df.at[idx, "Sex"] = "Unknown"
176
+ if row["Tissue Site"] not in tissue_sites:
177
+ warnings.append(
178
+ f"Slide {slide_name}: Unknown tissue site. Valid tissue sites are: {', '.join(tissue_sites)}. "
179
+ )
180
+ settings_df.at[idx, "Tissue Site"] = "Unknown"
181
  if (
182
  "Breast" not in settings_df.at[idx, "Cancer Subtype"]
183
  and row["IHC Subtype"] != ""
uv.lock CHANGED
@@ -2336,6 +2336,40 @@ wheels = [
2336
  { url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8", size = 210034, upload-time = "2025-06-01T10:19:20.436Z" },
2337
  ]
2338
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2339
  [[package]]
2340
  name = "loguru"
2341
  version = "0.7.3"
@@ -2565,11 +2599,14 @@ version = "0.1.0"
2565
  source = { editable = "." }
2566
  dependencies = [
2567
  { name = "gradio" },
 
2568
  { name = "loguru" },
2569
  { name = "memory-profiler" },
2570
  { name = "mussel", extra = ["torch-gpu"] },
2571
  { name = "paladin" },
 
2572
  { name = "spaces" },
 
2573
  ]
2574
 
2575
  [package.dev-dependencies]
@@ -2584,11 +2621,14 @@ dev = [
2584
  [package.metadata]
2585
  requires-dist = [
2586
  { name = "gradio", specifier = ">=5.49.0" },
 
2587
  { name = "loguru", specifier = ">=0.7.3" },
2588
  { name = "memory-profiler", specifier = ">=0.61.0" },
2589
  { name = "mussel", extras = ["torch-gpu"], git = "https://github.com/pathology-data-mining/Mussel.git?rev=mosaic-dev" },
2590
  { name = "paladin", git = "ssh://git@github.com/pathology-data-mining/paladin.git?rev=dev" },
 
2591
  { name = "spaces", specifier = ">=0.30.0" },
 
2592
  ]
2593
 
2594
  [package.metadata.requires-dev]
@@ -3199,8 +3239,8 @@ wheels = [
3199
 
3200
  [[package]]
3201
  name = "paladin"
3202
- version = "0.1.dev164+g0aef7cad1"
3203
- source = { git = "ssh://git@github.com/pathology-data-mining/paladin.git?rev=dev#0aef7cad1c2c4b54ea75406e3ed4e61c83591a71" }
3204
  dependencies = [
3205
  { name = "dvc" },
3206
  { name = "nn-template-core" },
@@ -3280,6 +3320,18 @@ wheels = [
3280
  { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
3281
  ]
3282
 
 
 
 
 
 
 
 
 
 
 
 
 
3283
  [[package]]
3284
  name = "pillow"
3285
  version = "11.3.0"
@@ -4846,6 +4898,20 @@ wheels = [
4846
  { url = "https://files.pythonhosted.org/packages/5c/2e/10b7fe92ddc69e5aae177775a3c8ed890bdd6cb40c2aa04e0a982937edd1/scmrepo-3.5.2-py3-none-any.whl", hash = "sha256:6e4660572b76512d0e013ca9806692188c736e8c9c76f833e3674fc21a558788", size = 73868, upload-time = "2025-08-06T14:46:31.635Z" },
4847
  ]
4848
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4849
  [[package]]
4850
  name = "semantic-version"
4851
  version = "2.10.0"
@@ -5052,6 +5118,52 @@ wheels = [
5052
  { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" },
5053
  ]
5054
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5055
  [[package]]
5056
  name = "stqdm"
5057
  version = "0.0.5"
 
2336
  { url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8", size = 210034, upload-time = "2025-06-01T10:19:20.436Z" },
2337
  ]
2338
 
2339
+ [[package]]
2340
+ name = "lightning"
2341
+ version = "2.6.0"
2342
+ source = { registry = "https://pypi.org/simple" }
2343
+ dependencies = [
2344
+ { name = "fsspec", extra = ["http"] },
2345
+ { name = "lightning-utilities" },
2346
+ { name = "packaging" },
2347
+ { name = "pytorch-lightning" },
2348
+ { name = "pyyaml" },
2349
+ { name = "torch" },
2350
+ { name = "torchmetrics" },
2351
+ { name = "tqdm" },
2352
+ { name = "typing-extensions" },
2353
+ ]
2354
+ sdist = { url = "https://files.pythonhosted.org/packages/1a/d5/892ea38816925b3511493e87b0b32494122bf8a20e66f4f2cd2667f95625/lightning-2.6.0.tar.gz", hash = "sha256:881841716b59c1837ae0c562c2e64fea9bcf49ef9de3867bd1f868557ec23d04", size = 656539, upload-time = "2025-11-28T09:34:25.069Z" }
2355
+ wheels = [
2356
+ { url = "https://files.pythonhosted.org/packages/d6/e9/36b340c7ec01dad6f034481e98fc9fc0133307beb05c714c0542af98bbde/lightning-2.6.0-py3-none-any.whl", hash = "sha256:f1a13a48909960a3454518486f113fae4fadb2db0e28e9c50d8d38d46c9dc3d6", size = 845956, upload-time = "2025-11-28T09:34:23.273Z" },
2357
+ ]
2358
+
2359
+ [[package]]
2360
+ name = "lightning-utilities"
2361
+ version = "0.15.2"
2362
+ source = { registry = "https://pypi.org/simple" }
2363
+ dependencies = [
2364
+ { name = "packaging" },
2365
+ { name = "setuptools" },
2366
+ { name = "typing-extensions" },
2367
+ ]
2368
+ sdist = { url = "https://files.pythonhosted.org/packages/b8/39/6fc58ca81492db047149b4b8fd385aa1bfb8c28cd7cacb0c7eb0c44d842f/lightning_utilities-0.15.2.tar.gz", hash = "sha256:cdf12f530214a63dacefd713f180d1ecf5d165338101617b4742e8f22c032e24", size = 31090, upload-time = "2025-08-06T13:57:39.242Z" }
2369
+ wheels = [
2370
+ { url = "https://files.pythonhosted.org/packages/de/73/3d757cb3fc16f0f9794dd289bcd0c4a031d9cf54d8137d6b984b2d02edf3/lightning_utilities-0.15.2-py3-none-any.whl", hash = "sha256:ad3ab1703775044bbf880dbf7ddaaac899396c96315f3aa1779cec9d618a9841", size = 29431, upload-time = "2025-08-06T13:57:38.046Z" },
2371
+ ]
2372
+
2373
  [[package]]
2374
  name = "loguru"
2375
  version = "0.7.3"
 
2599
  source = { editable = "." }
2600
  dependencies = [
2601
  { name = "gradio" },
2602
+ { name = "lightning" },
2603
  { name = "loguru" },
2604
  { name = "memory-profiler" },
2605
  { name = "mussel", extra = ["torch-gpu"] },
2606
  { name = "paladin" },
2607
+ { name = "seaborn" },
2608
  { name = "spaces" },
2609
+ { name = "statsmodels" },
2610
  ]
2611
 
2612
  [package.dev-dependencies]
 
2621
  [package.metadata]
2622
  requires-dist = [
2623
  { name = "gradio", specifier = ">=5.49.0" },
2624
+ { name = "lightning", specifier = ">=2.6.0" },
2625
  { name = "loguru", specifier = ">=0.7.3" },
2626
  { name = "memory-profiler", specifier = ">=0.61.0" },
2627
  { name = "mussel", extras = ["torch-gpu"], git = "https://github.com/pathology-data-mining/Mussel.git?rev=mosaic-dev" },
2628
  { name = "paladin", git = "ssh://git@github.com/pathology-data-mining/paladin.git?rev=dev" },
2629
+ { name = "seaborn", specifier = ">=0.13.2" },
2630
  { name = "spaces", specifier = ">=0.30.0" },
2631
+ { name = "statsmodels", specifier = ">=0.14.6" },
2632
  ]
2633
 
2634
  [package.metadata.requires-dev]
 
3239
 
3240
  [[package]]
3241
  name = "paladin"
3242
+ version = "0.0.0"
3243
+ source = { git = "ssh://git@github.com/pathology-data-mining/paladin.git?rev=dev#de6dab1a40948285d2e8aad322b9aca91ae669e6" }
3244
  dependencies = [
3245
  { name = "dvc" },
3246
  { name = "nn-template-core" },
 
3320
  { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
3321
  ]
3322
 
3323
+ [[package]]
3324
+ name = "patsy"
3325
+ version = "1.0.2"
3326
+ source = { registry = "https://pypi.org/simple" }
3327
+ dependencies = [
3328
+ { name = "numpy" },
3329
+ ]
3330
+ sdist = { url = "https://files.pythonhosted.org/packages/be/44/ed13eccdd0519eff265f44b670d46fbb0ec813e2274932dc1c0e48520f7d/patsy-1.0.2.tar.gz", hash = "sha256:cdc995455f6233e90e22de72c37fcadb344e7586fb83f06696f54d92f8ce74c0", size = 399942, upload-time = "2025-10-20T16:17:37.535Z" }
3331
+ wheels = [
3332
+ { url = "https://files.pythonhosted.org/packages/f1/70/ba4b949bdc0490ab78d545459acd7702b211dfccf7eb89bbc1060f52818d/patsy-1.0.2-py2.py3-none-any.whl", hash = "sha256:37bfddbc58fcf0362febb5f54f10743f8b21dd2aa73dec7e7ef59d1b02ae668a", size = 233301, upload-time = "2025-10-20T16:17:36.563Z" },
3333
+ ]
3334
+
3335
  [[package]]
3336
  name = "pillow"
3337
  version = "11.3.0"
 
4898
  { url = "https://files.pythonhosted.org/packages/5c/2e/10b7fe92ddc69e5aae177775a3c8ed890bdd6cb40c2aa04e0a982937edd1/scmrepo-3.5.2-py3-none-any.whl", hash = "sha256:6e4660572b76512d0e013ca9806692188c736e8c9c76f833e3674fc21a558788", size = 73868, upload-time = "2025-08-06T14:46:31.635Z" },
4899
  ]
4900
 
4901
+ [[package]]
4902
+ name = "seaborn"
4903
+ version = "0.13.2"
4904
+ source = { registry = "https://pypi.org/simple" }
4905
+ dependencies = [
4906
+ { name = "matplotlib" },
4907
+ { name = "numpy" },
4908
+ { name = "pandas" },
4909
+ ]
4910
+ sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" }
4911
+ wheels = [
4912
+ { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" },
4913
+ ]
4914
+
4915
  [[package]]
4916
  name = "semantic-version"
4917
  version = "2.10.0"
 
5118
  { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" },
5119
  ]
5120
 
5121
+ [[package]]
5122
+ name = "statsmodels"
5123
+ version = "0.14.6"
5124
+ source = { registry = "https://pypi.org/simple" }
5125
+ dependencies = [
5126
+ { name = "numpy" },
5127
+ { name = "packaging" },
5128
+ { name = "pandas" },
5129
+ { name = "patsy" },
5130
+ { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
5131
+ { name = "scipy", version = "1.16.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
5132
+ ]
5133
+ sdist = { url = "https://files.pythonhosted.org/packages/0d/81/e8d74b34f85285f7335d30c5e3c2d7c0346997af9f3debf9a0a9a63de184/statsmodels-0.14.6.tar.gz", hash = "sha256:4d17873d3e607d398b85126cd4ed7aad89e4e9d89fc744cdab1af3189a996c2a", size = 20689085, upload-time = "2025-12-05T23:08:39.522Z" }
5134
+ wheels = [
5135
+ { url = "https://files.pythonhosted.org/packages/b5/6d/9ec309a175956f88eb8420ac564297f37cf9b1f73f89db74da861052dc29/statsmodels-0.14.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4ff0649a2df674c7ffb6fa1a06bffdb82a6adf09a48e90e000a15a6aaa734b0", size = 10142419, upload-time = "2025-12-05T19:27:35.625Z" },
5136
+ { url = "https://files.pythonhosted.org/packages/86/8f/338c5568315ec5bf3ac7cd4b71e34b98cb3b0f834919c0c04a0762f878a1/statsmodels-0.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:109012088b3e370080846ab053c76d125268631410142daad2f8c10770e8e8d9", size = 10022819, upload-time = "2025-12-05T19:27:49.385Z" },
5137
+ { url = "https://files.pythonhosted.org/packages/b0/77/5fc4cbc2d608f9b483b0675f82704a8bcd672962c379fe4d82100d388dbf/statsmodels-0.14.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e93bd5d220f3cb6fc5fc1bffd5b094966cab8ee99f6c57c02e95710513d6ac3f", size = 10118927, upload-time = "2025-12-05T23:07:51.256Z" },
5138
+ { url = "https://files.pythonhosted.org/packages/94/55/b86c861c32186403fe121d9ab27bc16d05839b170d92a978beb33abb995e/statsmodels-0.14.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:06eec42d682fdb09fe5d70a05930857efb141754ec5a5056a03304c1b5e32fd9", size = 10413015, upload-time = "2025-12-05T23:08:53.95Z" },
5139
+ { url = "https://files.pythonhosted.org/packages/f9/be/daf0dba729ccdc4176605f4a0fd5cfe71cdda671749dca10e74a732b8b1c/statsmodels-0.14.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0444e88557df735eda7db330806fe09d51c9f888bb1f5906cb3a61fb1a3ed4a8", size = 10441248, upload-time = "2025-12-05T23:09:09.353Z" },
5140
+ { url = "https://files.pythonhosted.org/packages/9a/1c/2e10b7c7cc44fa418272996bf0427b8016718fd62f995d9c1f7ab37adf35/statsmodels-0.14.6-cp310-cp310-win_amd64.whl", hash = "sha256:e83a9abe653835da3b37fb6ae04b45480c1de11b3134bd40b09717192a1456ea", size = 9583410, upload-time = "2025-12-05T19:28:02.086Z" },
5141
+ { url = "https://files.pythonhosted.org/packages/a9/4d/df4dd089b406accfc3bb5ee53ba29bb3bdf5ae61643f86f8f604baa57656/statsmodels-0.14.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ad5c2810fc6c684254a7792bf1cbaf1606cdee2a253f8bd259c43135d87cfb4", size = 10121514, upload-time = "2025-12-05T19:28:16.521Z" },
5142
+ { url = "https://files.pythonhosted.org/packages/82/af/ec48daa7f861f993b91a0dcc791d66e1cf56510a235c5cbd2ab991a31d5c/statsmodels-0.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:341fa68a7403e10a95c7b6e41134b0da3a7b835ecff1eb266294408535a06eb6", size = 10003346, upload-time = "2025-12-05T19:28:29.568Z" },
5143
+ { url = "https://files.pythonhosted.org/packages/a9/2c/c8f7aa24cd729970728f3f98822fb45149adc216f445a9301e441f7ac760/statsmodels-0.14.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdf1dfe2a3ca56f5529118baf33a13efed2783c528f4a36409b46bbd2d9d48eb", size = 10129872, upload-time = "2025-12-05T23:09:25.724Z" },
5144
+ { url = "https://files.pythonhosted.org/packages/40/c6/9ae8e9b0721e9b6eb5f340c3a0ce8cd7cce4f66e03dd81f80d60f111987f/statsmodels-0.14.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3764ba8195c9baf0925a96da0743ff218067a269f01d155ca3558deed2658ca", size = 10381964, upload-time = "2025-12-05T23:09:41.326Z" },
5145
+ { url = "https://files.pythonhosted.org/packages/28/8c/cf3d30c8c2da78e2ad1f50ade8b7fabec3ff4cdfc56fbc02e097c4577f90/statsmodels-0.14.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e8d2e519852adb1b420e018f5ac6e6684b2b877478adf7fda2cfdb58f5acb5d", size = 10409611, upload-time = "2025-12-05T23:09:57.131Z" },
5146
+ { url = "https://files.pythonhosted.org/packages/bf/cc/018f14ecb58c6cb89de9d52695740b7d1f5a982aa9ea312483ea3c3d5f77/statsmodels-0.14.6-cp311-cp311-win_amd64.whl", hash = "sha256:2738a00fca51196f5a7d44b06970ace6b8b30289839e4808d656f8a98e35faa7", size = 9580385, upload-time = "2025-12-05T19:28:42.778Z" },
5147
+ { url = "https://files.pythonhosted.org/packages/25/ce/308e5e5da57515dd7cab3ec37ea2d5b8ff50bef1fcc8e6d31456f9fae08e/statsmodels-0.14.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe76140ae7adc5ff0e60a3f0d56f4fffef484efa803c3efebf2fcd734d72ecb5", size = 10091932, upload-time = "2025-12-05T19:28:55.446Z" },
5148
+ { url = "https://files.pythonhosted.org/packages/05/30/affbabf3c27fb501ec7b5808230c619d4d1a4525c07301074eb4bda92fa9/statsmodels-0.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26d4f0ed3b31f3c86f83a92f5c1f5cbe63fc992cd8915daf28ca49be14463a1c", size = 9997345, upload-time = "2025-12-05T19:29:10.278Z" },
5149
+ { url = "https://files.pythonhosted.org/packages/48/f5/3a73b51e6450c31652c53a8e12e24eac64e3824be816c0c2316e7dbdcb7d/statsmodels-0.14.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c00a42863e4f4733ac9d078bbfad816249c01451740e6f5053ecc7db6d6368", size = 10058649, upload-time = "2025-12-05T23:10:12.775Z" },
5150
+ { url = "https://files.pythonhosted.org/packages/81/68/dddd76117df2ef14c943c6bbb6618be5c9401280046f4ddfc9fb4596a1b8/statsmodels-0.14.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b58cf7474aa9e7e3b0771a66537148b2df9b5884fbf156096c0e6c1ff0469d", size = 10339446, upload-time = "2025-12-05T23:10:28.503Z" },
5151
+ { url = "https://files.pythonhosted.org/packages/56/4a/dce451c74c4050535fac1ec0c14b80706d8fc134c9da22db3c8a0ec62c33/statsmodels-0.14.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81e7dcc5e9587f2567e52deaff5220b175bf2f648951549eae5fc9383b62bc37", size = 10368705, upload-time = "2025-12-05T23:10:44.339Z" },
5152
+ { url = "https://files.pythonhosted.org/packages/60/15/3daba2df40be8b8a9a027d7f54c8dedf24f0d81b96e54b52293f5f7e3418/statsmodels-0.14.6-cp312-cp312-win_amd64.whl", hash = "sha256:b5eb07acd115aa6208b4058211138393a7e6c2cf12b6f213ede10f658f6a714f", size = 9543991, upload-time = "2025-12-05T23:10:58.536Z" },
5153
+ { url = "https://files.pythonhosted.org/packages/81/59/a5aad5b0cc266f5be013db8cde563ac5d2a025e7efc0c328d83b50c72992/statsmodels-0.14.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47ee7af083623d2091954fa71c7549b8443168f41b7c5dce66510274c50fd73e", size = 10072009, upload-time = "2025-12-05T23:11:14.021Z" },
5154
+ { url = "https://files.pythonhosted.org/packages/53/dd/d8cfa7922fc6dc3c56fa6c59b348ea7de829a94cd73208c6f8202dd33f17/statsmodels-0.14.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa60d82e29fcd0a736e86feb63a11d2380322d77a9369a54be8b0965a3985f71", size = 9980018, upload-time = "2025-12-05T23:11:30.907Z" },
5155
+ { url = "https://files.pythonhosted.org/packages/ee/77/0ec96803eba444efd75dba32f2ef88765ae3e8f567d276805391ec2c98c6/statsmodels-0.14.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89ee7d595f5939cc20bf946faedcb5137d975f03ae080f300ebb4398f16a5bd4", size = 10060269, upload-time = "2025-12-05T23:11:46.338Z" },
5156
+ { url = "https://files.pythonhosted.org/packages/10/b9/fd41f1f6af13a1a1212a06bb377b17762feaa6d656947bf666f76300fc05/statsmodels-0.14.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:730f3297b26749b216a06e4327fe0be59b8d05f7d594fb6caff4287b69654589", size = 10324155, upload-time = "2025-12-05T23:12:01.805Z" },
5157
+ { url = "https://files.pythonhosted.org/packages/ee/0f/a6900e220abd2c69cd0a07e3ad26c71984be6061415a60e0f17b152ecf08/statsmodels-0.14.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f1c08befa85e93acc992b72a390ddb7bd876190f1360e61d10cf43833463bc9c", size = 10349765, upload-time = "2025-12-05T23:12:18.018Z" },
5158
+ { url = "https://files.pythonhosted.org/packages/98/08/b79f0c614f38e566eebbdcff90c0bcacf3c6ba7a5bbb12183c09c29ca400/statsmodels-0.14.6-cp313-cp313-win_amd64.whl", hash = "sha256:8021271a79f35b842c02a1794465a651a9d06ec2080f76ebc3b7adce77d08233", size = 9540043, upload-time = "2025-12-05T23:12:33.887Z" },
5159
+ { url = "https://files.pythonhosted.org/packages/71/de/09540e870318e0c7b58316561d417be45eff731263b4234fdd2eee3511a8/statsmodels-0.14.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:00781869991f8f02ad3610da6627fd26ebe262210287beb59761982a8fa88cae", size = 10069403, upload-time = "2025-12-05T23:12:48.424Z" },
5160
+ { url = "https://files.pythonhosted.org/packages/ab/f0/63c1bfda75dc53cee858006e1f46bd6d6f883853bea1b97949d0087766ca/statsmodels-0.14.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:73f305fbf31607b35ce919fae636ab8b80d175328ed38fdc6f354e813b86ee37", size = 9989253, upload-time = "2025-12-05T23:13:05.274Z" },
5161
+ { url = "https://files.pythonhosted.org/packages/c1/98/b0dfb4f542b2033a3341aa5f1bdd97024230a4ad3670c5b0839d54e3dcab/statsmodels-0.14.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e443e7077a6e2d3faeea72f5a92c9f12c63722686eb80bb40a0f04e4a7e267ad", size = 10090802, upload-time = "2025-12-05T23:13:20.653Z" },
5162
+ { url = "https://files.pythonhosted.org/packages/34/0e/2408735aca9e764643196212f9069912100151414dd617d39ffc72d77eee/statsmodels-0.14.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3414e40c073d725007a6603a18247ab7af3467e1af4a5e5a24e4c27bc26673b4", size = 10337587, upload-time = "2025-12-05T23:13:37.597Z" },
5163
+ { url = "https://files.pythonhosted.org/packages/0f/36/4d44f7035ab3c0b2b6a4c4ebb98dedf36246ccbc1b3e2f51ebcd7ac83abb/statsmodels-0.14.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a518d3f9889ef920116f9fa56d0338069e110f823926356946dae83bc9e33e19", size = 10363350, upload-time = "2025-12-05T23:13:53.08Z" },
5164
+ { url = "https://files.pythonhosted.org/packages/26/33/f1652d0c59fa51de18492ee2345b65372550501ad061daa38f950be390b6/statsmodels-0.14.6-cp314-cp314-win_amd64.whl", hash = "sha256:151b73e29f01fe619dbce7f66d61a356e9d1fe5e906529b78807df9189c37721", size = 9588010, upload-time = "2025-12-05T23:14:07.28Z" },
5165
+ ]
5166
+
5167
  [[package]]
5168
  name = "stqdm"
5169
  version = "0.0.5"