KFB格式的全视野数字病理切片(Whole slide images, WSIs)是国内一种病理切片扫描仪扫描出来的私有格式,该扫描仪是宁波江丰生物信息技术有限公司的一款产品。
然而,在实际开发中我们是无法直接使用该格式的文件。另外kfb文件大小不一,小的几十MB,大的甚至几个G,这么大的文件打开都是问题。
网上搜索各种资料,各种尝试,甚至AI都找了,AI写的代码是各种问题,不是找不见maven依赖就是代码执行各种报错。真的是各种屡试不爽,前前后后折腾了一个礼拜。最终经过本人多次研究尝试,找到了解决方案(转jpg)。
1. 按照网上说的最多的方法来进行第一步操作(kfb转tif)
相关代码如下:
package com.lonzh.utils;
import lombok.SneakyThrows;
import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.concurrent.TimeUnit;
/**
* Kfb转成其他格式
*
* @author DaiHaijiao
*/
public class Kfb2Other {
public static final String TIF = ".tif";
public static final String SVS = ".svs";
/**
* 转换(性能一般的SSD磁盘 转换200Mb的文件大约需要20s)
*
* @param exePath
* @param kfbPath
* @param extensionName 拓展名
*/
public static void convert(String exePath, String kfbPath, String extensionName) {
new Thread() {
@SneakyThrows
@Override
public void run() {
BigDecimal decimal = new BigDecimal(1024);
//文件大小,单位:MB
BigDecimal fileSize = new BigDecimal(new File(kfbPath).length()).divide(decimal, 2, RoundingMode.HALF_UP).divide(decimal, 2, RoundingMode.HALF_UP);
//计算大约需要多少秒
int seconds = fileSize.divide(new BigDecimal(10), 2, RoundingMode.HALF_UP).intValue();
String tifPath = kfbPath.substring(0, kfbPath.lastIndexOf(".")) + extensionName;
String[] cmd = new String[]{"cmd", "/c", exePath + " \"" + kfbPath + "\" \"" + tifPath + "\" 3"};
// 执行自动备份任务
Process process = Runtime.getRuntime().exec(cmd);
process.waitFor(seconds, TimeUnit.SECONDS);
process.destroy();
}
}.start();
}
public static void main(String[] args) {
String exePtah = "C:\\病理教学软件\\kfb2tif\\KFB转Tif或SVS工具2.0\\x86\\KFbioConverter.exe";
convert(exePtah, "D:\\pathology-teaching\\file\\顶级目录\\骨/BL-01-01_骨骼肌萎缩-202306160841479.kfb", Kfb2Other.TIF);
}
}
代码中有使用到“KFbioConverter.exe”,假如你有C币,请移步传送门进行下载。假如你没有C币,就自行百度吧(就是花点时间的事)。
注意:“KFbioConverter.exe”不要安装,是通过代码调用执行的文件类型转换!!!
上述代码中是kfb转tif,假如你需要转svs,也可转svs,后续的转jpg是基于tif格式进行的转换。
2. tif转jpg
关键代码
package com.lonzh.utils;
import com.sun.media.jai.codec.*;
import javax.media.jai.JAI;
import javax.media.jai.RenderedOp;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.io.*;
public class Tif2Jpg {
/**
* @param fileAbsolutePath
* @param seconds 执行前休眠秒数
* @throws InterruptedException
*/
public static void tif2Jpg(String fileAbsolutePath, int seconds) throws InterruptedException {
Thread.sleep(1000 * seconds);
if (fileAbsolutePath == null || "".equals(fileAbsolutePath.trim())) {
return;
}
if (!new File(fileAbsolutePath).exists()) {
System.out.println("系统找不到指定文件【" + fileAbsolutePath + "】");
return;
}
FileSeekableStream fileSeekStream = null;
try {
fileSeekStream = new FileSeekableStream(fileAbsolutePath);
TIFFEncodeParam tiffEncodeParam = new TIFFEncodeParam();
JPEGEncodeParam jpegEncodeParam = new JPEGEncodeParam();
ImageDecoder dec = ImageCodec.createImageDecoder("tiff", fileSeekStream, null);
int count = dec.getNumPages();
tiffEncodeParam.setCompression(TIFFEncodeParam.COMPRESSION_GROUP4);
tiffEncodeParam.setLittleEndian(false);
System.out.println("该tif文件共有" + count + "页");
String filePathPrefix = fileAbsolutePath.substring(0, fileAbsolutePath.lastIndexOf("."));
// for (int i = 0; i < count; i++) {
//此处不能从0开始,否则报错(只取最前面两张图)
for (int i = 1; i < 3; i++) {
RenderedImage renderedImage = dec.decodeAsRenderedImage(i);
String isMin = i == 1 ? "_min" : "";
File imgFile = new File(filePathPrefix + isMin + ".jpg");
System.out.println("第" + i + "页保存至: " + imgFile.getCanonicalPath());
ParameterBlock pb = new ParameterBlock();
pb.addSource(renderedImage);
pb.add(imgFile.toString());
pb.add("JPEG");
pb.add(jpegEncodeParam);
RenderedOp renderedOp = JAI.create("filestore", pb);
renderedOp.dispose();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileSeekStream != null) {
try {
fileSeekStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void jpg2Tif(String fileAbsolutePath) {
OutputStream outputStream = null;
try {
RenderedOp renderOp = JAI.create("fileload", fileAbsolutePath);
String tifFilePath = fileAbsolutePath.substring(0, fileAbsolutePath.lastIndexOf(".")) + ".tif";
outputStream = new FileOutputStream(tifFilePath);
TIFFEncodeParam tiffParam = new TIFFEncodeParam();
ImageEncoder imageEncoder = ImageCodec.createImageEncoder("TIFF", outputStream, tiffParam);
imageEncoder.encode(renderOp);
System.out.println("jpg2Tif 保存至: " + tifFilePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String args[]) throws InterruptedException {
tif2Jpg("C:\\pathology-teaching\\file\\顶级目录\\心/BL-15-04_肝包虫病-202306131413433.tif", 1);
// jpg2Tif("D:/upload/2022/02/21/222.jpg");
}
}
tif中可能会包含多张图片,我这边所使用的kfb转换成的tif中至少都有5张图片(第一张是病理文件的小标签,后续开始才是正图)。代码对于你们所用的业务可能不够完善,自行完善。上述代码是满足我这边业务的,我代码中就是那个代码。
代码中使用了线程休眠,原因在于tif转jpg时,假如tif转换还没有完成时,此刻你调用了tif转jpg就会失败,所有才使用了休眠。如果你不需要,可以去除线程休眠相关代码。
上述代码需要在pom文件中引入相关依赖
javax.media
jai_codec
1.1.3
javax.media
jai_core
1.1.3
有C币的朋友可直接移步jar文件下载传送门(jar引入方式见:本地Maven仓库导入外部jar)
到此,kfb转jpg已经完成!