Spaces:
Sleeping
Sleeping
Add comprehensive sex and tissue site parameter support
Browse filesThis 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 +3 -0
- src/mosaic/analysis.py +8 -10
- src/mosaic/gradio_app.py +25 -3
- src/mosaic/inference/aeon.py +50 -5
- src/mosaic/inference/data.py +32 -7
- src/mosaic/ui/utils.py +14 -2
- uv.lock +114 -2
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/
|
| 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
|
| 525 |
-
sex_idx =
|
| 526 |
-
|
| 527 |
tissue_site_idx = None
|
| 528 |
-
if tissue_site
|
| 529 |
-
|
| 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"][:,
|
| 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 =
|
| 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(
|
| 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 "
|
| 244 |
-
|
| 245 |
Returns:
|
| 246 |
-
int: 0 for Male, 1 for Female
|
| 247 |
"""
|
| 248 |
-
|
|
|
|
| 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.
|
| 3203 |
-
source = { git = "ssh://git@github.com/pathology-data-mining/paladin.git?rev=dev#
|
| 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"
|