guohanghui commited on
Commit
3a59483
·
verified ·
1 Parent(s): 3e86bfa

Update MONAI/mcp_output/mcp_plugin/mcp_service.py

Browse files
MONAI/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  import sys
 
3
 
4
  # Add the local source directory to sys.path
5
  source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
@@ -7,127 +8,422 @@ if source_path not in sys.path:
7
  sys.path.insert(0, source_path)
8
 
9
  from fastmcp import FastMCP
 
10
 
11
- # Import core modules from the local source directory
12
- from monai.data import Dataset, DataLoader
13
- from monai.transforms import Compose, LoadImage, NormalizeIntensity
14
- from monai.networks.nets import UNet
15
- from monai.engines import SupervisedTrainer
16
- from monai.metrics import DiceMetric
 
 
 
 
 
17
 
18
  # Create the FastMCP service application
19
  mcp = FastMCP("monai_service")
20
 
21
- @mcp.tool(name="load_dataset", description="Load a dataset using MONAI's Dataset class")
22
- def load_dataset(data_dir: str) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  """
24
- Load a dataset from the specified directory.
25
 
26
  Parameters:
27
- - data_dir (str): The directory containing the dataset.
 
 
 
 
28
 
29
  Returns:
30
- - dict: A dictionary containing success status and the dataset object or error message.
31
  """
32
  try:
33
- dataset = Dataset(data_dir)
34
- return {"success": True, "result": dataset}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  except Exception as e:
36
- return {"success": False, "error": str(e)}
37
 
38
- @mcp.tool(name="create_dataloader", description="Create a DataLoader for the dataset")
39
- def create_dataloader(dataset: Dataset, batch_size: int) -> dict:
 
 
 
 
 
 
40
  """
41
- Create a DataLoader for the given dataset.
42
 
43
  Parameters:
44
- - dataset (Dataset): The dataset to load.
45
- - batch_size (int): The number of samples per batch.
 
 
46
 
47
  Returns:
48
- - dict: A dictionary containing success status and the DataLoader object or error message.
49
  """
50
  try:
51
- dataloader = DataLoader(dataset, batch_size=batch_size)
52
- return {"success": True, "result": dataloader}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  except Exception as e:
54
- return {"success": False, "error": str(e)}
 
55
 
56
- @mcp.tool(name="apply_transforms", description="Apply transforms to the dataset")
57
- def apply_transforms(dataset: Dataset) -> dict:
 
 
 
 
58
  """
59
- Apply a series of transforms to the dataset.
60
 
61
  Parameters:
62
- - dataset (Dataset): The dataset to transform.
 
 
63
 
64
  Returns:
65
- - dict: A dictionary containing success status and the transformed dataset or error message.
66
  """
67
  try:
68
- transforms = Compose([LoadImage(), NormalizeIntensity()])
69
- transformed_dataset = [transforms(item) for item in dataset]
70
- return {"success": True, "result": transformed_dataset}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  except Exception as e:
72
- return {"success": False, "error": str(e)}
73
 
74
- @mcp.tool(name="initialize_unet", description="Initialize a UNet model")
75
- def initialize_unet(spatial_dims: int, in_channels: int, out_channels: int) -> dict:
 
 
 
 
 
76
  """
77
- Initialize a UNet model with the specified parameters.
78
 
79
  Parameters:
80
- - spatial_dims (int): The number of spatial dimensions.
81
- - in_channels (int): The number of input channels.
82
- - out_channels (int): The number of output channels.
83
 
84
  Returns:
85
- - dict: A dictionary containing success status and the UNet model or error message.
86
  """
87
  try:
88
- model = UNet(spatial_dims=spatial_dims, in_channels=in_channels, out_channels=out_channels)
89
- return {"success": True, "result": model}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  except Exception as e:
91
- return {"success": False, "error": str(e)}
 
92
 
93
- @mcp.tool(name="train_model", description="Train a model using MONAI's SupervisedTrainer")
94
- def train_model(model, dataloader: DataLoader, max_epochs: int) -> dict:
 
 
 
 
 
 
95
  """
96
- Train a model using the specified dataloader and number of epochs.
97
 
98
  Parameters:
99
- - model: The model to train.
100
- - dataloader (DataLoader): The DataLoader for training data.
101
- - max_epochs (int): The maximum number of training epochs.
 
 
102
 
103
  Returns:
104
- - dict: A dictionary containing success status and training results or error message.
105
  """
