脑PET图像分析和疾病预测挑战赛代码笔记

逻辑回归baseline

1 基本代码逐行解析

1.1导入所需的库

# -*- coding: utf-8 -*-
import glob                # 获取文件路径
import numpy as np		   # 引入用于科学计算的numpy库,并将其定义为np。
import pandas as pd		   # 进行数据分析和处理的一个经典库。
import nibabel as nib      # 处理医学图像数据
from nibabel.viewers import OrthoSlicer3D    # 图像可视化
from collections import Counter              # 计数统计
注:# -*- coding: utf-8 -*- 在基于Python 3 的代码开头必须加。因为这行代码是一个注释,用于告诉Python解释器该文件的编码格式是UTF-8。在Python 2中,默认编码格式是ASCII,而在Python 3中,默认编码格式是UTF-8。如果你的代码中包含了**非ASCII字符(如中文、日文、韩文等**),那么就需要在文件开头添加这行注释,以确保Python解释器能够正确地读取和处理这些字符。
  • glob:这个库可以使用通配符来匹配文件路径,例如*.txt可以匹配所有扩展名为.txt的文件。在这个代码中,glob库被用来获取训练集和测试集的文件路径。
  • numpy:它是Python中用于科学计算的一个基础库。它提供了高性能的多维数组对象和用于处理这些数组的工具。NumPy的主要功能包括:
    • 快速的向量化操作,如数组的加、减、乘、除等。
    • 用于处理多维数组的函数和操作。
    • 线性代数、傅里叶变换和随机数生成等功能。
    • 与其他科学计算库的接口,如SciPy、Pandas和Matplotlib等。
  • pandas:它是一个开源数据分析和数据处理库,提供了快速、灵活和富有表现力的数据结构,旨在使数据清洗、准备和分析变得更加容易和直观。Pandas通常用于处理和分析表格数据,例如CSV文件或数据库中的表格。
  • nibabel:它是一个用于处理医学图像数据的 Python 库。它可以读取和写入常见的医学图像格式,如 NIfTI 和 DICOM,以及其他一些格式。它还提供了一些用于处理和分析医学图像数据的工具和函数。在这个代码中,nibabel 库被用来加载 PET 图像数据。
  • nibabel.viewers:它是nibabel库中的一个模块,在本代码中从该模块导入了一个OrthoSlicer3D函数,OrthoSlicer3D 函数用于创建一个交互式的 3D 图像查看器,允许用户在三个不同的平面上切片图像:矢状面、冠状面和轴向面。
  • collections:这是一个Python内置的模块,从该模块中导入了Counter类,用于计数和统计元素出现的次数。在这段代码中,Counter类被用于对每个样本的30次预测结果进行投票,选出最多的类别作为该样本的最终预测类别。

1.2 读取训练集和测试集的文件路径

train_path = glob.glob('D:/PET/Train/*/*')
test_path = glob.glob('D:/PET/Test/*')

train_path 是储存训练集文件地址的列表
test_path 是储存测试集文件地址的列表
第一行代码使用了glob模块的glob函数,它可以返回一个符合特定规则的文件路径列表。在这里,我们使用了通配符来匹配文件路径中的任意字符,从而获取了训练集中所有文件的路径。具体来说,我们使用了两个**,这表示我们希望匹配任意层级的子目录。
第二行代码也是使用了glob函数,但是只匹配了测试集中的文件路径。

1.3 打乱测试集和训练集的顺序

np.random.shuffle(train_path)
np.random.shuffle(test_path)

np.random.shuffle() 是numpy库中的一个函数,它的作用是将一个序列随机打乱,即将序列中的元素随机排列。这个函数会直接修改原序列,不会返回新的序列。
这两行代码的作用是打乱训练集和测试集的顺序,使得每次运行程序时,训练集和测试集的顺序都是随机的,从而避免了模型过拟合的问题
具体来说,np.random.shuffle(train_path)会将训练集中的文件路径随机打乱,np.random.shuffle(test_path)会将测试集中的文件路径随机打乱。这样,在后续的代码中,每次读取训练集和测试集时,它们的顺序都是随机的,从而可以更好地训练模型和评估模型的性能。

1.4 定义特征提取函数

