Skip to content
Snippets Groups Projects
Commit 9e51454f authored by tpylak's avatar tpylak
Browse files

LMS-2051 DynamiX intensity transformation improvements

SVN: 20246
parent 53b778f1
No related branches found
No related tags found
No related merge requests found
/*
* Copyright 2011 ETH Zuerich, CISD
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.systemsx.cisd.openbis.dss.etl.dynamix;
import java.awt.image.BufferedImage;
import ch.systemsx.cisd.base.image.IImageTransformer;
import ch.systemsx.cisd.base.image.IImageTransformerFactory;
/**
* Image transformation factory which reduces the intensity of the 8 bit grayscale images, so that
* only the colors from the given range are used.
* <p>
* Do not change this class, it is stored serialized in the database and the code should be
* considered frozen!
* </p>
*
* @author Tomasz Pylak
*/
public class IntensityRangeReductionFactory implements IImageTransformerFactory
{
private static final long serialVersionUID = 1L;
private final int precomputedColors[];
public IntensityRangeReductionFactory(int min, int max)
{
if (min < 0 || max > 255)
{
throw new IllegalStateException(String.format(
"Invalid range [%d, %d]. Min should be >= 0 and max should be <= 255!", min,
max));
}
double rescalingFactor = (max - min) / 255.0;
this.precomputedColors = new int[255];
for (int i = 0; i < precomputedColors.length; i++)
{
precomputedColors[i] = (int) (rescalingFactor * i + min);
}
}
public IImageTransformer createTransformer()
{
return new IImageTransformer()
{
public BufferedImage transform(BufferedImage image)
{
return reduceIntensityRange(image);
}
};
}
private BufferedImage reduceIntensityRange(BufferedImage image)
{
if (image.getColorModel().getNumComponents() > 1)
{
return image; // not a grayscale
}
if (image.getColorModel().getComponentSize(0) > 8)
{
return image; // more than 8 bits
}
BufferedImage newImage =
new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
for (int x = 0; x < image.getWidth(); x++)
{
for (int y = 0; y < image.getHeight(); y++)
{
int color = image.getRaster().getSample(x, y, 0);
int rescaledColor = precomputedColors[color];
newImage.getRaster().setSample(x, y, 0, rescaledColor);
}
}
return newImage;
}
}
......@@ -38,6 +38,8 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImag
import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageEnrichedDTO;
/**
* Abstract superclass for calculating image transformations for images of each spot.
*
* @author Tomasz Pylak
*/
abstract public class AbstractSpotImagesTransformerProcessingPlugin extends AbstractDatastorePlugin
......@@ -46,6 +48,16 @@ abstract public class AbstractSpotImagesTransformerProcessingPlugin extends Abst
protected abstract IImageTransformerFactoryProvider getTransformationProvider(
List<ImgImageEnrichedDTO> spotImages, IContentRepository contentRepository);
protected static final IImageTransformerFactoryProvider NO_TRANSFORMATION_PROVIDER =
new IImageTransformerFactoryProvider()
{
public IImageTransformerFactory tryGetTransformationFactory(
ImgImageEnrichedDTO image)
{
return null;
}
};
private static final long serialVersionUID = 1L;
private static final String CHANNEL_CODE_PROPERTY = "channel";
......@@ -88,21 +100,24 @@ abstract public class AbstractSpotImagesTransformerProcessingPlugin extends Abst
interface IImageTransformerFactoryProvider
{
IImageTransformerFactory getTransformationFactory(ImgImageEnrichedDTO image);
IImageTransformerFactory tryGetTransformationFactory(ImgImageEnrichedDTO image);
}
private void calculateAndSetImageTransformation(List<ImgImageEnrichedDTO> spotImages,
IContentRepository contentRepository, IImagingTransformerDAO transformerDAO)
{
long start = System.currentTimeMillis();
IImageTransformerFactoryProvider transformerFactoryProvider =
getTransformationProvider(spotImages, contentRepository);
for (ImgImageEnrichedDTO image : spotImages)
{
IImageTransformerFactory transformationFactory =
transformerFactoryProvider.getTransformationFactory(image);
transformerFactoryProvider.tryGetTransformationFactory(image);
transformerDAO.saveTransformerFactoryForImage(image.getAcquiredImageId(),
transformationFactory);
}
operationLog.info(String.format("Processed %d images of the spot in %d msec.",
spotImages.size(), (System.currentTimeMillis() - start)));
}
private IContentRepository createContentRepository(DataSetProcessingContext context,
......
......@@ -16,7 +16,6 @@
package ch.systemsx.cisd.openbis.dss.generic.server.plugins;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Collection;
......@@ -29,26 +28,25 @@ import ch.systemsx.cisd.base.image.IImageTransformerFactory;
import ch.systemsx.cisd.common.io.IContent;
import ch.systemsx.cisd.common.utilities.PropertyUtils;
import ch.systemsx.cisd.openbis.dss.etl.IContentRepository;
import ch.systemsx.cisd.openbis.dss.etl.dynamix.IntensityRangeReductionFactory;
import ch.systemsx.cisd.openbis.dss.generic.shared.utils.ImageUtil;
import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageEnrichedDTO;
import ch.systemsx.sybit.imageviewer.data.ImageViewerParameters;
import ch.systemsx.sybit.imageviewer.data.MinMaxInChannel;
import ch.systemsx.sybit.imageviewer.transformers.IJImageTransformerFactory;
/**
* Rescales colors so that all images of one well have comparable brightness. Used by DynamiX
* project.
* <p>
* Currently openBIS converts 16 bit grayscale images to 8 bit. This class has to revert this
* conversion and apply the correct one (taking all images of a well into account). Some information
* is lost because of this double conversion, but we cannot avoid it before openBIS supports 16bit
* images better.
* conversion and apply the correct one (taking all images of a well into account). We cannot avoid
* double convertions before openBIS supports 16bit images better.
* <p>
* Can process only tiff images which can be read by JAI library.
* Limitations:<br>
* 1) works on the first color component of the images (perfect for e.g. grayscale images).<br>
* 2) Can process only tiff images which can be read by JAI library.
*
* @author Tomasz Pylak
*/
public class WellImageIntensityTransformerProcessingPlugin extends
public class DynamixWellBrightnessEqualizerProcessingPlugin extends
AbstractSpotImagesTransformerProcessingPlugin
{
private static final long serialVersionUID = 1L;
......@@ -58,7 +56,7 @@ public class WellImageIntensityTransformerProcessingPlugin extends
// If the encountered minimum and maximum is equal to the spcified, no rescaling happens.
private final int minRescaledColor, maxRescaledColor;
public WellImageIntensityTransformerProcessingPlugin(Properties properties, File storeRoot)
public DynamixWellBrightnessEqualizerProcessingPlugin(Properties properties, File storeRoot)
{
super(properties, storeRoot);
this.minRescaledColor = PropertyUtils.getInt(properties, "min-color", 1);
......@@ -110,7 +108,7 @@ public class WellImageIntensityTransformerProcessingPlugin extends
{
if (spotImages.size() < 2)
{
return null;
return NO_TRANSFORMATION_PROVIDER;
}
final Map<ImgImageEnrichedDTO, ImagePixelsRange> rangeMap =
new HashMap<ImgImageEnrichedDTO, ImagePixelsRange>();
......@@ -123,33 +121,6 @@ public class WellImageIntensityTransformerProcessingPlugin extends
return createImageTransformerFactoryProvider(rangeMap);
}
private static BufferedImage loadImage(IContentRepository contentRepository,
ImgImageEnrichedDTO image)
{
IContent content = contentRepository.getContent(image.getFilePath());
return ImageUtil.loadJavaAdvancedImagingTiff(content.getInputStream(), image.getPage(),
true);
}
private static IImageTransformerFactoryProvider createImageTransformerFactoryProvider(
final Map<ImgImageEnrichedDTO, ImagePixelsRange> rangeMap)
{
final ImagePixelsRange globalRange = calculateOverlapRange(rangeMap.values());
return new IImageTransformerFactoryProvider()
{
public IImageTransformerFactory getTransformationFactory(ImgImageEnrichedDTO image)
{
ImagePixelsRange imageRange = rangeMap.get(image);
ImagePixelsRange rescaledRange = rescaleRange(imageRange, globalRange);
// debug
System.out.println(image.getFilePath().substring(40) + " global range: "
+ globalRange + ", local range: " + imageRange + ", rescaled range: "
+ rescaledRange);
return tryCreateRescaleTransformationFactory(rescaledRange);
}
};
}
private static ImagePixelsRange calculateOverlapRange(Collection<ImagePixelsRange> ranges)
{
ImagePixelsRange globalRange = null;
......@@ -160,39 +131,15 @@ public class WellImageIntensityTransformerProcessingPlugin extends
return globalRange;
}
private static ImagePixelsRange rescaleRange(ImagePixelsRange imageRange,
ImagePixelsRange globalRange)
{
double globalRangeLength = globalRange.getMax() - globalRange.getMin();
int min = (int) (255 * (imageRange.getMin() - globalRange.getMin()) / globalRangeLength);
int max = (int) (255 * (imageRange.getMax() - globalRange.getMin()) / globalRangeLength);
return new ImagePixelsRange(min, max);
}
private static IImageTransformerFactory tryCreateRescaleTransformationFactory(
ImagePixelsRange range)
{
ImageViewerParameters params = new ImageViewerParameters();
params.setMin(range.getMin());
params.setMax(range.getMax());
params.setLutOperation("");
params.setMinMaxInChannels(new HashMap<Integer, MinMaxInChannel>());
params.setSlice(1);
return new IJImageTransformerFactory(params);
}
private ImagePixelsRange calculatePixelsRange(BufferedImage bufferedImage)
{
int minColor = maxRescaledColor;
int maxColor = minRescaledColor;
boolean isGrayscale = bufferedImage.getSampleModel().getNumBands() == 1;
// first pass - calsulate min and max color
for (int x = 0; x < bufferedImage.getWidth(); x++)
{
for (int y = 0; y < bufferedImage.getHeight(); y++)
{
int dominantColorComponent =
getDominantColorComponent(bufferedImage, isGrayscale, x, y);
int dominantColorComponent = bufferedImage.getRaster().getSample(x, y, 0);
if (dominantColorComponent >= minRescaledColor
&& dominantColorComponent <= maxRescaledColor)
{
......@@ -209,29 +156,47 @@ public class WellImageIntensityTransformerProcessingPlugin extends
return new ImagePixelsRange(minColor, maxColor);
}
private static int getDominantColorComponent(BufferedImage bufferedImage, boolean isGrayscale,
int x, int y)
// --- DynamiX specific part ----------------
private static BufferedImage loadImage(IContentRepository contentRepository,
ImgImageEnrichedDTO image)
{
if (isGrayscale)
{
return getDominantColorComponentGrayscale(bufferedImage, x, y);
} else
{
return getDominantColorComponentRGB(bufferedImage, x, y);
}
IContent content = contentRepository.getContent(image.getFilePath());
return ImageUtil.loadJavaAdvancedImagingTiff(content.getInputStream(), image.getPage(),
true);
}
private static int getDominantColorComponentRGB(BufferedImage bufferedImage, int x, int y)
private static IImageTransformerFactoryProvider createImageTransformerFactoryProvider(
final Map<ImgImageEnrichedDTO, ImagePixelsRange> rangeMap)
{
int rgb = bufferedImage.getRGB(x, y);
Color color = new Color(rgb);
int max = Math.max(color.getRed(), color.getBlue());
return Math.max(max, color.getGreen());
final ImagePixelsRange globalRange = calculateOverlapRange(rangeMap.values());
return new IImageTransformerFactoryProvider()
{
public IImageTransformerFactory tryGetTransformationFactory(
ImgImageEnrichedDTO image)
{
ImagePixelsRange imageRange = rangeMap.get(image);
ImagePixelsRange rescaledRange = rescaleRange(imageRange, globalRange);
operationLog.info(image.getFilePath() + " global: " + globalRange + ", local: "
+ imageRange + ", rescaled: " + rescaledRange);
return new IntensityRangeReductionFactory(rescaledRange.getMin(),
rescaledRange.getMax());
}
};
}
private static int getDominantColorComponentGrayscale(BufferedImage bufferedImage, int x, int y)
/**
* We should have converted 'globalRange' to [0,255] but instead a smaller range 'imageRange'
* has been converted to [0,255]. We have to squeeze 'imageRange' to a smaller range [a,b] (a >=
* 0, b <= 255) into which it would be mapped if globalRange would be used at the beginning.
*/
private static ImagePixelsRange rescaleRange(ImagePixelsRange imageRange,
ImagePixelsRange globalRange)
{
return bufferedImage.getRaster().getSample(x, y, 0);
double globalRangeLength = globalRange.getMax() - globalRange.getMin();
int min = (int) (255 * (imageRange.getMin() - globalRange.getMin()) / globalRangeLength);
int max = (int) (255 * (imageRange.getMax() - globalRange.getMin()) / globalRangeLength);
return new ImagePixelsRange(min, max);
}
}
......@@ -20,11 +20,12 @@ import java.io.File;
import java.util.List;
import java.util.Properties;
import ch.systemsx.cisd.base.image.IImageTransformerFactory;
import ch.systemsx.cisd.openbis.dss.etl.IContentRepository;
import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageEnrichedDTO;
/**
* Removes all image-level transformations.
*
* @author Tomasz Pylak
*/
public class RevertImageLevelTransformationProcessingPlugin extends
......@@ -41,12 +42,6 @@ public class RevertImageLevelTransformationProcessingPlugin extends
protected IImageTransformerFactoryProvider getTransformationProvider(
List<ImgImageEnrichedDTO> spotImages, IContentRepository contentRepository)
{
return new IImageTransformerFactoryProvider()
{
public IImageTransformerFactory getTransformationFactory(ImgImageEnrichedDTO image)
{
return null;
}
};
return NO_TRANSFORMATION_PROVIDER;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment