001/**
002 * 
003 */
004package icy.roi;
005
006import icy.plugin.PluginDescriptor;
007import icy.plugin.PluginLauncher;
008import icy.plugin.PluginLoader;
009import icy.plugin.interface_.PluginROIDescriptor;
010import icy.roi.ROIEvent.ROIEventType;
011import icy.sequence.Sequence;
012import icy.sequence.SequenceEvent;
013import icy.system.IcyExceptionHandler;
014import icy.util.StringUtil;
015
016import java.util.Collection;
017import java.util.HashMap;
018import java.util.List;
019import java.util.Map;
020
021import plugins.kernel.roi.descriptor.measure.ROIBasicMeasureDescriptorsPlugin;
022
023/**
024 * Abstract class providing the basic methods to retrieve properties and compute a specific
025 * descriptor for a region of interest (ROI)
026 * 
027 * @author Stephane Dallongeville, Alexandre Dufour
028 */
029public abstract class ROIDescriptor
030{
031    /**
032     * Returns all available ROI descriptors (see {@link ROIDescriptor}) and their attached plugin
033     * (see {@link PluginROIDescriptor}).<br/>
034     * This list can be extended by installing new plugin(s) implementing the {@link PluginROIDescriptor} interface.
035     * 
036     * @see ROIDescriptor#compute(ROI, Sequence)
037     * @see PluginROIDescriptor#compute(ROI, Sequence)
038     */
039    public static Map<ROIDescriptor, PluginROIDescriptor> getDescriptors()
040    {
041        final Map<ROIDescriptor, PluginROIDescriptor> result = new HashMap<ROIDescriptor, PluginROIDescriptor>();
042        final List<PluginDescriptor> pluginDescriptors = PluginLoader.getPlugins(PluginROIDescriptor.class);
043
044        for (PluginDescriptor pluginDescriptor : pluginDescriptors)
045        {
046            try
047            {
048                final PluginROIDescriptor plugin = (PluginROIDescriptor) PluginLauncher.create(pluginDescriptor);
049                final List<ROIDescriptor> descriptors = plugin.getDescriptors();
050
051                if (descriptors != null)
052                {
053                    for (ROIDescriptor roiDescriptor : descriptors)
054                        result.put(roiDescriptor, plugin);
055                }
056            }
057            catch (Throwable e)
058            {
059                // show a message in the output console
060                IcyExceptionHandler.showErrorMessage(e, false, true);
061                // and send an error report (silent as we don't want a dialog appearing here)
062                IcyExceptionHandler.report(pluginDescriptor, IcyExceptionHandler.getErrorMessage(e, true));
063            }
064        }
065
066        return result;
067    }
068
069    /**
070     * Returns the descriptor identified by the given id from the given list of {@link ROIDescriptor}.<br>
071     * It can return <code>null</code> if the descriptor is not found in the given list.
072     * 
073     * @param id
074     *        the id of the descriptor ({@link ROIBasicMeasureDescriptorsPlugin#ID_VOLUME} for instance) @see
075     *        #getDescriptors()
076     * @see #computeDescriptor(String, ROI, Sequence)
077     */
078    public static ROIDescriptor getDescriptor(Collection<ROIDescriptor> descriptors, String id)
079    {
080        for (ROIDescriptor roiDescriptor : descriptors)
081            if (StringUtil.equals(roiDescriptor.getId(), id))
082                return roiDescriptor;
083
084        return null;
085    }
086    
087    /**
088     * Returns the descriptor identified by the given id from the given list of {@link ROIDescriptor}.<br>
089     * It can return <code>null</code> if the descriptor is not found in the given list.
090     * 
091     * @param id
092     *        the id of the descriptor ({@link ROIBasicMeasureDescriptorsPlugin#ID_VOLUME} for instance) @see
093     *        #getDescriptors()
094     * @see #computeDescriptor(String, ROI, Sequence)
095     */
096    public static ROIDescriptor getDescriptor(String id)
097    {
098        return getDescriptor(getDescriptors().keySet(), id);
099    }
100
101    /**
102     * Computes the specified descriptor from the input {@link ROIDescriptor} set on given ROI
103     * and returns the result (or <code>null</code> if the descriptor is not found).
104     * 
105     * @param roiDescriptors
106     *        the input {@link ROIDescriptor} set (see {@link #getDescriptors()} method)
107     * @param descriptorId
108     *        the id of the descriptor we want to compute ({@link ROIBasicMeasureDescriptorsPlugin#ID_VOLUME} for
109     *        instance)
110     * @param roi
111     *        the ROI on which the descriptor(s) should be computed
112     * @param sequence
113     *        an optional sequence where the pixel size can be retrieved
114     * @return the computed descriptor or <code>null</code> if the descriptor if not found in the
115     *         specified set
116     * @throws UnsupportedOperationException
117     *         if the type of the given ROI is not supported by this descriptor, or if <code>sequence</code> is
118     *         <code>null</code> while the calculation requires it, or if
119     *         the specified Z, T or C position are not supported by the descriptor
120     */
121    public static Object computeDescriptor(Collection<ROIDescriptor> roiDescriptors, String descriptorId, ROI roi,
122            Sequence sequence)
123    {
124        final ROIDescriptor roiDescriptor = getDescriptor(roiDescriptors, descriptorId);
125
126        if (roiDescriptor != null)
127            return roiDescriptor.compute(roi, sequence);
128
129        return null;
130    }
131
132    /**
133     * Computes the specified descriptor on given ROI and returns the result (or <code>null</code> if the descriptor is
134     * not found).
135     * 
136     * @param descriptorId
137     *        the id of the descriptor we want to compute ({@link ROIBasicMeasureDescriptorsPlugin#ID_VOLUME} for
138     *        instance)
139     * @param roi
140     *        the ROI on which the descriptor(s) should be computed
141     * @param sequence
142     *        an optional sequence where the pixel size can be retrieved
143     * @return the computed descriptor or <code>null</code> if the descriptor if not found in the
144     *         specified set
145     * @throws UnsupportedOperationException
146     *         if the type of the given ROI is not supported by this descriptor, or if <code>sequence</code> is
147     *         <code>null</code> while the calculation requires it, or if
148     *         the specified Z, T or C position are not supported by the descriptor
149     */
150    public static Object computeDescriptor(String descriptorId, ROI roi, Sequence sequence)
151    {
152        return computeDescriptor(getDescriptors().keySet(), descriptorId, roi, sequence);
153    }
154
155    protected final String id;
156    protected final String name;
157    protected final Class<?> type;
158
159    /**
160     * Create a new {@link ROIDescriptor} with given id, name and type
161     */
162    protected ROIDescriptor(String id, String name, Class<?> type)
163    {
164        super();
165
166        this.id = id;
167        this.name = name;
168        this.type = type;
169    }
170
171    /**
172     * Create a new {@link ROIDescriptor} with given name and type
173     */
174    protected ROIDescriptor(String name, Class<?> type)
175    {
176        this(name, name, type);
177    }
178
179    /**
180     * Returns the id of this descriptor.<br/>
181     * By default it uses the descriptor's name but it can be overridden to be different.
182     */
183    public String getId()
184    {
185        return id;
186    }
187
188    /**
189     * Returns the name of this descriptor.<br/>
190     * The name is used as title (column header) in the ROI panel so keep it short and self
191     * explanatory.
192     */
193    public String getName()
194    {
195        return name;
196    };
197
198    /**
199     * Returns a single line description (used as tooltip) for this descriptor
200     */
201    public abstract String getDescription();
202
203    /**
204     * Returns the unit of this descriptor (<code>ex: "px", "mm", "µm2"...</code>).</br>
205     * It can return an empty or <code>null</code> string (default implementation) if there is no
206     * specific unit attached to the descriptor.<br/>
207     * Note that unit is concatenated to the name to build the title (column header) in the ROI
208     * panel.
209     * 
210     * @param sequence
211     *        the sequence on which we want to compute the descriptor (if required) to get access to
212     *        the pixel size informations and return according unit
213     */
214    public String getUnit(Sequence sequence)
215    {
216        return null;
217    }
218
219    /**
220     * Returns the type of result for this descriptor
221     * 
222     * @see #compute(ROI, Sequence)
223     */
224    public Class<?> getType()
225    {
226        return type;
227    };
228
229    /**
230     * Returns <code>true</code> if this descriptor compute its result on {@link Sequence} data and *per channel* (as
231     * pixel intensity information).<br>
232     * By default it returns <code>false</code>, override this method if a descriptor require per channel computation.
233     * 
234     * @see #compute(ROI, Sequence)
235     */
236    public boolean separateChannel()
237    {
238        return false;
239    }
240
241    /**
242     * Returns <code>true</code> if this descriptor need to be recomputed when the specified Sequence change event
243     * happen.<br>
244     * By default it returns <code>false</code>, override this method if a descriptor need a specific implementation.
245     * 
246     * @see #compute(ROI, Sequence)
247     */
248    public boolean needRecompute(SequenceEvent change)
249    {
250        return false;
251    }
252
253    /**
254     * Returns <code>true</code> if this descriptor need to be recomputed when the specified ROI change event happen.<br>
255     * By default it returns <code>true</code> on ROI content change, override this method if a descriptor need a
256     * specific implementation.
257     * 
258     * @see #compute(ROI, Sequence)
259     */
260    public boolean needRecompute(ROIEvent change)
261    {
262        return (change.getType() == ROIEventType.ROI_CHANGED);
263    };
264
265    /**
266     * Computes the descriptor on the specified ROI and return the result.
267     * 
268     * @param roi
269     *        the ROI on which the descriptor(s) should be computed
270     * @param sequence
271     *        an optional sequence where the pixel informations can be retrieved (see {@link #separateChannel()})
272     * @return the result of this descriptor computed from the specified parameters.
273     * @throws UnsupportedOperationException
274     *         if the type of the given ROI is not supported by this descriptor, or if <code>sequence</code> is
275     *         <code>null</code> while the calculation requires it
276     */
277    public abstract Object compute(ROI roi, Sequence sequence) throws UnsupportedOperationException;
278
279    /*
280     * We want a unique id for each {@link ROIDescriptor}
281     */
282    @Override
283    public boolean equals(Object obj)
284    {
285        if (obj instanceof ROIDescriptor)
286            return StringUtil.equals(((ROIDescriptor) obj).getId(), getId());
287
288        return super.equals(obj);
289    }
290
291    /*
292     * We want a unique id for each {@link ROIDescriptor}
293     */
294    @Override
295    public int hashCode()
296    {
297        return getId().hashCode();
298    }
299
300    @Override
301    public String toString()
302    {
303        // default implementation
304        return getName();
305    }
306}