medvision-classification


Namemedvision-classification JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummaryA comprehensive PyTorch Lightning framework for medical image classification with support for 2D/3D images
upload_time2025-07-15 05:22:00
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords medical imaging classification pytorch lightning deep learning
VCS
bugtrack_url
requirements torch torchvision pytorch-lightning lightning numpy pyyaml tqdm click pillow scikit-image pandas nibabel SimpleITK pydicom monai opencv-python albumentations matplotlib seaborn tensorboard wandb scikit-learn scipy torchmetrics timm efficientnet-pytorch hydra-core omegaconf rich
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # MedVision-Classification

MedVision-Classification 是一个基于 PyTorch Lightning 的医学影像分类框架,提供了训练和推理的简单接口。

## 特点

- 基于 PyTorch Lightning 的高级接口
- 支持常见的医学影像格式(NIfTI、DICOM 等)
- 内置多种分类模型架构(ResNet、DenseNet、EfficientNet 等)
- 灵活的数据加载和预处理管道
- 模块化设计,易于扩展
- 命令行界面用于训练和推理
- 支持二分类和多分类任务

## 安装

### 系统要求

- Python 3.8+
- PyTorch 2.0+
- CUDA (可选,用于GPU加速)

### 基本安装

最简单的安装方式:

```bash
pip install -e .
```

### 从源码安装

```bash
git clone https://github.com/yourusername/medvision-classification.git
cd medvision-classification
pip install -e .
```

### 使用requirements文件

```bash
# 基本环境
pip install -r requirements.txt

# 开发环境
pip install -r requirements-dev.txt
```

### 使用conda环境

推荐使用 conda 创建独立的虚拟环境:

```bash
# 创建并激活环境
conda env create -f environment.yml
conda activate medvision-cls

# 安装项目本身
pip install -e .
```

## 快速入门

### 训练2D模型

```bash
MedVision-cls train configs/train_config.yml
```

### 训练3D模型

```bash
MedVision-cls train configs/train_3d_resnet_config.yml
```

### 测试模型

```bash
MedVision-cls test configs/test_config.yml
```

### 推理

```bash
MedVision-cls predict configs/inference_config.yml --input /path/to/image --output /path/to/output
```

## 配置格式

### 2D分类训练配置示例

```yaml
# 2D ResNet Training Configuration
seed: 42

task_dim: 2d

# Model configuration
model:
  type: "classification"
  network:
    name: "resnet50"
    pretrained: true
  num_classes: 2

  # Metrics to compute
  metrics:
    accuracy:
      type: "accuracy"
    f1:
      type: "f1"
    precision:
      type: "precision"
    recall:
      type: "recall"
    auc:
      type: "auroc"
        
  # Loss configuration
  loss:
    type: "cross_entropy"
    weight: null
    label_smoothing: 0.0
  
  # Optimizer configuration
  optimizer:
    type: "adam"
    lr: 0.001
    weight_decay: 0.0001
  
  # Scheduler configuration
  scheduler:
    type: "cosine"
    T_max: 100
    eta_min: 0.00001

# Data configuration
data:
  type: "medical"
  batch_size: 4
  num_workers: 4
  data_dir: "data/classification"
  image_format: "*.png"
  
  # Transform configuration for 2D data
  transforms:
    image_size: [224, 224]
    normalize: true
    augment: true
    
  # Data split configuration
  train_val_split: [0.8, 0.2]
  seed: 42

# Training configuration
training:
  max_epochs: 10
  accelerator: "gpu"
  devices: [0,1,2,3]  # Multi-GPU training
  precision: 16
  save_metrics: true
  
  # Callbacks
  model_checkpoint:
    monitor: "val/accuracy"
    mode: "max"
    save_top_k: 3
    filename: "epoch_{epoch:02d}-val_acc_{val/accuracy:.3f}"

# Validation configuration
validation:
  check_val_every_n_epoch: 1

# Class names
class_names:
  - "Class_0"
  - "Class_1"

# Output paths
outputs:
  output_dir: "outputs"
  checkpoint_dir: "outputs/checkpoints"
  log_dir: "outputs/logs"

# Logging
logging:
  log_every_n_steps: 10
  wandb:
    enabled: false
    project: "medvision-2d-classification"
    entity: null
```

### 3D分类训练配置示例

```yaml
# 3D ResNet Training Configuration
seed: 42

task_dim: 3D

# Model configuration
model:
  type: "classification"
  network:
    name: "resnet3d_18"  # Options: resnet3d_18, resnet3d_34, resnet3d_50
    pretrained: false    # No pretrained weights for 3D models
    in_channels: 3       # Input channels (typically 1 for medical images)
    dropout: 0.1
  num_classes: 2

  # Metrics to compute
  metrics:
    accuracy:
      type: "accuracy"
    f1:
      type: "f1"
    precision:
      type: "precision"
    recall:
      type: "recall"
    auc:
      type: "auroc"

  # Loss configuration
  loss:
    type: "cross_entropy"
    weight: null
    label_smoothing: 0.0
  
  # Optimizer configuration
  optimizer:
    type: "adam"
    lr: 0.001
    weight_decay: 0.0001
  
  # Scheduler configuration
  scheduler:
    type: "cosine"
    T_max: 100
    eta_min: 0.00001

# Data configuration
data:
  type: "medical"
  batch_size: 4         # Smaller batch size for 3D data
  num_workers: 4
  data_dir: "data/3D"
  image_format: "*.nii.gz"  # 3D medical image format
  
  # Transform configuration for 3D data
  transforms:
    image_size: [64, 64, 64]  # [D, H, W] for 3D volumes
    normalize: true
    augment: true
    
  # Data split configuration
  train_val_split: [0.8, 0.2]
  seed: 42

# Training configuration
training:
  max_epochs: 5
  accelerator: "gpu"
  devices: 1            # Single GPU for 3D (memory intensive)
  precision: 16         # Use mixed precision to save memory
  
  # Callbacks
  early_stopping:
    monitor: "val/loss"
    patience: 10
    mode: "min"
  
  model_checkpoint:
    monitor: "val/accuracy"
    mode: "max"
    save_top_k: 3
    filename: "epoch_{epoch:02d}-val_acc_{val/accuracy:.3f}"

# Validation configuration
validation:
  check_val_every_n_epoch: 1

# Output paths
outputs:
  output_dir: "outputs"
  checkpoint_dir: "outputs/checkpoints"
  log_dir: "outputs/logs"

# Logging
logging:
  log_every_n_steps: 10
  wandb:
    enabled: false
    project: "medvision-3d-classification"
    entity: null
```

### 推理配置示例