# 对PET文件提取特征
def extract_feature(path):
    # 加载PET图像数据
    img = nib.load(path)
    # 获取第一个通道的数据
    img = img.dataobj[:, :, :, 0]    
    # 随机筛选其中的10个通道提取特征
    random_img = img[:, :, np.random.choice(range(img.shape[2]), 10)]    
   
    # 对图片计算统计值
    feat = [
        (random_img != 0).sum(),               # 非零像素的数量
        (random_img == 0).sum(),               # 零像素的数量
        random_img.mean(),                     # 平均值
        random_img.std(),                      # 标准差
        len(np.where(random_img.mean(0))[0]),  # 在列方向上平均值不为零的数量
        len(np.where(random_img.mean(1))[0]),  # 在行方向上平均值不为零的数量
        random_img.mean(0).max(),              # 列方向上的最大平均值
        random_img.mean(1).max()               # 行方向上的最大平均值
    ]
    
    # 根据路径判断样本类别('NC'表示正常,'MCI'表示异常)
    if 'NC' in path:
        return feat + ['NC']
    else:
        return feat + ['MCI']

该段代码 def 了一个特征提取函数 extract_feature( ) 。

img = nib.load(path)	

这行代码的作用是从指定路径 path 加载医学图像数据。nib 是一个 Python 库,用于处理医学图像数据。load() 是 nib 库中的一个函数,用于加载医学图像数据。在这里,path 是一个字符串,表示医学图像数据的路径。nib.load() 函数将读取该路径下的医学图像数据,并将其存储在 img 变量中。

img = img.dataobj[:, :, :, 0]

这行代码的作用是从一个医学图像数据中提取出第一个通道的数据。在这里,img 是一个nibabel 库中的图像对象,它包含了医学图像的数据和元数据。dataobj 是一个属性,它包含了图像数据的 numpy 数组。这个数组的维度是(x,y,z,c),其中x,y,z是图像的空间坐标,c是通道数1。在这里,我们使用[:, :, :, 0]来提取第一个通道的数据(图像的强度值,也就是像素的灰度值或者颜色值),即在第四个维度上选择索引为0的通道。

random_img = img[:, :, np.random.choice(range(img.shape[2]), 10)]

这行代码的作用是从一个三维的医学图像数据中,随机选择10个切片,然后提取这些切片的图像数据。具体来说,这行代码中:

  • img 是一个三维的医学图像数据,它包含了很多个切片,每个切片都是一个二维的矩阵。

  • img.shape[2] 表示 img 中切片的数量。

  • range(img.shape[2]) 生成了一个从 0 到 img.shape[2] - 1 的整数序列。

  • np.random.choice(range(img.shape[2]), 10) 随机选择了这个整数序列中的 10 个数,这些数表示了被选中的切片的索引。

  • img[:, :, np.random.choice(range(img.shape[2]), 10)] 选择了 img 中所有行和列,以及被选中的 10 个切片,生成了一个三维的数组 random_img,它包含了被选中的 10 个切片的图像数据。

      feat = [
      (random_img != 0).sum(),               # 非零像素的数量
      (random_img == 0).sum(),               # 零像素的数量
      random_img.mean(),                     # 平均值
      random_img.std(),                      # 标准差
      len(np.where(random_img.mean(0))[0]),  # 在列方向上平均值不为零的数量
      len(np.where(random_img.mean(1))[0]),  # 在行方向上平均值不为零的数量
      random_img.mean(0).max(),              # 列方向上的最大平均值
      random_img.mean(1).max()               # 行方向上的最大平均值 
      ]
    