106
  try:
107
- trainer = SupervisedTrainer(max_epochs=max_epochs, train_data_loader=dataloader, network=model)
108
- trainer.run()
109
- return {"success": True, "result": "Training completed"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  except Exception as e:
111
- return {"success": False, "error": str(e)}
112
 
113
- @mcp.tool(name="calculate_dice", description="Calculate Dice metric for model evaluation")
114
- def calculate_dice(predictions, targets) -> dict:
 
 
 
 
 
115
  """
116
- Calculate the Dice metric for the given predictions and targets.
117
 
118
  Parameters:
119
- - predictions: The model predictions.
120
- - targets: The ground truth targets.
 
121
 
122
  Returns:
123
- - dict: A dictionary containing success status and the Dice score or error message.
124
  """
125
  try:
126
- dice_metric = DiceMetric()
127
- dice_score = dice_metric(predictions, targets)
128
- return {"success": True, "result": dice_score}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  except Exception as e:
130
- return {"success": False, "error": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
  def create_app() -> FastMCP:
133
  """
 
1
  import os
2
  import sys
3
+ from typing import List, Optional, Dict, Any
4
 
5
  # Add the local source directory to sys.path
6
  source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
 
8
  sys.path.insert(0, source_path)
9
 
10
  from fastmcp import FastMCP
11
+ import numpy as np
12
 
13
+ # Import MONAI modules
14
+ import monai
15
+ from monai.transforms import (
16
+ Compose, LoadImage, EnsureChannelFirst, ScaleIntensity,
17
+ NormalizeIntensity, Resize, RandRotate, RandFlip, ToTensor
18
+ )
19
+ from monai.networks.nets import UNet, VNet, AttentionUnet, SegResNet, DenseNet121
20
+ from monai.losses import DiceLoss, DiceCELoss, FocalLoss, TverskyLoss
21
+ from monai.metrics import DiceMetric, MeanIoU, HausdorffDistanceMetric
22
+ from monai.data import decollate_batch
23
+ from monai.inferers import sliding_window_inference
24
 
25
  # Create the FastMCP service application
26
  mcp = FastMCP("monai_service")
27
 
28
+
29
+ @mcp.tool(name="get_monai_info", description="Get MONAI library information and configuration")
30
+ def get_monai_info() -> dict:
31
+ """
32
+ Get MONAI library version and system configuration.
33
+
34
+ Returns:
35
+ - dict: MONAI version and configuration info.
36
+ """
37
+ try:
38
+ config = monai.config.get_config_values()
39
+ return {
40
+ "success": True,
41
+ "result": {
42
+ "version": monai.__version__,
43
+ "config": {k: str(v) for k, v in config.items()}
44
+ },
45
+ "error": None
46
+ }
47
+ except Exception as e:
48
+ return {"success": False, "result": None, "error": str(e)}
49
+
50
+
51
+ @mcp.tool(name="create_unet_model", description="Create and initialize a UNet model")
52
+ def create_unet_model(
53
+ spatial_dims: int = 3,
54
+ in_channels: int = 1,
55
+ out_channels: int = 2,
56
+ channels: List[int] = None,
57
+ strides: List[int] = None
58
+ ) -> dict:
59
  """
60
+ Create a UNet model for medical image segmentation.
61
 
62
  Parameters:
63
+ - spatial_dims: Number of spatial dimensions (2 or 3).
64
+ - in_channels: Number of input channels.
65
+ - out_channels: Number of output channels/classes.
66
+ - channels: Feature channels per layer (default: [16, 32, 64, 128, 256]).
67
+ - strides: Strides per layer (default: [2, 2, 2, 2]).
68
 
69
  Returns:
70
+ - dict: Model information and parameter count.
71
  """
72
  try:
73
+ if channels is None:
74
+ channels = [16, 32, 64, 128, 256]
75
+ if strides is None:
76
+ strides = [2, 2, 2, 2]
77
+
78
+ model = UNet(
79
+ spatial_dims=spatial_dims,
80
+ in_channels=in_channels,
81
+ out_channels=out_channels,
82
+ channels=channels,
83
+ strides=strides,
84
+ num_res_units=2
85
+ )
86
+
87
+ total_params = sum(p.numel() for p in model.parameters())
88
+ trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
89
+
90
+ return {
91
+ "success": True,
92
+ "result": {
93
+ "model_type": "UNet",
94
+ "spatial_dims": spatial_dims,
95
+ "in_channels": in_channels,
96
+ "out_channels": out_channels,
97
+ "channels": channels,
98
+ "strides": strides,
99
+ "total_parameters": total_params,
100
+ "trainable_parameters": trainable_params
101
+ },
102
+ "error": None
103
+ }
104
  except Exception as e:
105
+ return {"success": False, "result": None, "error": str(e)}
106
 
107
+
108
+ @mcp.tool(name="create_segresnet_model", description="Create a SegResNet model")
109
+ def create_segresnet_model(
110
+ spatial_dims: int = 3,
111
+ in_channels: int = 1,
112
+ out_channels: int = 2,
113
+ init_filters: int = 16
114
+ ) -> dict:
115
  """
116
+ Create a SegResNet model for medical image segmentation.
117
 
118
  Parameters:
119
+ - spatial_dims: Number of spatial dimensions (2 or 3).
120
+ - in_channels: Number of input channels.
121
+ - out_channels: Number of output channels/classes.
122
+ - init_filters: Initial number of filters.
123
 
124
  Returns:
125
+ - dict: Model information and parameter count.
126
  """
127
  try:
128
+ model = SegResNet(
129
+ spatial_dims=spatial_dims,
130
+ in_channels=in_channels,
131
+ out_channels=out_channels,
132
+ init_filters=init_filters
133
+ )
134
+
135
+ total_params = sum(p.numel() for p in model.parameters())
136
+
137
+ return {
138
+ "success": True,
139
+ "result": {
140
+ "model_type": "SegResNet",
141
+ "spatial_dims": spatial_dims,
142
+ "in_channels": in_channels,
143
+ "out_channels": out_channels,
144
+ "init_filters": init_filters,
145
+ "total_parameters": total_params
146
+ },
147
+ "error": None
148
+ }
149
  except Exception as e:
150
+ return {"success": False, "result": None, "error": str(e)}
151
+
152
 
153
+ @mcp.tool(name="compute_dice_score", description="Compute Dice score between prediction and ground truth")
154
+ def compute_dice_score(
155
+ prediction: List[List[List[float]]],
156
+ ground_truth: List[List[List[float]]],
157
+ include_background: bool = False
158
+ ) -> dict:
159
  """
160
+ Compute Dice similarity coefficient between prediction and ground truth.
161
 
162
  Parameters:
163
+ - prediction: Predicted segmentation as nested list (2D or 3D).
164
+ - ground_truth: Ground truth segmentation as nested list.
165
+ - include_background: Whether to include background class.
166
 
167
  Returns:
168
+ - dict: Dice score for each class.
169
  """
170
  try:
171
+ import torch
172
+
173
+ pred_tensor = torch.tensor(prediction).unsqueeze(0).unsqueeze(0).float()
174
+ gt_tensor = torch.tensor(ground_truth).unsqueeze(0).unsqueeze(0).float()
175
+
176
+ dice_metric = DiceMetric(include_background=include_background, reduction="mean")
177
+ dice_metric(y_pred=pred_tensor, y=gt_tensor)
178
+ dice_score = dice_metric.aggregate().item()
179
+ dice_metric.reset()
180
+
181
+ return {
182
+ "success": True,
183
+ "result": {
184
+ "dice_score": dice_score,
185
+ "include_background": include_background
186
+ },
187
+ "error": None
188
+ }
189
  except Exception as e:
190
+ return {"success": False, "result": None, "error": str(e)}
191
 
192
+
193
+ @mcp.tool(name="compute_loss", description="Compute segmentation loss")
194
+ def compute_loss(
195
+ prediction: List[List[List[float]]],
196
+ ground_truth: List[List[List[float]]],
197
+ loss_type: str = "dice"
198
+ ) -> dict:
199
  """
200
+ Compute segmentation loss between prediction and ground truth.
201
 
202
  Parameters:
203
+ - prediction: Predicted logits as nested list.
204
+ - ground_truth: Ground truth labels as nested list.
205
+ - loss_type: Type of loss ('dice', 'dice_ce', 'focal', 'tversky').
206
 
207
  Returns:
208
+ - dict: Loss value.
209
  """
210
  try:
211
+ import torch
212
+
213
+ pred_tensor = torch.tensor(prediction).unsqueeze(0).unsqueeze(0).float()
214
+ gt_tensor = torch.tensor(ground_truth).unsqueeze(0).unsqueeze(0).float()
215
+
216
+ if loss_type == "dice":
217
+ loss_fn = DiceLoss(sigmoid=True)
218
+ elif loss_type == "dice_ce":
219
+ loss_fn = DiceCELoss(sigmoid=True)
220
+ elif loss_type == "focal":
221
+ loss_fn = FocalLoss()
222
+ elif loss_type == "tversky":
223
+ loss_fn = TverskyLoss(sigmoid=True)
224
+ else:
225
+ return {"success": False, "result": None, "error": f"Unknown loss type: {loss_type}"}
226
+
227
+ loss_value = loss_fn(pred_tensor, gt_tensor).item()
228
+
229
+ return {
230
+ "success": True,
231
+ "result": {
232
+ "loss_type": loss_type,
233
+ "loss_value": loss_value
234
+ },
235
+ "error": None
236
+ }
237
  except Exception as e:
238
+ return {"success": False, "result": None, "error": str(e)}
239
+
240
 
241
+ @mcp.tool(name="apply_intensity_transforms", description="Apply intensity transforms to an image")
242
+ def apply_intensity_transforms(
243
+ image: List[List[List[float]]],
244
+ normalize: bool = True,
245
+ scale_intensity: bool = False,
246
+ target_min: float = 0.0,
247
+ target_max: float = 1.0
248
+ ) -> dict:
249
  """
250
+ Apply intensity transforms to a medical image.
251
 
252
  Parameters:
253
+ - image: Input image as nested list (2D or 3D).
254
+ - normalize: Whether to normalize intensity (zero mean, unit std).
255
+ - scale_intensity: Whether to scale intensity to target range.
256
+ - target_min: Minimum value for scaling.
257
+ - target_max: Maximum value for scaling.
258
 
259
  Returns:
260
+ - dict: Transformed image statistics.
261
  """
262
  try:
263
+ img_array = np.array(image, dtype=np.float32)
264
+
265
+ transforms_list = []
266
+ applied_transforms = []
267
+
268
+ if normalize:
269
+ transforms_list.append(NormalizeIntensity())
270
+ applied_transforms.append("NormalizeIntensity")
271
+
272
+ if scale_intensity:
273
+ transforms_list.append(ScaleIntensity(minv=target_min, maxv=target_max))
274
+ applied_transforms.append(f"ScaleIntensity({target_min}, {target_max})")
275
+
276
+ if transforms_list:
277
+ transform = Compose(transforms_list)
278
+ result = transform(img_array)
279
+ else:
280
+ result = img_array
281
+
282
+ return {
283
+ "success": True,
284
+ "result": {
285
+ "applied_transforms": applied_transforms,
286
+ "output_shape": list(result.shape),
287
+ "output_min": float(result.min()),
288
+ "output_max": float(result.max()),
289
+ "output_mean": float(result.mean()),
290
+ "output_std": float(result.std())
291
+ },
292
+ "error": None
293
+ }
294
  except Exception as e:
295
+ return {"success": False, "result": None, "error": str(e)}
296
 
297
+
298
+ @mcp.tool(name="resize_image", description="Resize a medical image using MONAI")
299
+ def resize_image(
300
+ image: List[List[List[float]]],
301
+ spatial_size: List[int],
302
+ mode: str = "trilinear"
303
+ ) -> dict:
304
  """
305
+ Resize a medical image to target spatial size.
306
 
307
  Parameters:
308
+ - image: Input image as nested list (2D or 3D).
309
+ - spatial_size: Target spatial size [H, W] or [D, H, W].
310
+ - mode: Interpolation mode ('nearest', 'bilinear', 'trilinear').
311
 
312
  Returns:
313
+ - dict: Resized image info.
314
  """
315
  try:
316
+ img_array = np.array(image, dtype=np.float32)
317
+ original_shape = img_array.shape
318
+
319
+ # Add channel dimension if needed
320
+ if len(img_array.shape) == len(spatial_size):
321
+ img_array = img_array[np.newaxis, ...]
322
+
323
+ resize_transform = Resize(spatial_size=spatial_size, mode=mode)
324
+ resized = resize_transform(img_array)
325
+
326
+ return {
327
+ "success": True,
328
+ "result": {
329
+ "original_shape": list(original_shape),
330
+ "target_size": spatial_size,
331
+ "output_shape": list(resized.shape),
332
+ "interpolation_mode": mode
333
+ },
334
+ "error": None
335
+ }
336
  except Exception as e:
337
+ return {"success": False, "result": None, "error": str(e)}
338
+
339
+
340
+ @mcp.tool(name="get_network_architectures", description="Get details about available network architectures")
341
+ def get_network_architectures() -> dict:
342
+ """
343
+ Get detailed information about available MONAI network architectures.
344
+
345
+ Returns:
346
+ - dict: Network architecture details.
347
+ """
348
+ try:
349
+ architectures = {
350
+ "UNet": {
351
+ "description": "U-Net architecture for semantic segmentation",
352
+ "use_case": "General medical image segmentation",
353
+ "parameters": ["spatial_dims", "in_channels", "out_channels", "channels", "strides"]
354
+ },
355
+ "VNet": {
356
+ "description": "V-Net for volumetric medical image segmentation",
357
+ "use_case": "3D medical image segmentation",
358
+ "parameters": ["spatial_dims", "in_channels", "out_channels"]
359
+ },
360
+ "AttentionUnet": {
361
+ "description": "Attention U-Net with attention gates",
362
+ "use_case": "Segmentation with attention mechanism",
363
+ "parameters": ["spatial_dims", "in_channels", "out_channels", "channels", "strides"]
364
+ },
365
+ "SegResNet": {
366
+ "description": "ResNet-based encoder-decoder for segmentation",
367
+ "use_case": "Medical image segmentation with residual connections",
368
+ "parameters": ["spatial_dims", "in_channels", "out_channels", "init_filters"]
369
+ },
370
+ "SwinUNETR": {
371
+ "description": "Swin Transformer based U-Net",
372
+ "use_case": "State-of-the-art medical image segmentation",
373
+ "parameters": ["img_size", "in_channels", "out_channels", "feature_size"]
374
+ },
375
+ "DenseNet121": {
376
+ "description": "DenseNet for classification",
377
+ "use_case": "Medical image classification",
378
+ "parameters": ["spatial_dims", "in_channels", "out_channels"]
379
+ }
380
+ }
381
+
382
+ return {"success": True, "result": architectures, "error": None}
383
+ except Exception as e:
384
+ return {"success": False, "result": None, "error": str(e)}
385
+
386
+
387
+ @mcp.tool(name="create_densenet_classifier", description="Create a DenseNet classifier model")
388
+ def create_densenet_classifier(
389
+ spatial_dims: int = 3,
390
+ in_channels: int = 1,
391
+ out_channels: int = 2
392
+ ) -> dict:
393
+ """
394
+ Create a DenseNet121 classifier for medical image classification.
395
+
396
+ Parameters:
397
+ - spatial_dims: Number of spatial dimensions (2 or 3).
398
+ - in_channels: Number of input channels.
399
+ - out_channels: Number of output classes.
400
+
401
+ Returns:
402
+ - dict: Model information.
403
+ """
404
+ try:
405
+ model = DenseNet121(
406
+ spatial_dims=spatial_dims,
407
+ in_channels=in_channels,
408
+ out_channels=out_channels
409
+ )
410
+
411
+ total_params = sum(p.numel() for p in model.parameters())
412
+
413
+ return {
414
+ "success": True,
415
+ "result": {
416
+ "model_type": "DenseNet121",
417
+ "spatial_dims": spatial_dims,
418
+ "in_channels": in_channels,
419
+ "out_channels": out_channels,
420
+ "total_parameters": total_params
421
+ },
422
+ "error": None
423
+ }
424
+ except Exception as e:
425
+ return {"success": False, "result": None, "error": str(e)}
426
+
427
 
428
  def create_app() -> FastMCP:
429
  """