```yaml
# Model configuration
model:
  type: "classification"
  network:
    name: "resnet50"
    pretrained: false
  num_classes: 2
  checkpoint_path: "outputs/checkpoints/best_model.ckpt"

# Inference settings
inference:
  batch_size: 1
  device: "cuda:0"  # 或 "cpu"
  return_probabilities: true
  class_names: ["class0", "class1"]
  confidence_threshold: 0.5

# Preprocessing
preprocessing:
  image_size: [224, 224]
  normalize: true
  mean: [0.485, 0.456, 0.406]
  std: [0.229, 0.224, 0.225]

# Output settings
output:
  save_predictions: true
  include_probabilities: true
  format: "json"  # 或 "csv"
```

## 数据格式

### 文件夹结构

```
data/
├── classification/
│   ├── train/
│   │   ├── class1/
│   │   │   ├── image1.png
│   │   │   └── image2.png
│   │   └── class2/
│   │       ├── image3.png
│   │       └── image4.png
│   ├── val/
│   │   ├── class1/
│   │   └── class2/
│   └── test/
│       ├── class1/
│       └── class2/
```

### CSV格式

```csv
image_path,label
/path/to/image1.png,0
/path/to/image2.png,1
/path/to/image3.png,0
```

## 支持的模型

- **ResNet系列**: ResNet18, ResNet34, ResNet50, ResNet101, ResNet152
- **DenseNet系列**: DenseNet121, DenseNet161, DenseNet169, DenseNet201
- **EfficientNet系列**: EfficientNet-B0 到 EfficientNet-B7
- **Vision Transformer**: ViT-Base, ViT-Large
- **ConvNeXt**: ConvNeXt-Tiny, ConvNeXt-Small, ConvNeXt-Base
- **Medical专用**: MedNet, RadImageNet预训练模型

## 许可证

本项目基于 MIT 许可证开源。

## 贡献

欢迎贡献代码!请查看 [CONTRIBUTING.md](CONTRIBUTING.md) 了解详情。

## 引用

如果您在研究中使用了本框架,请引用:

```bibtex
@software{medvision_classification,
  title={MedVision-Classification: A PyTorch Lightning Framework for Medical Image Classification},
  author={Your Name},
  year={2025},
  url={https://github.com/Hi-Zhipeng/MedVision-classification}
}
```


"""
PyTorch Lightning module for medical image classification
"""

import torch
import torch.nn as nn
import pytorch_lightning as pl
from torch.optim import Adam, SGD, AdamW
from torch.optim.lr_scheduler import CosineAnnealingLR, ReduceLROnPlateau, StepLR
from typing import Dict, Any
from torchmetrics import MetricCollection, Accuracy, F1Score, Precision, Recall, AUROC

from .model_factory import create_model
from ..losses import create_loss


class ClassificationLightningModule(pl.LightningModule):
    """PyTorch Lightning module for medical image classification"""
    
    def __init__(
        self,
        model_name: str = "resnet50",
        num_classes: int = 2,
        pretrained: bool = True,
        loss_config: Dict[str, Any] = None,
        optimizer_config: Dict[str, Any] = None,
        scheduler_config: Dict[str, Any] = None,
        metrics_config: Dict[str, Any] = None,
        **model_kwargs
    ):
        super().__init__()
        self.save_hyperparameters()
        
        # Create model
        self.model = create_model(
            model_name=model_name,
            num_classes=num_classes,
            pretrained=pretrained,
            **model_kwargs
        )
        
        # Setup loss
        loss_config = loss_config or {"type": "cross_entropy"}
        self.loss_fn = create_loss(loss_config)
        
        # Setup metrics
        metrics_config = metrics_config or {}
        
        self.setup_metrics(metrics_config)
        
        # Store configs
        self.optimizer_config = optimizer_config or {"type": "adam", "lr": 1e-3}
        self.scheduler_config = scheduler_config or {"type": "cosine", "T_max": 100}
        
        # For logging
        self.train_step_outputs = []
        self.val_step_outputs = []
        self.test_step_outputs = []
    
    def setup_metrics(self, metrics_config: Dict[str, Any]):
        """Setup metrics for training, validation, and testing"""
        
        print(f"Setting up metrics with config: {metrics_config}")
        
        # Create metric collections using torchmetrics directly
        train_metrics = {}
        val_metrics = {}
        test_metrics = {}
        
        # Only create metrics specified in config
        for metric_name, metric_config in metrics_config.items():
            print(f"Creating metric: {metric_name} with config: {metric_config}")
            try:
                metric_type = metric_config.get("type", "accuracy").lower()
                task = "binary" if self.hparams.num_classes == 2 else "multiclass"
                
                if metric_type == "accuracy":
                    metric = Accuracy(task=task, num_classes=self.hparams.num_classes)
                elif metric_type == "f1":
                    metric = F1Score(task=task, num_classes=self.hparams.num_classes, average="macro")
                elif metric_type == "precision":
                    metric = Precision(task=task, num_classes=self.hparams.num_classes, average="macro")
                elif metric_type == "recall":
                    metric = Recall(task=task, num_classes=self.hparams.num_classes, average="macro")
                elif metric_type == "auroc":
                    metric = AUROC(task=task, num_classes=self.hparams.num_classes)
                else:
                    print(f"⚠️  Unknown metric type: {metric_type}, skipping")
                    continue
                
                train_metrics[metric_name] = metric.clone()
                val_metrics[metric_name] = metric.clone()
                test_metrics[metric_name] = metric.clone()
                print(f"✅ Created and cloned {metric_name}")
                    
            except Exception as e:
                print(f"❌ Could not create metric {metric_name}: {e}")
                import traceback
                traceback.print_exc()
                continue
        
        # Create ModuleDict collections
        self.train_metrics = nn.ModuleDict(train_metrics)
        self.val_metrics = nn.ModuleDict(val_metrics)
        self.test_metrics = nn.ModuleDict(test_metrics)
        
        print(f"Final train_metrics: {list(self.train_metrics.keys())}")
        print(f"Final val_metrics: {list(self.val_metrics.keys())}")
        print(f"Final test_metrics: {list(self.test_metrics.keys())}")
    
    def _update_metrics(self, metrics: nn.ModuleDict, logits: torch.Tensor, labels: torch.Tensor):
        """Update metrics with predictions and labels"""
        preds = torch.softmax(logits, dim=1)
        pred_classes = torch.argmax(preds, dim=1)
        
        for metric_name, metric in metrics.items():
            if metric_name == "auc":
                # AUC needs probabilities - for binary: preds[:, 1], for multiclass: preds
                if self.hparams.num_classes == 2:
                    metric.update(preds[:, 1], labels)
                else:
                    metric.update(preds, labels)
            else:
                # Other metrics need predicted class indices
                metric.update(pred_classes, labels)
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.model(x)
    
    # def training_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:
    #     images, labels = batch["image"], batch["label"]
    #     logits = self(images)
    #     loss = self.loss_fn(logits, labels)
        
    #     # Update metrics
    #     self._update_metrics(self.train_metrics, logits, labels)
        
    #     # Log loss
    #     self.log("train/loss", loss, prog_bar=True, on_step=True, on_epoch=True)
        
    #     self.train_step_outputs.append(loss)
    #     return loss
    
    # def on_train_epoch_end(self):
    #     # Compute and log metrics
    #     for metric_name, metric in self.train_metrics.items():
    #         value = metric.compute()
    #         self.log(f"train/{metric_name}", value, prog_bar=True)
    #         metric.reset()
        
    #     self.train_step_outputs.clear()
    
    # def validation_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:
    #     images, labels = batch["image"], batch["label"]

    #     logits = self(images)
    #     loss = self.loss_fn(logits, labels)
        
    #     # Update metrics
    #     self._update_metrics(self.val_metrics, logits, labels)
        
    #     # Log loss
    #     self.log("val/loss", loss, prog_bar=True, on_step=False, on_epoch=True)
        
    #     self.val_step_outputs.append(loss)
    #     return loss
    
    # def on_validation_epoch_end(self):
    #     # Compute and log metrics
    #     for metric_name, metric in self.val_metrics.items():
    #         value = metric.compute()
    #         self.log(f"val/{metric_name}", value, prog_bar=True, on_epoch=True)
    #         metric.reset()
        
    #     self.val_step_outputs.clear()
    
    # def test_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:
    #     images, labels = batch["image"], batch["label"]
    #     logits = self(images)
    #     loss = self.loss_fn(logits, labels)
        
    #     # Update metrics
    #     self._update_metrics(self.test_metrics, logits, labels)
        
    #     # Log loss
    #     self.log("test/loss", loss, on_step=False, on_epoch=True)
        
    #     self.test_step_outputs.append({
    #         "loss": loss,
    #         "preds": torch.softmax(logits, dim=1),
    #         "labels": labels
    #     })
    #     return loss
    
    # def on_test_epoch_end(self):
    #     # Compute metrics without logging to avoid deadlock
    #     for metric_name, metric in self.test_metrics.items():
    #         value = metric.compute()
    #         # Store metric values for later use if needed
    #         # self.log(f"test/{metric_name}", value)  # Removed to avoid deadlock
    #         metric.reset()
        
    #     self.test_step_outputs.clear()