这段代码定义了一个名为 feat 的列表,其中包含了8个元素。这些元素是对一个名为 random_img 的图像进行统计分析得到的。
具体来说,这些元素分别是:

  1. (random_img != 0).sum():非零像素的数量。这个统计量可以帮助我们了解图像中有多少像素是有效的,也就是不为零的。

  2. (random_img == 0).sum():零像素的数量。这个统计量可以帮助我们了解图像中有多少像素是无效的,也就是为零的。

  3. random_img.mean():图像的平均值。这个统计量可以帮助我们了解图像的亮度分布情况,也就是图像的整体亮度水平。

  4. random_img.std():图像的标准差。这个统计量可以帮助我们了解图像的亮度变化情况,也就是图像的亮度分布的离散程度。

  5. len(np.where(random_img.mean(0))[0]):在列方向上平均值不为零的数量。这个统计量可以帮助我们了解图像的梯度分布情况,也就是图像在列方向上的亮度变化情况。

  6. len(np.where(random_img.mean(1))[0]):在行方向上平均值不为零的数量。这个统计量可以帮助我们了解图像的梯度分布情况,也就是图像在行方向上的亮度变化情况。

  7. random_img.mean(0).max():列方向上的最大平均值。这个统计量可以帮助我们了解图像的主要亮度区域,也就是图像在列方向上的亮度最高的区域。

  8. random_img.mean(1).max():行方向上的最大平均值。这个统计量可以帮助我们了解图像的主要亮度区域,也就是图像在行方向上的亮度最高的区域。

     if 'NC' in path:
     return feat + ['NC']
     else:
     return feat + ['MCI']
    

这段代码是一个条件语句,它的作用是根据文件路径path中是否包含字符串’NC’来判断样本的类别。如果文件路径path中包含’NC’,则返回一个由特征列表feat和字符串’NC’组成的新列表;否则,返回一个由特征列表feat和字符串’MCI’组成的新列表。

1.5 对训练集进行特征提取

train_feat = []
for _ in range(30):
    for path in train_path:
        train_feat.append(extract_feature(path))

这段代码的作用是对训练集进行特征提取。具体来说,它使用了一个for循环,循环了30次。在每次循环中,它又使用了一个for循环,遍历了训练集中的所有文件路径。对于每个文件路径,它调用了名为extract_feature的函数,该函数会读取该文件的图像数据,并从中提取出一些特征。这些特征包括非零像素的数量、零像素的数量、平均值、标准差等等。提取出的特征以及该文件的类别('NC’表示正常,'MCI’表示异常)被添加到一个名为train_feat的列表中。最终,这个列表中会包含30次循环中所有文件的特征和类别。
train_feat = [ ]:创建一个空的列表。
for _ in range(30)::执行一个循环,循环30次。
for path in train_path::执行另一个循环,遍历train_path列表中的每个元素。在每次循环中,将当前元素存储在名为path的变量中。
train_feat.append(extract_feature(path))extract_feature(path) 是一个函数,它的参数是一个文件路径 path,返回值是一个包含了该文件的特征的一维数组。train_feat 是一个列表,用于存储所有训练数据的特征。因此,train_feat.append(extract_feature(path)) 的作用是将 extract_feature(path) 的返回值添加到 train_feat 列表的末尾,也就是将该文件的特征添加到训练数据的特征列表中。
使用 .append() 方法是因为 train_feat 列表是在程序运行时动态生成的,我们无法预先知道列表的长度。因此,我们需要使用 .append() 方法来将新的元素添加到列表的末尾,以便动态地扩展列表的长度。

1.6 对测试集进行特征提取

通过循环,对测试集中的每个文件路径调用extract_feature函数进行特征提取,并将提取得到的特征值添加到test_feat列表中。
函数解释同1.5。

1.7 使用逻辑回归模型进行训练

# 使用训练集的特征作为输入,训练集的类别作为输出,对逻辑回归模型进行训练。
from sklearn.linear_model import LogisticRegression
m = LogisticRegression(max_iter=1000)
m.fit(
    np.array(train_feat)[:, :-1].astype(np.float32),  # 特征
    np.array(train_feat)[:, -1]                       # 类别
)

scikit-learn(简称 sklearn)是一个基于 Python 的机器学习库,提供了各种常用的机器学习算法和工具,包括分类、回归、聚类、降维、模型选择、预处理等。scikit-learn 的设计目标是提供简单、高效、易用的工具,使机器学习更加普及和易于实现。
scikit-learn 的主要特点包括:

  • 简单易用:提供了一致的 API 和文档,易于学习和使用。
  • 高效:基于 NumPy 和 SciPy 实现,具有高效的计算性能。
  • 开源:采用 BSD 开源协议,可以自由使用和修改。
    -丰富的功能:提供了各种常用的机器学习算法和工具,包括分类、回归、聚类、降维、模型选择、预处理等。
  • 可扩展性:支持自定义算法和工具的开发和集成。

