Java를 통해 DICOM 만화화
서버 측 API를 사용하여 DICOM 파일을 Cartoonify하기 위해 자신의 Java 앱을 빌드합니다.
Java을 사용하여 DICOM 파일을 만화화하는 방법
만화 효과에는 고유한 매력이 있어 어린 시절의 향수를 불러일으키는 경우가 많습니다. 거의 모든 그래픽 디자인 기사에는 만화 이미지가 필수 요소로 통합되어 있습니다. 인물 사진 만화화, 조명 미세 조정, 흑백으로 변환, 색상 실험, 다양한 편집 기술 혼합, 정교한 이미지 효과 제작 등은 모두 AdjustBrightness, BinarizeFixed, Filter, RecreColor 및 ApplyMask와 같은 이미지 필터를 통해 달성할 수 있습니다. 이 필터는 로드된 원본 사진에 적용할 수 있습니다. 웹페이지 주제에 관계없이 만화 스타일 이미지는 일러스트레이션 목적으로 적합합니다. 과학 논문이 활기를 띠고 다양한 콘텐츠가 사용자의 관심을 끌게 되어 웹 사이트 트래픽이 증가합니다. DICOM 파일을 만화화하기 위해 다음을 사용하겠습니다. 자바용 Aspose.Imaging 기능이 풍부하고 강력하며 사용하기쉬운 Java 플랫폼용 이미지 조작 및 변환 API인 API입니다. 에서 직접 최신 버전을 다운로드할 수 있습니다.[메이븐(https://repository.aspose.com/webapp/#/artifacts/browse/tree/General/repo/com/aspose/aspose-imaging) 다음 구성을 pom.xml에 추가하여 Maven 기반 프로젝트 내에 설치합니다.
Repository
<repository>
<id>AsposeJavaAPI</id>
<name>Aspose Java API</name>
<url>https://repository.aspose.com/repo/</url>
</repository>
Dependency
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-imaging</artifactId>
<version>version of aspose-imaging API</version>
<classifier>jdk16</classifier>
</dependency>
Java을 통해 DICOM을 만화화하는 단계
당신은 필요합니다 aspose-imaging-version-jdk16.jar 자신의 환경에서 다음 워크플로를 시도합니다.
- Image.Load 메서드로 DICOM 파일 로드
- 이미지를 만화화합니다.
- Aspose.Imaging 형식에서 지원하는 압축 이미지를 디스크에 저장
시스템 요구 사항
Java용 Aspose.Imaging은 모든 주요 운영 체제에서 지원됩니다. 다음 전제 조건이 있는지 확인하십시오.
- JDK 1.6 이상이 설치되어 있습니다.
DICOM 이미지를 만화화 - Java
import com.aspose.imaging.*; | |
import com.aspose.imaging.fileformats.png.PngImage; | |
import com.aspose.imaging.imagefilters.filteroptions.FilterOptionsBase; | |
import com.aspose.imaging.imagefilters.filteroptions.MedianFilterOptions; | |
import com.aspose.imaging.imageoptions.PngOptions; | |
import com.aspose.imaging.masking.ImageMasking; | |
import com.aspose.imaging.masking.options.MaskingOptions; | |
import java.io.File; | |
import java.util.*; | |
import java.util.function.Consumer; | |
import java.util.function.Function; | |
import java.util.stream.Collectors; | |
cartoonify(); | |
public static void cartoonify() | |
{ | |
filterImages(image -> | |
{ | |
try (PngImage processedImage = new PngImage(image)) | |
{ | |
image.resize(image.getWidth() * 2, image.getHeight(), ResizeType.LeftTopToLeftTop); | |
ImageFilterExtensions.cartoonify(processedImage); | |
Graphics gr = new Graphics(image); | |
gr.drawImage(processedImage, processedImage.getWidth(), 0); | |
gr.drawLine(new Pen(Color.getDarkRed(), 3), processedImage.getWidth(), 0, processedImage.getWidth(), image.getHeight()); | |
} | |
}, "cartoonify"); | |
} | |
static String templatesFolder = "D:\\TestData\\"; | |
public static void filterImages(Consumer<RasterImage> doFilter, String filterName) | |
{ | |
List<String> rasterFormats = Arrays.asList("jpg", "png", "bmp", "apng", "dicom", | |
"jp2", "j2k", "tga", "webp", "tif", "gif", "ico"); | |
List<String> vectorFormats = Arrays.asList("svg", "otg", "odg", "eps", "wmf", "emf", "wmz", "emz", "cmx", "cdr"); | |
List<String> allFormats = new LinkedList<>(rasterFormats); | |
allFormats.addAll(vectorFormats); | |
allFormats.forEach( | |
formatExt -> | |
{ | |
String inputFile = templatesFolder + "template." + formatExt; | |
boolean isVectorFormat = vectorFormats.contains(formatExt); | |
//Need to rasterize vector formats before background remove | |
if (isVectorFormat) | |
{ | |
inputFile = rasterizeVectorImage(formatExt, inputFile); | |
} | |
String outputFile = templatesFolder + String.format("%s_%s.png", filterName, formatExt); | |
System.out.println("Processing " + formatExt); | |
try (RasterImage image = (RasterImage) Image.load(inputFile)) | |
{ | |
doFilter.accept(image); | |
//If image is multipage save each page to png to demonstrate results | |
if (image instanceof IMultipageImage && ((IMultipageImage) image).getPageCount() > 1) | |
{ | |
IMultipageImage multiPage = (IMultipageImage) image; | |
final int pageCount = multiPage.getPageCount(); | |
final Image[] pages = multiPage.getPages(); | |
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) | |
{ | |
String fileName = String.format("%s_page%d_%s.png", filterName, pageIndex, formatExt); | |
pages[pageIndex].save(fileName, new PngOptions()); | |
} | |
} | |
else | |
{ | |
image.save(outputFile, new PngOptions()); | |
} | |
} | |
//Remove rasterized vector image | |
if (isVectorFormat) | |
{ | |
new File(inputFile).delete(); | |
} | |
} | |
); | |
} | |
private static String rasterizeVectorImage(String formatExt, String inputFile) | |
{ | |
String outputFile = templatesFolder + "rasterized." + formatExt + ".png"; | |
try (Image image = Image.load(inputFile)) | |
{ | |
image.save(outputFile, new PngOptions()); | |
} | |
return outputFile; | |
} | |
interface IImageDataContext | |
{ | |
void applyData(); | |
} | |
class ImageFilterExtensions | |
{ | |
public static void cartoonify(RasterImage image) | |
{ | |
try (RasterImage outlines = detectOutlines(image, Color.getBlack())) | |
{ | |
image.adjustBrightness(30); | |
image.filter(image.getBounds(), new MedianFilterOptions(7)); | |
Graphics gr = new Graphics(image); | |
gr.drawImage(outlines, Point.getEmpty()); | |
} | |
} | |
public static RasterImage detectOutlines(RasterImage image, Color outlineColor) | |
{ | |
PngImage outlines = new PngImage(image); | |
IImageDataContext ctx = getDataContext(outlines); | |
applyConvolutionFilter(ctx, ConvolutionFilterOptions.getBlur()); | |
applyConvolutionFilter(ctx, ConvolutionFilterOptions.getOutline()); | |
ctx.applyData(); | |
outlines.binarizeFixed((byte)30); | |
ImageMasking.applyMask(outlines, outlines, new MaskingOptions() | |
{{ | |
setBackgroundReplacementColor(Color.getTransparent()); | |
}}); | |
outlines.replaceColor(Color.fromArgb(255, 255, 255), (byte)0, outlineColor); | |
applyConvolutionFilter(outlines, ConvolutionFilterOptions.getBlur()); | |
return outlines; | |
} | |
public static RasterImage applyOperationToRasterImage(RasterImage image, Consumer<RasterImage> operation) | |
{ | |
if (image instanceof IMultipageImage) | |
{ | |
IMultipageImage multipage = (IMultipageImage) image; | |
for (Image page : multipage.getPages()) | |
{ | |
operation.accept((RasterImage) page); | |
} | |
} | |
else | |
{ | |
operation.accept(image); | |
} | |
return image; | |
} | |
public static RasterImage applyFilter(RasterImage image, FilterOptionsBase filterOptions) | |
{ | |
return applyOperationToRasterImage(image, img -> | |
img.filter(img.getBounds(), filterOptions)); | |
} | |
public static RasterImage applyConvolutionFilter(RasterImage image, ConvolutionFilterOptions filterOptions) | |
{ | |
return applyOperationToRasterImage(image, img -> | |
{ | |
ImagePixelsLoader pixelsLoader = new ImagePixelsLoader(img.getBounds()); | |
img.loadPartialArgb32Pixels(img.getBounds(), pixelsLoader); | |
PixelBuffer outBuffer = new PixelBuffer(img.getBounds(), new int[img.getWidth() * img.getHeight()]); | |
ConvolutionFilter.doFiltering(pixelsLoader.getPixelsBuffer(), outBuffer, filterOptions); | |
img.saveArgb32Pixels(outBuffer.getRectangle(), outBuffer.getPixels()); | |
}); | |
} | |
public static IImageDataContext getDataContext(RasterImage image) | |
{ | |
if (image instanceof IMultipageImage) | |
{ | |
return new MultipageDataContext( | |
Arrays.stream(((IMultipageImage)image).getPages()).map(page -> { | |
ImageDataContext buf = new ImageDataContext((RasterImage) page); | |
buf.setBuffer(getImageBuffer((RasterImage)page)); | |
return buf; | |
}).collect(Collectors.toList())); | |
} | |
ImageDataContext buf = new ImageDataContext(image); | |
buf.setBuffer(getImageBuffer(image)); | |
return buf; | |
} | |
static IPixelBuffer getImageBuffer(RasterImage img) | |
{ | |
ImagePixelsLoader pixelsLoader = new ImagePixelsLoader(img.getBounds()); | |
img.loadPartialArgb32Pixels(img.getBounds(), pixelsLoader); | |
return pixelsLoader.getPixelsBuffer(); | |
} | |
public static IImageDataContext applyToDataContext(IImageDataContext dataContext, | |
Function<IPixelBuffer, IPixelBuffer> processor) | |
{ | |
if (dataContext instanceof MultipageDataContext) | |
{ | |
for (ImageDataContext context : (MultipageDataContext) dataContext) | |
{ | |
context.setBuffer(processor.apply(context.getBuffer())); | |
} | |
} | |
if (dataContext instanceof ImageDataContext) | |
{ | |
ImageDataContext ctx = (ImageDataContext)dataContext; | |
ctx.setBuffer(processor.apply(ctx.getBuffer())); | |
} | |
return dataContext; | |
} | |
public static IImageDataContext applyConvolutionFilter(IImageDataContext dataContext, | |
ConvolutionFilterOptions filterOptions) | |
{ | |
return applyToDataContext(dataContext, buffer -> | |
{ | |
PixelBuffer outBuffer = new PixelBuffer(buffer.getRectangle(), new int[buffer.getRectangle().getWidth() * buffer.getRectangle().getHeight()]); | |
ConvolutionFilter.doFiltering(buffer, outBuffer, filterOptions); | |
return outBuffer; | |
}); | |
} | |
} | |
class ImageDataContext implements IImageDataContext | |
{ | |
private final RasterImage image; | |
private IPixelBuffer buffer; | |
public ImageDataContext(RasterImage image) | |
{ | |
this.image = image; | |
} | |
public RasterImage getImage() | |
{ | |
return image; | |
} | |
public IPixelBuffer getBuffer() | |
{ | |
return buffer; | |
} | |
public void setBuffer(IPixelBuffer buffer) | |
{ | |
this.buffer = buffer; | |
} | |
public void applyData() | |
{ | |
this.buffer.saveToImage(this.image); | |
} | |
} | |
class MultipageDataContext extends LinkedList<ImageDataContext> implements IImageDataContext | |
{ | |
public MultipageDataContext(Collection<ImageDataContext> enumerable) | |
{ | |
addAll(enumerable); | |
} | |
public void applyData() | |
{ | |
for (ImageDataContext context : this) | |
{ | |
context.applyData(); | |
} | |
} | |
} | |
class ImagePixelsLoader implements IPartialArgb32PixelLoader | |
{ | |
private final CompositePixelBuffer pixelsBuffer; | |
public ImagePixelsLoader(Rectangle rectangle) | |
{ | |
this.pixelsBuffer = new CompositePixelBuffer(rectangle); | |
} | |
public CompositePixelBuffer getPixelsBuffer() | |
{ | |
return pixelsBuffer; | |
} | |
@Override | |
public void process(Rectangle pixelsRectangle, int[] pixels, Point start, Point end) | |
{ | |
this.pixelsBuffer.addPixels(pixelsRectangle,pixels); | |
} | |
} | |
interface IPixelBuffer | |
{ | |
Rectangle getRectangle(); | |
int get(int x, int y); | |
void set(int x, int y, int value); | |
void saveToImage(RasterImage image); | |
} | |
class PixelBuffer implements IPixelBuffer | |
{ | |
private final Rectangle rectangle; | |
private final int[] pixels; | |
public PixelBuffer(Rectangle rectangle,int[] pixels) | |
{ | |
this.rectangle = rectangle; | |
this.pixels = pixels; | |
} | |
@Override | |
public com.aspose.imaging.Rectangle getRectangle() | |
{ | |
return rectangle; | |
} | |
public int[] getPixels() | |
{ | |
return pixels; | |
} | |
@Override | |
public int get(int x, int y) | |
{ | |
return pixels[getIndex(x,y)]; | |
} | |
@Override | |
public void set(int x, int y, int value) | |
{ | |
pixels[getIndex(x,y)] = value; | |
} | |
public void saveToImage(RasterImage image) | |
{ | |
image.saveArgb32Pixels(this.rectangle, this.pixels); | |
} | |
public boolean contains(int x,int y) | |
{ | |
return this.rectangle.contains(x,y); | |
} | |
private int getIndex(int x,int y) | |
{ | |
x -= this.rectangle.getLeft(); | |
y -= this.rectangle.getTop(); | |
return x + y * this.rectangle.getWidth(); | |
} | |
} | |
class CompositePixelBuffer implements IPixelBuffer | |
{ | |
private final List<PixelBuffer> _buffers = new ArrayList<>(); | |
private final Rectangle rectangle; | |
public CompositePixelBuffer(Rectangle rectangle) | |
{ | |
this.rectangle = rectangle; | |
} | |
@Override | |
public com.aspose.imaging.Rectangle getRectangle() | |
{ | |
return rectangle; | |
} | |
@Override | |
public int get(int x, int y) | |
{ | |
return getBuffer(x,y).get(x, y); | |
} | |
@Override | |
public void set(int x, int y, int value) | |
{ | |
getBuffer(x, y).set(x, y, value); | |
} | |
@Override | |
public void saveToImage(RasterImage image) | |
{ | |
for (PixelBuffer buffer : this._buffers) | |
{ | |
buffer.saveToImage(image); | |
} | |
} | |
public void addPixels(Rectangle rectangle,int[] pixels) | |
{ | |
if(rectangle.intersectsWith(rectangle)) | |
{ | |
this._buffers.add(new PixelBuffer(rectangle,pixels)); | |
} | |
} | |
private PixelBuffer getBuffer(int x,int y) | |
{ | |
return this._buffers.stream().filter(b -> b.contains(x,y)).findFirst().get(); | |
} | |
} | |
class ConvolutionFilter | |
{ | |
public static void doFiltering( | |
IPixelBuffer inputBuffer, | |
IPixelBuffer outputBuffer, | |
ConvolutionFilterOptions options) | |
{ | |
double factor = options.getFactor(); | |
int bias = options.getBias(); | |
double[][] kernel = options.getKernel(); | |
int filterWidth = kernel[0].length; | |
int filterCenter = (filterWidth - 1) / 2; | |
int x, y; | |
int filterX, filterY, filterPx, filterPy, filterYPos, pixel; | |
double r, g, b, kernelValue; | |
int top = inputBuffer.getRectangle().getTop(); | |
int bottom = inputBuffer.getRectangle().getBottom(); | |
int left = inputBuffer.getRectangle().getLeft(); | |
int right = inputBuffer.getRectangle().getRight(); | |
for (y = top; y < bottom; y++) | |
{ | |
for (x = left; x < right; x++) | |
{ | |
r = 0; | |
g = 0; | |
b = 0; | |
for (filterY = -filterCenter; filterY <= filterCenter; filterY++) | |
{ | |
filterYPos = filterY + filterCenter; | |
filterPy = filterY + y; | |
if (filterPy >= top && filterPy < bottom) | |
{ | |
for (filterX = -filterCenter; filterX <= filterCenter; filterX++) | |
{ | |
filterPx = filterX + x; | |
if (filterPx >= left && filterPx < right) | |
{ | |
kernelValue = kernel[filterYPos][filterX + filterCenter]; | |
pixel = inputBuffer.get(filterPx, filterPy); | |
r += ((pixel >> 16) & 0xFF) * kernelValue; | |
g += ((pixel >> 8) & 0xFF) * kernelValue; | |
b += (pixel & 0xFF) * kernelValue; | |
} | |
} | |
} | |
} | |
r = (factor * r) + bias; | |
g = (factor * g) + bias; | |
b = (factor * b) + bias; | |
r = r > 255 ? 255 : (r < 0 ? 0 : r); | |
g = g > 255 ? 255 : (g < 0 ? 0 : g); | |
b = b > 255 ? 255 : (b < 0 ? 0 : b); | |
outputBuffer.set(x, y, (inputBuffer.get(x, y) & 0xFF000000) | ((int)r << 16) | ((int)g << 8) | (int)b); | |
} | |
} | |
} | |
} | |
class ConvolutionFilterOptions | |
{ | |
private double factor = 1.0; | |
public double getFactor() | |
{ | |
return factor; | |
} | |
public void setFactor(double factor) | |
{ | |
this.factor = factor; | |
} | |
private int bias = 0; | |
public int getBias() | |
{ | |
return bias; | |
} | |
public void setBias(int bias) | |
{ | |
this.bias = bias; | |
} | |
private double[][] kernel; | |
public double[][] getKernel() | |
{ | |
return kernel; | |
} | |
public void setKernel(double[][] kernel) | |
{ | |
this.kernel = kernel; | |
} | |
public ConvolutionFilterOptions() | |
{ | |
} | |
public ConvolutionFilterOptions(double[][] kernel) | |
{ | |
this.kernel = kernel; | |
} | |
public static ConvolutionFilterOptions getBlur() | |
{ | |
ConvolutionFilterOptions filterOptions = new ConvolutionFilterOptions(); | |
filterOptions.setKernel(new double[][] { { 1, 2, 1 }, { 2, 4, 2 }, { 1, 2, 1 } }); | |
filterOptions.setFactor(0.25 * 0.25); | |
return filterOptions; | |
} | |
public static ConvolutionFilterOptions getSharpen() | |
{ | |
return new ConvolutionFilterOptions(new double[][] { { 0, -1, 0 }, { -1, 5, -1 }, { 0, -1, 0 } }); | |
} | |
public static ConvolutionFilterOptions getEmboss() | |
{ | |
return new ConvolutionFilterOptions(new double[][] { { -2, -1, 0 }, { -1, 1, 1 }, { 0, 1, 2 } }); | |
} | |
public static ConvolutionFilterOptions getOutline() | |
{ | |
return new ConvolutionFilterOptions(new double[][] { { -1, -1, -1 }, { -1, 8, -1 }, { -1, -1, -1 } }); | |
} | |
public static ConvolutionFilterOptions getBottomSobel() | |
{ | |
return new ConvolutionFilterOptions(new double[][] { { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } }); | |
} | |
public static ConvolutionFilterOptions getTopSobel() | |
{ | |
return new ConvolutionFilterOptions(new double[][] { { 1, 2, 1 }, { 0, 0, 0 }, { -1, -2, -1 } }); | |
} | |
public static ConvolutionFilterOptions getLeftSobel() | |
{ | |
return new ConvolutionFilterOptions(new double[][] { { 1, 0, -1 }, { 2, 0, -2 }, { 1, 0, -1 } }); | |
} | |
public static ConvolutionFilterOptions getRightSobel() | |
{ | |
return new ConvolutionFilterOptions(new double[][] { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } }); | |
} | |
} |
Java API용 Aspose.Imaging 정보
Aspose.Imaging API는 애플리케이션 내에서 이미지(사진)를 생성, 수정, 그리기 또는 변환하는 이미지 처리 솔루션입니다. 다양한 이미지 형식 간의 변환(균일한 다중 페이지 또는 다중 프레임 이미지 처리 포함), 그리기와 같은 수정, 그래픽 프리미티브 작업, 변환(크기 조정, 자르기, 뒤집기 및 회전)을 포함하되 이에 국한되지 않는 플랫폼 간 이미지 처리 , 이진화, 회색조, 조정), 고급 이미지 조작 기능(필터링, 디더링, 마스킹, 기울기 보정) 및 메모리 최적화 전략. 독립 실행형 라이브러리이며 이미지 작업을 위해 소프트웨어에 의존하지 않습니다. 프로젝트 내에서 기본 API를 사용하여 고성능 이미지 변환 기능을 쉽게 추가할 수 있습니다. 이는 100% 비공개 온프레미스 API이며 이미지는 서버에서 처리됩니다.온라인 앱을 통해 DICOM 만화화
라이브 데모 웹사이트 를 방문하여 DICOM 문서를 만화화하세요. 라이브 데모에는 다음과 같은 이점이 있습니다.
DICOM 뭐가 DICOM 파일 형식
DICOM은 Digital Imaging and Communications in Medicine의 약자로 의료 정보학 분야와 관련이 있습니다. DICOM은 파일 형식 정의와 네트워크 통신 프로토콜의 조합입니다. DICOM은 .DCM 확장자를 사용합니다. .DCM은 형식 1.x와 형식 2.x의 두 가지 형식으로 존재합니다. DCM 형식 1.x는 일반 및 확장의 두 가지 버전으로 추가로 제공됩니다. DICOM은 다양한 공급업체의 프린터, 서버, 스캐너 등과 같은 의료 영상 장치의 통합에 사용되며 고유성을 위해 각 환자의 식별 데이터도 포함합니다. DICOM 파일은 DICOM 형식의 이미지 데이터를 수신할 수 있는 경우 두 당사자 간에 공유할 수 있습니다. DICOM의 통신 부분은 응용 계층 프로토콜이며 TCP/IP를 사용하여 엔터티 간에 통신합니다. HTTP 및 HTTPS 프로토콜은 DICOM의 웹 서비스에 사용됩니다. 웹 서비스에서 지원하는 버전은 1.0, 1.1, 2 이상입니다.
더 읽어보기기타 지원되는 Cartoonify 형식
Java을 사용하면 다음을 포함한 다양한 형식을 쉽게 만화화할 수 있습니다.