# ...existing code...

    def training_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:
        images, labels = batch["image"], batch["label"]
        logits = self(images)
        loss = self.loss_fn(logits, labels)
        
        # Update metrics
        self._update_metrics(self.train_metrics, logits, labels)
        
        # Log loss
        self.log("train/loss", loss, prog_bar=True, on_step=True, on_epoch=True, sync_dist=True)
        
        # Log metrics in the step itself to avoid deadlock
        for metric_name, metric in self.train_metrics.items():
            self.log(f"train/{metric_name}", metric, prog_bar=True, on_step=False, on_epoch=True, sync_dist=True)
        
        self.train_step_outputs.append(loss)
        return loss

    def on_train_epoch_end(self):
        # Only reset metrics, don't log here to avoid deadlock
        for metric in self.train_metrics.values():
            metric.reset()
        
        self.train_step_outputs.clear()

    def validation_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:
        images, labels = batch["image"], batch["label"]
        logits = self(images)
        loss = self.loss_fn(logits, labels)
        
        # Update metrics
        self._update_metrics(self.val_metrics, logits, labels)
        
        # Log loss
        self.log("val/loss", loss, prog_bar=True, on_step=False, on_epoch=True, sync_dist=True)
        
        # Log metrics in the step itself to avoid deadlock
        for metric_name, metric in self.val_metrics.items():
            self.log(f"val/{metric_name}", metric, prog_bar=True, on_step=False, on_epoch=True, sync_dist=True)
        
        self.val_step_outputs.append(loss)
        return loss

    def on_validation_epoch_end(self):
        # Only reset metrics, don't log here to avoid deadlock
        for metric in self.val_metrics.values():
            metric.reset()
        
        self.val_step_outputs.clear()

    def test_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:
        images, labels = batch["image"], batch["label"]
        logits = self(images)
        loss = self.loss_fn(logits, labels)
        
        # Update metrics
        self._update_metrics(self.test_metrics, logits, labels)
        
        # Log loss
        self.log("test/loss", loss, on_step=False, on_epoch=True, sync_dist=True)
        
        # Log metrics in the step itself to avoid deadlock
        for metric_name, metric in self.test_metrics.items():
            self.log(f"test/{metric_name}", metric, on_step=False, on_epoch=True, sync_dist=True)
        
        self.test_step_outputs.append({
            "loss": loss,
            "preds": torch.softmax(logits, dim=1),
            "labels": labels
        })
        return loss

    def on_test_epoch_end(self):
        # Only reset metrics, don't log here to avoid deadlock
        
        for metric in self.test_metrics.values():
            metric.reset()

        self.test_step_outputs.clear()

    def predict_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> Dict[str, torch.Tensor]:
        images = batch["image"]
        logits = self(images)
        probs = torch.softmax(logits, dim=1)
        preds = torch.argmax(probs, dim=1)
        
        return {
            "predictions": preds,
            "probabilities": probs,
            "logits": logits
        }
    
    def configure_optimizers(self):
        # Setup optimizer
        optimizer_type = self.optimizer_config.get("type", "adam").lower()
        lr = self.optimizer_config.get("lr", 1e-3)
        weight_decay = self.optimizer_config.get("weight_decay", 0)
        
        if optimizer_type == "adam":
            optimizer = Adam(self.parameters(), lr=lr, weight_decay=weight_decay)
        elif optimizer_type == "adamw":
            optimizer = AdamW(self.parameters(), lr=lr, weight_decay=weight_decay)
        elif optimizer_type == "sgd":
            momentum = self.optimizer_config.get("momentum", 0.9)
            optimizer = SGD(self.parameters(), lr=lr, weight_decay=weight_decay, momentum=momentum)
        else:
            raise ValueError(f"Unsupported optimizer: {optimizer_type}")
        
        # Setup scheduler
        scheduler_type = self.scheduler_config.get("type", "cosine").lower()
        
        if scheduler_type == "cosine":
            T_max = self.scheduler_config.get("T_max", 100)
            eta_min = self.scheduler_config.get("eta_min", 0)
            scheduler = CosineAnnealingLR(optimizer, T_max=T_max, eta_min=eta_min)
            return [optimizer], [scheduler]
        elif scheduler_type == "plateau":
            patience = self.scheduler_config.get("patience", 10)
            factor = self.scheduler_config.get("factor", 0.5)
            monitor = self.scheduler_config.get("monitor", "val/loss")
            scheduler = ReduceLROnPlateau(optimizer, patience=patience, factor=factor)
            return {
                "optimizer": optimizer,
                "lr_scheduler": {
                    "scheduler": scheduler,
                    "monitor": monitor,
                    "interval": "epoch",
                    "frequency": 1
                }
            }
        elif scheduler_type == "step":
            step_size = self.scheduler_config.get("step_size", 30)
            gamma = self.scheduler_config.get("gamma", 0.1)
            scheduler = StepLR(optimizer, step_size=step_size, gamma=gamma)
            return [optimizer], [scheduler]
        else:
            return optimizer

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "medvision-classification",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "medical imaging, classification, pytorch, lightning, deep learning",
    "author": null,
    "author_email": "weizhipeng <weizhipeng@shu.edu.cn>",
    "download_url": "https://files.pythonhosted.org/packages/a0/55/e4b48c8b98e5e6f07ce2dd4f8da868b62afd4a455e1376f28e30e6f7abb1/medvision_classification-0.1.0.tar.gz",
    "platform": null,
    "description": "# MedVision-Classification\n\nMedVision-Classification \u662f\u4e00\u4e2a\u57fa\u4e8e PyTorch Lightning \u7684\u533b\u5b66\u5f71\u50cf\u5206\u7c7b\u6846\u67b6\uff0c\u63d0\u4f9b\u4e86\u8bad\u7ec3\u548c\u63a8\u7406\u7684\u7b80\u5355\u63a5\u53e3\u3002\n\n## \u7279\u70b9\n\n- \u57fa\u4e8e PyTorch Lightning \u7684\u9ad8\u7ea7\u63a5\u53e3\n- \u652f\u6301\u5e38\u89c1\u7684\u533b\u5b66\u5f71\u50cf\u683c\u5f0f\uff08NIfTI\u3001DICOM \u7b49\uff09\n- \u5185\u7f6e\u591a\u79cd\u5206\u7c7b\u6a21\u578b\u67b6\u6784\uff08ResNet\u3001DenseNet\u3001EfficientNet \u7b49\uff09\n- \u7075\u6d3b\u7684\u6570\u636e\u52a0\u8f7d\u548c\u9884\u5904\u7406\u7ba1\u9053\n- \u6a21\u5757\u5316\u8bbe\u8ba1\uff0c\u6613\u4e8e\u6269\u5c55\n- \u547d\u4ee4\u884c\u754c\u9762\u7528\u4e8e\u8bad\u7ec3\u548c\u63a8\u7406\n- \u652f\u6301\u4e8c\u5206\u7c7b\u548c\u591a\u5206\u7c7b\u4efb\u52a1\n\n## \u5b89\u88c5\n\n### \u7cfb\u7edf\u8981\u6c42\n\n- Python 3.8+\n- PyTorch 2.0+\n- CUDA (\u53ef\u9009\uff0c\u7528\u4e8eGPU\u52a0\u901f)\n\n### \u57fa\u672c\u5b89\u88c5\n\n\u6700\u7b80\u5355\u7684\u5b89\u88c5\u65b9\u5f0f\uff1a\n\n```bash\npip install -e .\n```\n\n### \u4ece\u6e90\u7801\u5b89\u88c5\n\n```bash\ngit clone https://github.com/yourusername/medvision-classification.git\ncd medvision-classification\npip install -e .\n```\n\n### \u4f7f\u7528requirements\u6587\u4ef6\n\n```bash\n# \u57fa\u672c\u73af\u5883\npip install -r requirements.txt\n\n# \u5f00\u53d1\u73af\u5883\npip install -r requirements-dev.txt\n```\n\n### \u4f7f\u7528conda\u73af\u5883\n\n\u63a8\u8350\u4f7f\u7528 conda \u521b\u5efa\u72ec\u7acb\u7684\u865a\u62df\u73af\u5883\uff1a\n\n```bash\n# \u521b\u5efa\u5e76\u6fc0\u6d3b\u73af\u5883\nconda env create -f environment.yml\nconda activate medvision-cls\n\n# \u5b89\u88c5\u9879\u76ee\u672c\u8eab\npip install -e .\n```\n\n## \u5feb\u901f\u5165\u95e8\n\n### \u8bad\u7ec32D\u6a21\u578b\n\n```bash\nMedVision-cls train configs/train_config.yml\n```\n\n### \u8bad\u7ec33D\u6a21\u578b\n\n```bash\nMedVision-cls train configs/train_3d_resnet_config.yml\n```\n\n### \u6d4b\u8bd5\u6a21\u578b\n\n```bash\nMedVision-cls test configs/test_config.yml\n```\n\n### \u63a8\u7406\n\n```bash\nMedVision-cls predict configs/inference_config.yml --input /path/to/image --output /path/to/output\n```\n\n## \u914d\u7f6e\u683c\u5f0f\n\n### 2D\u5206\u7c7b\u8bad\u7ec3\u914d\u7f6e\u793a\u4f8b\n\n```yaml\n# 2D ResNet Training Configuration\nseed: 42\n\ntask_dim: 2d\n\n# Model configuration\nmodel:\n  type: \"classification\"\n  network:\n    name: \"resnet50\"\n    pretrained: true\n  num_classes: 2\n\n  # Metrics to compute\n  metrics:\n    accuracy:\n      type: \"accuracy\"\n    f1:\n      type: \"f1\"\n    precision:\n      type: \"precision\"\n    recall:\n      type: \"recall\"\n    auc:\n      type: \"auroc\"\n        \n  # Loss configuration\n  loss:\n    type: \"cross_entropy\"\n    weight: null\n    label_smoothing: 0.0\n  \n  # Optimizer configuration\n  optimizer:\n    type: \"adam\"\n    lr: 0.001\n    weight_decay: 0.0001\n  \n  # Scheduler configuration\n  scheduler:\n    type: \"cosine\"\n    T_max: 100\n    eta_min: 0.00001\n\n# Data configuration\ndata:\n  type: \"medical\"\n  batch_size: 4\n  num_workers: 4\n  data_dir: \"data/classification\"\n  image_format: \"*.png\"\n  \n  # Transform configuration for 2D data\n  transforms:\n    image_size: [224, 224]\n    normalize: true\n    augment: true\n    \n  # Data split configuration\n  train_val_split: [0.8, 0.2]\n  seed: 42\n\n# Training configuration\ntraining:\n  max_epochs: 10\n  accelerator: \"gpu\"\n  devices: [0,1,2,3]  # Multi-GPU training\n  precision: 16\n  save_metrics: true\n  \n  # Callbacks\n  model_checkpoint:\n    monitor: \"val/accuracy\"\n    mode: \"max\"\n    save_top_k: 3\n    filename: \"epoch_{epoch:02d}-val_acc_{val/accuracy:.3f}\"\n\n# Validation configuration\nvalidation:\n  check_val_every_n_epoch: 1\n\n# Class names\nclass_names:\n  - \"Class_0\"\n  - \"Class_1\"\n\n# Output paths\noutputs:\n  output_dir: \"outputs\"\n  checkpoint_dir: \"outputs/checkpoints\"\n  log_dir: \"outputs/logs\"\n\n# Logging\nlogging:\n  log_every_n_steps: 10\n  wandb:\n    enabled: false\n    project: \"medvision-2d-classification\"\n    entity: null\n```\n\n### 3D\u5206\u7c7b\u8bad\u7ec3\u914d\u7f6e\u793a\u4f8b\n\n```yaml\n# 3D ResNet Training Configuration\nseed: 42\n\ntask_dim: 3D\n\n# Model configuration\nmodel:\n  type: \"classification\"\n  network:\n    name: \"resnet3d_18\"  # Options: resnet3d_18, resnet3d_34, resnet3d_50\n    pretrained: false    # No pretrained weights for 3D models\n    in_channels: 3       # Input channels (typically 1 for medical images)\n    dropout: 0.1\n  num_classes: 2\n\n  # Metrics to compute\n  metrics:\n    accuracy:\n      type: \"accuracy\"\n    f1:\n      type: \"f1\"\n    precision:\n      type: \"precision\"\n    recall:\n      type: \"recall\"\n    auc:\n      type: \"auroc\"\n\n  # Loss configuration\n  loss:\n    type: \"cross_entropy\"\n    weight: null\n    label_smoothing: 0.0\n  \n  # Optimizer configuration\n  optimizer:\n    type: \"adam\"\n    lr: 0.001\n    weight_decay: 0.0001\n  \n  # Scheduler configuration\n  scheduler:\n    type: \"cosine\"\n    T_max: 100\n    eta_min: 0.00001\n\n# Data configuration\ndata:\n  type: \"medical\"\n  batch_size: 4         # Smaller batch size for 3D data\n  num_workers: 4\n  data_dir: \"data/3D\"\n  image_format: \"*.nii.gz\"  # 3D medical image format\n  \n  # Transform configuration for 3D data\n  transforms:\n    image_size: [64, 64, 64]  # [D, H, W] for 3D volumes\n    normalize: true\n    augment: true\n    \n  # Data split configuration\n  train_val_split: [0.8, 0.2]\n  seed: 42\n\n# Training configuration\ntraining:\n  max_epochs: 5\n  accelerator: \"gpu\"\n  devices: 1            # Single GPU for 3D (memory intensive)\n  precision: 16         # Use mixed precision to save memory\n  \n  # Callbacks\n  early_stopping:\n    monitor: \"val/loss\"\n    patience: 10\n    mode: \"min\"\n  \n  model_checkpoint:\n    monitor: \"val/accuracy\"\n    mode: \"max\"\n    save_top_k: 3\n    filename: \"epoch_{epoch:02d}-val_acc_{val/accuracy:.3f}\"\n\n# Validation configuration\nvalidation:\n  check_val_every_n_epoch: 1\n\n# Output paths\noutputs:\n  output_dir: \"outputs\"\n  checkpoint_dir: \"outputs/checkpoints\"\n  log_dir: \"outputs/logs\"\n\n# Logging\nlogging:\n  log_every_n_steps: 10\n  wandb:\n    enabled: false\n    project: \"medvision-3d-classification\"\n    entity: null\n```\n\n### \u63a8\u7406\u914d\u7f6e\u793a\u4f8b\n\n```yaml\n# Model configuration\nmodel:\n  type: \"classification\"\n  network:\n    name: \"resnet50\"\n    pretrained: false\n  num_classes: 2\n  checkpoint_path: \"outputs/checkpoints/best_model.ckpt\"\n\n# Inference settings\ninference:\n  batch_size: 1\n  device: \"cuda:0\"  # \u6216 \"cpu\"\n  return_probabilities: true\n  class_names: [\"class0\", \"class1\"]\n  confidence_threshold: 0.5\n\n# Preprocessing\npreprocessing:\n  image_size: [224, 224]\n  normalize: true\n  mean: [0.485, 0.456, 0.406]\n  std: [0.229, 0.224, 0.225]\n\n# Output settings\noutput:\n  save_predictions: true\n  include_probabilities: true\n  format: \"json\"  # \u6216 \"csv\"\n```\n\n## \u6570\u636e\u683c\u5f0f\n\n### \u6587\u4ef6\u5939\u7ed3\u6784\n\n```\ndata/\n\u251c\u2500\u2500 classification/\n\u2502   \u251c\u2500\u2500 train/\n\u2502   \u2502   \u251c\u2500\u2500 class1/\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 image1.png\n\u2502   \u2502   \u2502   \u2514\u2500\u2500 image2.png\n\u2502   \u2502   \u2514\u2500\u2500 class2/\n\u2502   \u2502       \u251c\u2500\u2500 image3.png\n\u2502   \u2502       \u2514\u2500\u2500 image4.png\n\u2502   \u251c\u2500\u2500 val/\n\u2502   \u2502   \u251c\u2500\u2500 class1/\n\u2502   \u2502   \u2514\u2500\u2500 class2/\n\u2502   \u2514\u2500\u2500 test/\n\u2502       \u251c\u2500\u2500 class1/\n\u2502       \u2514\u2500\u2500 class2/\n```\n\n### CSV\u683c\u5f0f\n\n```csv\nimage_path,label\n/path/to/image1.png,0\n/path/to/image2.png,1\n/path/to/image3.png,0\n```\n\n## \u652f\u6301\u7684\u6a21\u578b\n\n- **ResNet\u7cfb\u5217**: ResNet18, ResNet34, ResNet50, ResNet101, ResNet152\n- **DenseNet\u7cfb\u5217**: DenseNet121, DenseNet161, DenseNet169, DenseNet201\n- **EfficientNet\u7cfb\u5217**: EfficientNet-B0 \u5230 EfficientNet-B7\n- **Vision Transformer**: ViT-Base, ViT-Large\n- **ConvNeXt**: ConvNeXt-Tiny, ConvNeXt-Small, ConvNeXt-Base\n- **Medical\u4e13\u7528**: MedNet, RadImageNet\u9884\u8bad\u7ec3\u6a21\u578b\n\n## \u8bb8\u53ef\u8bc1\n\n\u672c\u9879\u76ee\u57fa\u4e8e MIT \u8bb8\u53ef\u8bc1\u5f00\u6e90\u3002\n\n## \u8d21\u732e\n\n\u6b22\u8fce\u8d21\u732e\u4ee3\u7801\uff01\u8bf7\u67e5\u770b [CONTRIBUTING.md](CONTRIBUTING.md) \u4e86\u89e3\u8be6\u60c5\u3002\n\n## \u5f15\u7528\n\n\u5982\u679c\u60a8\u5728\u7814\u7a76\u4e2d\u4f7f\u7528\u4e86\u672c\u6846\u67b6\uff0c\u8bf7\u5f15\u7528\uff1a\n\n```bibtex\n@software{medvision_classification,\n  title={MedVision-Classification: A PyTorch Lightning Framework for Medical Image Classification},\n  author={Your Name},\n  year={2025},\n  url={https://github.com/Hi-Zhipeng/MedVision-classification}\n}\n```\n\n\n\"\"\"\nPyTorch Lightning module for medical image classification\n\"\"\"\n\nimport torch\nimport torch.nn as nn\nimport pytorch_lightning as pl\nfrom torch.optim import Adam, SGD, AdamW\nfrom torch.optim.lr_scheduler import CosineAnnealingLR, ReduceLROnPlateau, StepLR\nfrom typing import Dict, Any\nfrom torchmetrics import MetricCollection, Accuracy, F1Score, Precision, Recall, AUROC\n\nfrom .model_factory import create_model\nfrom ..losses import create_loss\n\n\nclass ClassificationLightningModule(pl.LightningModule):\n    \"\"\"PyTorch Lightning module for medical image classification\"\"\"\n    \n    def __init__(\n        self,\n        model_name: str = \"resnet50\",\n        num_classes: int = 2,\n        pretrained: bool = True,\n        loss_config: Dict[str, Any] = None,\n        optimizer_config: Dict[str, Any] = None,\n        scheduler_config: Dict[str, Any] = None,\n        metrics_config: Dict[str, Any] = None,\n        **model_kwargs\n    ):\n        super().__init__()\n        self.save_hyperparameters()\n        \n        # Create model\n        self.model = create_model(\n            model_name=model_name,\n            num_classes=num_classes,\n            pretrained=pretrained,\n            **model_kwargs\n        )\n        \n        # Setup loss\n        loss_config = loss_config or {\"type\": \"cross_entropy\"}\n        self.loss_fn = create_loss(loss_config)\n        \n        # Setup metrics\n        metrics_config = metrics_config or {}\n        \n        self.setup_metrics(metrics_config)\n        \n        # Store configs\n        self.optimizer_config = optimizer_config or {\"type\": \"adam\", \"lr\": 1e-3}\n        self.scheduler_config = scheduler_config or {\"type\": \"cosine\", \"T_max\": 100}\n        \n        # For logging\n        self.train_step_outputs = []\n        self.val_step_outputs = []\n        self.test_step_outputs = []\n    \n    def setup_metrics(self, metrics_config: Dict[str, Any]):\n        \"\"\"Setup metrics for training, validation, and testing\"\"\"\n        \n        print(f\"Setting up metrics with config: {metrics_config}\")\n        \n        # Create metric collections using torchmetrics directly\n        train_metrics = {}\n        val_metrics = {}\n        test_metrics = {}\n        \n        # Only create metrics specified in config\n        for metric_name, metric_config in metrics_config.items():\n            print(f\"Creating metric: {metric_name} with config: {metric_config}\")\n            try:\n                metric_type = metric_config.get(\"type\", \"accuracy\").lower()\n                task = \"binary\" if self.hparams.num_classes == 2 else \"multiclass\"\n                \n                if metric_type == \"accuracy\":\n                    metric = Accuracy(task=task, num_classes=self.hparams.num_classes)\n                elif metric_type == \"f1\":\n                    metric = F1Score(task=task, num_classes=self.hparams.num_classes, average=\"macro\")\n                elif metric_type == \"precision\":\n                    metric = Precision(task=task, num_classes=self.hparams.num_classes, average=\"macro\")\n                elif metric_type == \"recall\":\n                    metric = Recall(task=task, num_classes=self.hparams.num_classes, average=\"macro\")\n                elif metric_type == \"auroc\":\n                    metric = AUROC(task=task, num_classes=self.hparams.num_classes)\n                else:\n                    print(f\"\u26a0\ufe0f  Unknown metric type: {metric_type}, skipping\")\n                    continue\n                \n                train_metrics[metric_name] = metric.clone()\n                val_metrics[metric_name] = metric.clone()\n                test_metrics[metric_name] = metric.clone()\n                print(f\"\u2705 Created and cloned {metric_name}\")\n                    \n            except Exception as e:\n                print(f\"\u274c Could not create metric {metric_name}: {e}\")\n                import traceback\n                traceback.print_exc()\n                continue\n        \n        # Create ModuleDict collections\n        self.train_metrics = nn.ModuleDict(train_metrics)\n        self.val_metrics = nn.ModuleDict(val_metrics)\n        self.test_metrics = nn.ModuleDict(test_metrics)\n        \n        print(f\"Final train_metrics: {list(self.train_metrics.keys())}\")\n        print(f\"Final val_metrics: {list(self.val_metrics.keys())}\")\n        print(f\"Final test_metrics: {list(self.test_metrics.keys())}\")\n    \n    def _update_metrics(self, metrics: nn.ModuleDict, logits: torch.Tensor, labels: torch.Tensor):\n        \"\"\"Update metrics with predictions and labels\"\"\"\n        preds = torch.softmax(logits, dim=1)\n        pred_classes = torch.argmax(preds, dim=1)\n        \n        for metric_name, metric in metrics.items():\n            if metric_name == \"auc\":\n                # AUC needs probabilities - for binary: preds[:, 1], for multiclass: preds\n                if self.hparams.num_classes == 2:\n                    metric.update(preds[:, 1], labels)\n                else:\n                    metric.update(preds, labels)\n            else:\n                # Other metrics need predicted class indices\n                metric.update(pred_classes, labels)\n    \n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        return self.model(x)\n    \n    # def training_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:\n    #     images, labels = batch[\"image\"], batch[\"label\"]\n    #     logits = self(images)\n    #     loss = self.loss_fn(logits, labels)\n        \n    #     # Update metrics\n    #     self._update_metrics(self.train_metrics, logits, labels)\n        \n    #     # Log loss\n    #     self.log(\"train/loss\", loss, prog_bar=True, on_step=True, on_epoch=True)\n        \n    #     self.train_step_outputs.append(loss)\n    #     return loss\n    \n    # def on_train_epoch_end(self):\n    #     # Compute and log metrics\n    #     for metric_name, metric in self.train_metrics.items():\n    #         value = metric.compute()\n    #         self.log(f\"train/{metric_name}\", value, prog_bar=True)\n    #         metric.reset()\n        \n    #     self.train_step_outputs.clear()\n    \n    # def validation_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:\n    #     images, labels = batch[\"image\"], batch[\"label\"]\n\n    #     logits = self(images)\n    #     loss = self.loss_fn(logits, labels)\n        \n    #     # Update metrics\n    #     self._update_metrics(self.val_metrics, logits, labels)\n        \n    #     # Log loss\n    #     self.log(\"val/loss\", loss, prog_bar=True, on_step=False, on_epoch=True)\n        \n    #     self.val_step_outputs.append(loss)\n    #     return loss\n    \n    # def on_validation_epoch_end(self):\n    #     # Compute and log metrics\n    #     for metric_name, metric in self.val_metrics.items():\n    #         value = metric.compute()\n    #         self.log(f\"val/{metric_name}\", value, prog_bar=True, on_epoch=True)\n    #         metric.reset()\n        \n    #     self.val_step_outputs.clear()\n    \n    # def test_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:\n    #     images, labels = batch[\"image\"], batch[\"label\"]\n    #     logits = self(images)\n    #     loss = self.loss_fn(logits, labels)\n        \n    #     # Update metrics\n    #     self._update_metrics(self.test_metrics, logits, labels)\n        \n    #     # Log loss\n    #     self.log(\"test/loss\", loss, on_step=False, on_epoch=True)\n        \n    #     self.test_step_outputs.append({\n    #         \"loss\": loss,\n    #         \"preds\": torch.softmax(logits, dim=1),\n    #         \"labels\": labels\n    #     })\n    #     return loss\n    \n    # def on_test_epoch_end(self):\n    #     # Compute metrics without logging to avoid deadlock\n    #     for metric_name, metric in self.test_metrics.items():\n    #         value = metric.compute()\n    #         # Store metric values for later use if needed\n    #         # self.log(f\"test/{metric_name}\", value)  # Removed to avoid deadlock\n    #         metric.reset()\n        \n    #     self.test_step_outputs.clear()\n\n# ...existing code...\n\n    def training_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:\n        images, labels = batch[\"image\"], batch[\"label\"]\n        logits = self(images)\n        loss = self.loss_fn(logits, labels)\n        \n        # Update metrics\n        self._update_metrics(self.train_metrics, logits, labels)\n        \n        # Log loss\n        self.log(\"train/loss\", loss, prog_bar=True, on_step=True, on_epoch=True, sync_dist=True)\n        \n        # Log metrics in the step itself to avoid deadlock\n        for metric_name, metric in self.train_metrics.items():\n            self.log(f\"train/{metric_name}\", metric, prog_bar=True, on_step=False, on_epoch=True, sync_dist=True)\n        \n        self.train_step_outputs.append(loss)\n        return loss\n\n    def on_train_epoch_end(self):\n        # Only reset metrics, don't log here to avoid deadlock\n        for metric in self.train_metrics.values():\n            metric.reset()\n        \n        self.train_step_outputs.clear()\n\n    def validation_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:\n        images, labels = batch[\"image\"], batch[\"label\"]\n        logits = self(images)\n        loss = self.loss_fn(logits, labels)\n        \n        # Update metrics\n        self._update_metrics(self.val_metrics, logits, labels)\n        \n        # Log loss\n        self.log(\"val/loss\", loss, prog_bar=True, on_step=False, on_epoch=True, sync_dist=True)\n        \n        # Log metrics in the step itself to avoid deadlock\n        for metric_name, metric in self.val_metrics.items():\n            self.log(f\"val/{metric_name}\", metric, prog_bar=True, on_step=False, on_epoch=True, sync_dist=True)\n        \n        self.val_step_outputs.append(loss)\n        return loss\n\n    def on_validation_epoch_end(self):\n        # Only reset metrics, don't log here to avoid deadlock\n        for metric in self.val_metrics.values():\n            metric.reset()\n        \n        self.val_step_outputs.clear()\n\n    def test_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> torch.Tensor:\n        images, labels = batch[\"image\"], batch[\"label\"]\n        logits = self(images)\n        loss = self.loss_fn(logits, labels)\n        \n        # Update metrics\n        self._update_metrics(self.test_metrics, logits, labels)\n        \n        # Log loss\n        self.log(\"test/loss\", loss, on_step=False, on_epoch=True, sync_dist=True)\n        \n        # Log metrics in the step itself to avoid deadlock\n        for metric_name, metric in self.test_metrics.items():\n            self.log(f\"test/{metric_name}\", metric, on_step=False, on_epoch=True, sync_dist=True)\n        \n        self.test_step_outputs.append({\n            \"loss\": loss,\n            \"preds\": torch.softmax(logits, dim=1),\n            \"labels\": labels\n        })\n        return loss\n\n    def on_test_epoch_end(self):\n        # Only reset metrics, don't log here to avoid deadlock\n        \n        for metric in self.test_metrics.values():\n            metric.reset()\n\n        self.test_step_outputs.clear()\n\n    def predict_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> Dict[str, torch.Tensor]:\n        images = batch[\"image\"]\n        logits = self(images)\n        probs = torch.softmax(logits, dim=1)\n        preds = torch.argmax(probs, dim=1)\n        \n        return {\n            \"predictions\": preds,\n            \"probabilities\": probs,\n            \"logits\": logits\n        }\n    \n    def configure_optimizers(self):\n        # Setup optimizer\n        optimizer_type = self.optimizer_config.get(\"type\", \"adam\").lower()\n        lr = self.optimizer_config.get(\"lr\", 1e-3)\n        weight_decay = self.optimizer_config.get(\"weight_decay\", 0)\n        \n        if optimizer_type == \"adam\":\n            optimizer = Adam(self.parameters(), lr=lr, weight_decay=weight_decay)\n        elif optimizer_type == \"adamw\":\n            optimizer = AdamW(self.parameters(), lr=lr, weight_decay=weight_decay)\n        elif optimizer_type == \"sgd\":\n            momentum = self.optimizer_config.get(\"momentum\", 0.9)\n            optimizer = SGD(self.parameters(), lr=lr, weight_decay=weight_decay, momentum=momentum)\n        else:\n            raise ValueError(f\"Unsupported optimizer: {optimizer_type}\")\n        \n        # Setup scheduler\n        scheduler_type = self.scheduler_config.get(\"type\", \"cosine\").lower()\n        \n        if scheduler_type == \"cosine\":\n            T_max = self.scheduler_config.get(\"T_max\", 100)\n            eta_min = self.scheduler_config.get(\"eta_min\", 0)\n            scheduler = CosineAnnealingLR(optimizer, T_max=T_max, eta_min=eta_min)\n            return [optimizer], [scheduler]\n        elif scheduler_type == \"plateau\":\n            patience = self.scheduler_config.get(\"patience\", 10)\n            factor = self.scheduler_config.get(\"factor\", 0.5)\n            monitor = self.scheduler_config.get(\"monitor\", \"val/loss\")\n            scheduler = ReduceLROnPlateau(optimizer, patience=patience, factor=factor)\n            return {\n                \"optimizer\": optimizer,\n                \"lr_scheduler\": {\n                    \"scheduler\": scheduler,\n                    \"monitor\": monitor,\n                    \"interval\": \"epoch\",\n                    \"frequency\": 1\n                }\n            }\n        elif scheduler_type == \"step\":\n            step_size = self.scheduler_config.get(\"step_size\", 30)\n            gamma = self.scheduler_config.get(\"gamma\", 0.1)\n            scheduler = StepLR(optimizer, step_size=step_size, gamma=gamma)\n            return [optimizer], [scheduler]\n        else:\n            return optimizer\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A comprehensive PyTorch Lightning framework for medical image classification with support for 2D/3D images",
    "version": "0.1.0",
    "project_urls": {
        "Bug Tracker": "https://github.com/Hi-Zhipeng/MedVision-classification/issues",
        "Documentation": "https://github.com/Hi-Zhipeng/MedVision-classification#readme",
        "Homepage": "https://github.com/Hi-Zhipeng/MedVision-classification",
        "Repository": "https://github.com/Hi-Zhipeng/MedVision-classification"
    },
    "split_keywords": [
        "medical imaging",
        " classification",
        " pytorch",
        " lightning",
        " deep learning"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "25e76a9292eff1ad6a4801ffa0290fea61e6b07f88186f616d1c6eb564baa72b",
                "md5": "2230764f1564f60a034173b60d11641d",
                "sha256": "edfb9248f21cfb9c490c0b05c627352163f43f69453c1edc3c49611ff989b0a7"
            },
            "downloads": -1,
            "filename": "medvision_classification-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "2230764f1564f60a034173b60d11641d",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 54485,
            "upload_time": "2025-07-15T05:21:57",
            "upload_time_iso_8601": "2025-07-15T05:21:57.841051Z",
            "url": "https://files.pythonhosted.org/packages/25/e7/6a9292eff1ad6a4801ffa0290fea61e6b07f88186f616d1c6eb564baa72b/medvision_classification-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "a055e4b48c8b98e5e6f07ce2dd4f8da868b62afd4a455e1376f28e30e6f7abb1",
                "md5": "c7134a2d454e926c640eddd5f5230438",
                "sha256": "7ea941740eb64754892108ecf6966e040084ca5e97866f29a48589ddcc14c15e"
            },
            "downloads": -1,
            "filename": "medvision_classification-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "c7134a2d454e926c640eddd5f5230438",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 47624,
            "upload_time": "2025-07-15T05:22:00",
            "upload_time_iso_8601": "2025-07-15T05:22:00.065046Z",
            "url": "https://files.pythonhosted.org/packages/a0/55/e4b48c8b98e5e6f07ce2dd4f8da868b62afd4a455e1376f28e30e6f7abb1/medvision_classification-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-15 05:22:00",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Hi-Zhipeng",
    "github_project": "MedVision-classification",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [
        {
            "name": "torch",
            "specs": [
                [
                    ">=",
                    "2.0.0"
                ]
            ]
        },
        {
            "name": "torchvision",
            "specs": [
                [
                    ">=",
                    "0.15.0"
                ]
            ]
        },
        {
            "name": "pytorch-lightning",
            "specs": [
                [
                    ">=",
                    "2.0.0"
                ]
            ]
        },
        {
            "name": "lightning",
            "specs": [
                [
                    ">=",
                    "2.0.0"
                ]
            ]
        },
        {
            "name": "numpy",
            "specs": [
                [
                    ">=",
                    "1.21.0"
                ]
            ]
        },
        {
            "name": "pyyaml",
            "specs": [
                [
                    ">=",
                    "6.0"
                ]
            ]
        },
        {
            "name": "tqdm",
            "specs": [
                [
                    ">=",
                    "4.64.0"
                ]
            ]
        },
        {
            "name": "click",
            "specs": [
                [
                    ">=",
                    "8.0.0"
                ]
            ]
        },
        {
            "name": "pillow",
            "specs": [
                [
                    ">=",
                    "9.0.0"
                ]
            ]
        },
        {
            "name": "scikit-image",
            "specs": [
                [
                    ">=",
                    "0.19.0"
                ]
            ]
        },
        {
            "name": "pandas",
            "specs": [
                [
                    ">=",
                    "1.4.0"
                ]
            ]
        },
        {
            "name": "nibabel",
            "specs": [
                [
                    ">=",
                    "3.2.0"
                ]
            ]
        },
        {
            "name": "SimpleITK",
            "specs": [
                [
                    ">=",
                    "2.2.0"
                ]
            ]
        },
        {
            "name": "pydicom",
            "specs": [
                [
                    ">=",
                    "2.3.0"
                ]
            ]
        },
        {
            "name": "monai",
            "specs": [
                [
                    ">=",
                    "1.3.0"
                ]
            ]
        },
        {
            "name": "opencv-python",
            "specs": [
                [
                    ">=",
                    "4.7.0"
                ]
            ]
        },
        {
            "name": "albumentations",
            "specs": [
                [
                    ">=",
                    "1.3.0"
                ]
            ]
        },
        {
            "name": "matplotlib",
            "specs": [
                [
                    ">=",
                    "3.6.0"
                ]
            ]
        },
        {
            "name": "seaborn",
            "specs": [
                [
                    ">=",
                    "0.12.0"
                ]
            ]
        },
        {
            "name": "tensorboard",
            "specs": [
                [
                    ">=",
                    "2.10.0"
                ]
            ]
        },
        {
            "name": "wandb",
            "specs": [
                [
                    ">=",
                    "0.15.0"
                ]
            ]
        },
        {
            "name": "scikit-learn",
            "specs": [
                [
                    ">=",
                    "1.1.0"
                ]
            ]
        },
        {
            "name": "scipy",
            "specs": [
                [
                    ">=",
                    "1.9.0"
                ]
            ]
        },
        {
            "name": "torchmetrics",
            "specs": [
                [
                    ">=",
                    "0.11.0"
                ]
            ]
        },
        {
            "name": "timm",
            "specs": [
                [
                    ">=",
                    "0.9.0"
                ]
            ]
        },
        {
            "name": "efficientnet-pytorch",
            "specs": [
                [
                    ">=",
                    "0.7.0"
                ]
            ]
        },
        {
            "name": "hydra-core",
            "specs": [
                [
                    ">=",
                    "1.3.0"
                ]
            ]
        },
        {
            "name": "omegaconf",
            "specs": [
                [
                    ">=",
                    "2.3.0"
                ]
            ]
        },
        {
            "name": "rich",
            "specs": [
                [
                    ">=",
                    "13.0.0"
                ]
            ]
        }
    ],
    "lcname": "medvision-classification"
}
        
Elapsed time: 0.47058s