scikit-learn 是机器学习领域最流行的 Python 库之一,被广泛应用于数据科学、人工智能、自然语言处理、图像处理等领域。

from sklearn.linear_model import LogisticRegression

这段代码的含义是从 sklearn 库中导入 linear_model 模块中的 LogisticRegression 类。LogisticRegression 是一个用于分类问题的机器学习算法,它可以根据给定的训练数据集来学习如何将输入数据映射到不同的类别中。在这个代码中,LogisticRegression 类被用于训练一个逻辑回归模型,该模型可以用于对测试数据进行分类预测。

m = LogisticRegression(max_iter=1000)

这行代码定义了一个名为m的变量,并将其赋值为一个逻辑回归模型。逻辑回归是一种用于分类问题的机器学习算法。在这里,LogisticRegression是一个来自sklearn.linear_model模块的类,它实现了逻辑回归算法。max_iter参数指定了逻辑回归算法的最大迭代次数,这里设置为1000。

m.fit(
np.array(train_feat)[:, :-1].astype(np.float32),  # 特征
np.array(train_feat)[:, -1]                       # 类别
)

这段代码是使用逻辑回归模型 m 对训练集进行训练。fit 是逻辑回归模型的方法,用于训练模型。具体来说,这段代码的含义是:

  • np.array(train_feat)train_feat 转换为 numpy 数组。
  • [:, :-1] 取 numpy 数组的所有行,但是只取每行的第一个到倒数第二个元素,也就是去掉了每行的最后一个元素,即类别。
  • .astype(np.float32) 将 numpy 数组中的元素类型转换为 32 位浮点数。
  • np.array(train_feat)[:, -1] 取 numpy 数组的所有行,但是只取每行的最后一个元素,即类别。
  • m.fit() 使用逻辑回归模型 m 对训练集进行训练,其中第一个参数是特征,第二个参数是类别。
  • .fit() 是 scikit-learn 库中机器学习模型的一个方法,用于对模型进行训练。
    需要注意的是,不同的机器学习模型的 .fit() 方法的参数可能会有所不同,具体的参数需要根据不同的模型进行设置。

1.8 对测试集进行预测

# 对测试集进行预测并进行转置操作,使得每个样本有30次预测结果。
test_pred = m.predict(np.array(test_feat)[:, :-1].astype(np.float32))
test_pred = test_pred.reshape(30, -1).T
test_pred = m.predict(np.array(test_feat)[:, :-1].astype(np.float32))

当你使用机器学习模型进行分类时,你需要将测试数据集输入到模型中,以便预测它们的类别。这段代码的作用是使用逻辑回归模型 m 对测试集 test_feat 进行预测,并将预测结果存储在 test_pred 中。
具体来说,np.array(test_feat) test_feat 转换为一个 NumPy 数组,
[:, :-1] 选择了数组的所有行和除最后一列之外的所有列,这是因为最后一列是样本的类别标签,而在测试集中,我们不知道这些标签,因此不需要将其输入到模型中。.astype(np.float32) 将数组中的所有元素转换为 32 位浮点数,以便与模型的输入类型匹配。
最后,m.predict() 方法将测试集输入到逻辑回归模型中,并返回一个包含预测类别的 NumPy 数组。这个数组被存储在 test_pred 中,以便后续的投票和结果提交。
.predict() 是 scikit-learn 库中机器学习模型的一个方法,用于对新的数据进行预测。在这个例子中,m 是一个逻辑回归模型的实例,.predict() 方法用于对测试数据进行预测。
.predict() 方法的参数是测试数据,用于对测试数据进行预测。在这个例子中,测试数据是 np.array(test_feat)[:, :-1].astype(np.float32),表示测试数据的特征。.predict() 方法会使用训练好的模型对测试数据进行预测,返回一个包含了所有测试数据的预测结果的一维数组。
需要注意的是,不同的机器学习模型的 .predict() 方法的参数可能会有所不同,具体的参数需要根据不同的模型进行设置。

test_pred = test_pred.reshape(30, -1).T

这行代码将test_pred数组重新排列成一个新的形状。具体来说,它将test_pred数组重新排列为一个30行的矩阵,每行(for _ in range(30)的一次)包含所有样本的预测结果,而每列包含一个样本的30次预测结果。这是通过使用reshape()方法完成的,该方法将数组重新排列为指定的形状。在这种情况下,reshape()方法的参数是(30, -1),其中-1表示将数组的第二个维度自动计算为适合数组的大小。然后,.T将矩阵转置,使得每个样本的30次预测结果在一行中。最后,将结果存储回test_pred变量中。

1.9 对每个样本的30次预测结果进行投票

# 对每个样本的30次预测结果进行投票,选出最多的类别作为该样本的最终预测类别,存储在test_pred_label列表中。
test_pred_label = [Counter(x).most_common(1)[0][0] for x in test_pred]

这行代码的作用是对测试集进行预测,并将预测结果存储在test_pred_label列表中。具体来说,代码中使用了Python内置的Counter类,它可以对一个列表中的元素进行计数,并返回一个字典,其中键为元素,值为元素出现的次数。在这里,我们将test_pred中每个样本的30次预测结果作为一个列表x,然后使用Counter(x)对其进行计数。接着,我们使用most_common(1)方法获取出现次数最多的元素及其出现次数,返回一个列表,其中第一个元素是一个元组,包含出现次数最多的元素及其出现次数。由于我们只需要最多的元素,因此使用[0][0]获取元组中的第一个元素,即出现次数最多的元素。最后,使用列表推导式将每个样本的预测结果存储在test_pred_label列表中。

1.10 生成提交结果的DataFrame

submit = pd.DataFrame(
    {
        'uuid': [int(x.split('\\')[-1][:-4]) for x in test_path],  # 提取测试集文件名中的ID
        'label': test_pred_label                                  # 预测的类别
    }
)

这行代码的作用是生成一个DataFrame,其中包括样本ID和预测类别。DataFrame是pandas库中的一个数据结构,类似于Excel表格,可以方便地进行数据处理和分析。
具体来说,这个DataFrame包括两列,一列是uuid,表示样本的ID,另一列是label,表示样本的预测类别。这两列的数据来源如下:

  • uuid列的数据来源是test_path列表中每个元素的文件名。test_path是一个包含测试集文件路径的列表,每个文件名的格式为"ID.nii",其中ID是样本的唯一标识符。为了提取出ID,代码使用了Python中的字符串操作和列表操作。具体来说,代码使用了split(‘’)将文件路径按照"“分割成一个列表,然后使用[-1]取出列表中的最后一个元素,即文件名。接着使用[:-4]将文件名的后缀”.nii"去掉,最后使用int将ID转换成整数类型。这样就得到了一个包含所有测试集样本ID的列表,作为uuid列的数据。
  • .split(‘\’)是一个字符串方法,用于将字符串按照指定的分隔符进行分割,并返回一个包含分割后的子字符串的列表。在这个代码中,x.split(‘\’)将x字符串按照反斜杠\进行分割,并返回一个包含分割后的子字符串的列表。由于反斜杠\在Python中是一个转义字符,因此需要使用两个反斜杠\来表示一个反斜杠字符。例如,如果x的值为’D:\data\test\1.nii’,那么x.split(‘\’)将返回一个包含三个子字符串的列表[‘D:’, ‘data’, ‘test’, ‘1.nii’]。然后,[-1]用于获取列表中的最后一个元素’1.nii’,[:-4]用于去掉文件名的后缀.nii,最终得到文件名中的ID。
  • label列的数据来源是test_pred_label列表,即每个测试集样本的预测类别。test_pred_label是一个包含所有测试集样本预测类别的列表,其中每个元素是字符串类型的’NC’或’MCI’。这个列表直接作为label列的数据。
    最终,这个DataFrame被赋值给了变量submit,可以方便地进行后续的数据处理和分析。

1.11 按照样本ID对结果进行排序并保存为CSV文件

# 按照ID对结果排序并保存为CSV文件
submit = submit.sort_values(by='uuid')
submit.to_csv('submit1.csv', index=None)

这段代码的作用是将一个名为submit的DataFrame按照其中一列名为uuid的值进行排序,并将排序后的结果保存为一个名为submit1.csv的CSV文件。
具体来说,submit.sort_values(by='uuid')这一行代码使用了DataFrame的sort_values()方法,该方法可以对DataFrame中的数据进行排序。其中,by='uuid'表示按照uuid这一列的值进行排序。排序后的结果被赋值给了submit变量,因此原来的submit变量被替换成了排序后的结果。
接下来的一行代码submit.to_csv(‘submit1.csv’, index=None)使用了DataFrame的to_csv()方法,该方法可以将DataFrame中的数据保存为CSV文件。其中,'submit1.csv’表示保存的文件名为submit1.csv,index=None表示不将DataFrame的行索引保存到CSV文件中。这样,排序后的结果就被保存为了一个名为submit1.csv的CSV文件。

2 一些问题和疑惑

(1)

submit = pd.DataFrame(
{
    'uuid': [int(x.split('\\')[-1][:-4]) for x in test_path],  # 提取测试集文件名中的ID
    'label': test_pred_label                                  # 预测的类别
}
)

为什么DataFrame对象中uuid是乱序的,和原test文件中不一致?
已解决:因为前面使用了np.random.shuffle()函数,它会将一个序列随机打乱,直接修改原序列。在本项目中它打乱了数据集路径的顺序,使得每次运行程序时,训练集和测试集的顺序都是随机的,从而避免了模型过拟合的问题。
这样的话,是如何确定ID就是对应预测的文件的ID?
已解决:因为数据之间是相互关联的。即:test_pred_label - test_pred - test_feat - test_path,而uuid - test_path


  1. 在计算机图像中,通道数指的是每个像素点包含的颜色通道数量。对于彩色图像,通常有三个颜色通道:红色、绿色和蓝色(RGB)。每个像素点的颜色由这三个通道的值组成。
    对于灰度图像,只有一个通道,每个像素点的值表示灰度强度。在神经影像学中,有些图像可能具有多个通道,每个通道代表不同的影像模态或测量。例如,在 NIfTI 文件格式下的医学图像中,第一个通道的含义取决于具体的图像。NIfTI 文件格式是一种常用的医学图像文件格式,它可以存储多个通道的图像数据。通常情况下,第一个通道代表的是图像的强度值,也就是像素的灰度值或者颜色值。对于一些特殊的医学图像,第一个通道可能代表不同的影像模态或测量,例如在脑部 MRI 图像中,第一个通道可能代表 T1 加权影像,或者 T2 加权影像等。在上面的代码中,通过 img.dataobj[:, :, :, 0] 取出了第一个通道的数据。 ↩︎

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
2020年科大讯飞pet图像分析疾病预测比赛吸引了众多来自全国各地的优秀选手参与,其中脱颖而出的三支队伍值得一提。 第一名队伍:团队名称“智拍医生”。他们凭借精湛的图像分析疾病预测技术,以及出色的团队协作能力,在比赛中获得了第一名的成绩。他们利用深度学习框架和大量的训练数据,成功设计出一套高效准确的pet图像分析算法,能够快速识别和定位疾病相关区域。同时,他们还应用多种机器学习算法,通过对大量数据的模式分析和特征提取,开发出了一套可靠的疾病预测模型。 第二名队伍:团队名称“尖叫壁纸”。他们队伍由一群年轻的学生组成,展现了惊人的创造力和实验精神。他们基于深度学习网络构建了一个高效的pet图像分析模型,并成功实现了部异常区域的自动分割和分类。此外,他们还结合了医学图像处理和人工智能算法,设计出一套智能辅助诊断系统,能够帮助医生快速准确地预测和诊断疾病。 第三名队伍:团队名称“智能AI”。他们队伍由一群经验丰富的研究人员组成,他们的团队在前沿的图像分析和医学影像处理领域具有深厚的技术积累。他们通过大量的pet图像数据和医学知识,结合机器学习和深度学习技术,开发了一套快速准确的pet图像分析疾病预测算法。他们的团队成员分工明确,高效协作,充分发挥了各自的专业优势,取得了令人瞩目的成绩。 可见,这三支队伍在科大讯飞pet图像分析疾病预测比赛中表现出色。他们的创新思维、技术实力和团队合作精神为医学影像处理和疾病诊断领域带来了新的突破和进步。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值