001/*
002 * Copyright 2010-2018 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.file;
020
021import java.io.IOException;
022import java.nio.channels.ClosedByInterruptException;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import icy.gui.frame.progress.FileFrame;
031import icy.sequence.DimensionId;
032import icy.sequence.MetaDataUtil;
033import icy.type.DataType;
034import icy.util.StringUtil;
035import icy.util.StringUtil.AlphanumComparator;
036import ome.xml.meta.OMEXMLMetadata;
037import plugins.kernel.importer.LociImporterPlugin;
038
039/**
040 * This class is an utility class aim to help in grouping a list of <i>file path</id> representing image to form a complete and valid Sequence.
041 * 
042 * @author Stephane
043 */
044public class SequenceFileSticher
045{
046    public static class SequenceType
047    {
048        public int sizeX;
049        public int sizeY;
050        public int sizeZ;
051        public int sizeT;
052        public int sizeC;
053        public DataType dataType;
054        public double pixelSizeX;
055        public double pixelSizeY;
056        public double pixelSizeZ;
057        public double timeInterval;
058
059        // internal
060        int hc;
061
062        public SequenceType()
063        {
064            super();
065
066            // undetermined
067            sizeX = 0;
068            sizeY = 0;
069            sizeZ = 0;
070            sizeT = 0;
071            sizeC = 0;
072            dataType = null;
073            pixelSizeX = 0d;
074            pixelSizeY = 0d;
075            pixelSizeZ = 0d;
076            timeInterval = 0d;
077        }
078
079        void computeHashCode()
080        {
081            hc = (sizeX << 0) ^ (sizeY << 4) ^ (sizeZ << 8) ^ (sizeT << 12) ^ (sizeC << 16)
082                    ^ (Float.floatToIntBits((float) pixelSizeX) << 20)
083                    ^ (Float.floatToIntBits((float) pixelSizeY) << 24) ^ (Float.floatToIntBits((float) pixelSizeZ) >> 4)
084                    ^ (Float.floatToIntBits((float) timeInterval) >> 8)
085                    ^ ((dataType != null) ? (dataType.ordinal() >> 12) : 0);
086        }
087
088        @Override
089        public int hashCode()
090        {
091            return hc;
092        }
093
094        @Override
095        public boolean equals(Object obj)
096        {
097            if (obj instanceof SequenceType)
098            {
099                final SequenceType st = (SequenceType) obj;
100
101                return (st.sizeX == sizeX) && (st.sizeY == sizeY) && (st.sizeZ == sizeZ) && (st.sizeT == sizeT)
102                        && (st.sizeC == sizeC) && (st.pixelSizeX == pixelSizeX) && (st.pixelSizeY == pixelSizeY)
103                        && (st.pixelSizeZ == pixelSizeZ) && (st.timeInterval == timeInterval)
104                        && (st.dataType == dataType);
105            }
106
107            return super.equals(obj);
108        }
109    }
110
111    public static class SequenceIdent
112    {
113        /**
114         * base path pattern (identical part of the path in this group)
115         */
116        public final String base;
117        /**
118         * Series index for this group
119         */
120        public final int series;
121        /**
122         * base image type for this group (correspond to the type of each image)
123         */
124        public final SequenceType baseType;
125        /**
126         * Compatible importer capable of loading this image group
127         */
128        public final SequenceFileImporter importer;
129
130        private final int hc;
131
132        public SequenceIdent(String base, int series, SequenceType type, SequenceFileImporter importer)
133        {
134            super();
135
136            this.base = base;
137            this.series = series;
138            this.baseType = type;
139            this.importer = importer;
140
141            hc = base.hashCode() ^ series;
142        }
143
144        public SequenceIdent(String base, int series)
145        {
146            this(base, series, null, null);
147        }
148
149        @Override
150        public int hashCode()
151        {
152            return hc;
153        }
154
155        @Override
156        public boolean equals(Object obj)
157        {
158            if (obj instanceof SequenceIdent)
159            {
160                final SequenceIdent ident = (SequenceIdent) obj;
161
162                boolean result = base.equals(ident.base) && (series == ident.series);
163
164                // test baseType only if defined
165                if (result && (baseType != null) && (ident.baseType != null))
166                    result &= baseType.equals(ident.baseType);
167
168                return result;
169            }
170
171            return super.equals(obj);
172        }
173    }
174
175    public static class SequenceAbsolutePosition implements Comparable<SequenceAbsolutePosition>
176    {
177        // absolute position from metadata
178        public double posX;
179        public double posY;
180        public double posZ;
181        public double posT;
182        public double indX;
183        public double indY;
184        public double indZ;
185        public double indT;
186
187        // internal
188        protected int hc;
189
190        public SequenceAbsolutePosition()
191        {
192            super();
193
194            // undetermined
195            posX = -1d;
196            posY = -1d;
197            posZ = -1d;
198            posT = -1d;
199            indX = -1d;
200            indY = -1d;
201            indZ = -1d;
202            indT = -1d;
203        }
204
205        /**
206         * Set index X from absolute position and sequence properties (pixel size, dimension)
207         */
208        public void setIndexX(SequenceType type)
209        {
210            // -1 mean not set
211            if (posX != -1d)
212            {
213                // get pixel position if possible
214                final double pixPos;
215
216                if (type.pixelSizeX > 0d)
217                    // we use * 2 to avoid index duplication because of rounding, anyway we will fix interval later
218                    pixPos = (2d * posX) / type.pixelSizeX;
219                else
220                    // use absolute position as pixel pos
221                    pixPos = posX;
222
223                // index = (pixel position) / (image size)
224                indX = Math.round(pixPos / type.sizeX);
225            }
226        }
227
228        /**
229         * Set index X from absolute position and sequence properties (pixel size, dimension)
230         */
231        public void setIndexY(SequenceType type)
232        {
233            // -1 mean not set
234            if (posY != -1d)
235            {
236                // get pixel position if possible
237                final double pixPos;
238
239                if (type.pixelSizeY > 0d)
240                    // we use * 2 to avoid index duplication because of rounding, anyway we will fix interval later
241                    pixPos = (2d * posY) / type.pixelSizeY;
242                else
243                    // use absolute position as pixel pos
244                    pixPos = posY;
245
246                // index = (pixel position) / (image size)
247                indY = Math.round(pixPos / type.sizeY);
248            }
249        }
250
251        /**
252         * Set index X from absolute position and sequence properties (pixel size, dimension)
253         */
254        public void setIndexZ(SequenceType type)
255        {
256            // -1 mean not set
257            if (posZ != -1d)
258            {
259                // get pixel position if possible
260                final double pixPos;
261
262                if (type.pixelSizeZ > 0d)
263                    // we use * 2 to avoid index duplication because of rounding, anyway we will fix interval later
264                    pixPos = (2d * posZ) / type.pixelSizeZ;
265                else
266                    // use absolute position as pixel pos
267                    pixPos = posZ;
268
269                // index = (pixel position) / (image size)
270                indZ = Math.round(pixPos / type.sizeZ);
271            }
272        }
273
274        /**
275         * Set index T from absolute time position and sequence properties (pixel size, dimension)
276         */
277        public void setIndexT(SequenceType type)
278        {
279            // -1 mean not set
280            if (posT != -1d)
281            {
282                // get time position in second if possible
283                final double timePos;
284
285                if (type.timeInterval > 0d)
286                    // we use * 2 to avoid index duplication because of rounding, anyway we will fix interval later
287                    timePos = (2d * posT) / type.timeInterval;
288                else
289                    // use absolute position as time pos
290                    timePos = posT;
291
292                // index = (time position) / (image size)
293                indT = Math.round(timePos / type.sizeT);
294            }
295        }
296
297        public void clearIndX()
298        {
299            indX = -1d;
300        }
301
302        public void clearIndY()
303        {
304            indY = -1d;
305        }
306
307        public void clearIndZ()
308        {
309            indZ = -1d;
310        }
311
312        public void clearIndT()
313        {
314            indT = -1d;
315        }
316
317        public DimensionId getDifference(SequenceAbsolutePosition sap)
318        {
319            if (compare(indT, sap.indT) != 0)
320                return DimensionId.T;
321            if (compare(indZ, sap.indZ) != 0)
322                return DimensionId.Z;
323            if (compare(indY, sap.indY) != 0)
324                return DimensionId.Y;
325            if (compare(indX, sap.indX) != 0)
326                return DimensionId.X;
327
328            return null;
329        }
330
331        public void computeHashCode()
332        {
333            hc = Float.floatToIntBits((float) indX) ^ (Float.floatToIntBits((float) indY) << 8)
334                    ^ (Float.floatToIntBits((float) indZ) << 16) ^ (Float.floatToIntBits((float) indT) << 24)
335                    ^ Float.floatToIntBits((float) posX) ^ (Float.floatToIntBits((float) posY) >> 8)
336                    ^ (Float.floatToIntBits((float) posZ) >> 16) ^ (Float.floatToIntBits((float) posT) >> 24);
337        }
338
339        @Override
340        public int hashCode()
341        {
342            return hc;
343        }
344
345        @Override
346        public boolean equals(Object obj)
347        {
348            if (obj instanceof SequenceAbsolutePosition)
349            {
350                final SequenceAbsolutePosition sap = (SequenceAbsolutePosition) obj;
351
352                return (sap.indX == indX) && (sap.indY == indY) && (sap.indZ == indZ) && (sap.indT == indT);
353            }
354
355            return super.equals(obj);
356        }
357
358        @Override
359        public int compareTo(SequenceAbsolutePosition sap)
360        {
361            int result = compare(indT, sap.indT);
362            if (result == 0)
363                result = compare(indZ, sap.indZ);
364            if (result == 0)
365                result = compare(indY, sap.indY);
366            if (result == 0)
367                result = compare(indX, sap.indX);
368
369            return result;
370        }
371
372    }
373
374    public static class SequenceIndexPosition implements Comparable<SequenceIndexPosition>
375    {
376        public int x;
377        public int y;
378        public int z;
379        public int t;
380        public int c;
381
382        public SequenceIndexPosition()
383        {
384            super();
385
386            // undetermined
387            x = -1;
388            y = -1;
389            z = -1;
390            t = -1;
391            c = -1;
392        }
393
394        public DimensionId getDifference(SequenceIndexPosition sip)
395        {
396            if (SequenceFileSticher.compare(t, sip.t) != 0)
397                return DimensionId.T;
398            if (SequenceFileSticher.compare(z, sip.z) != 0)
399                return DimensionId.Z;
400            if (SequenceFileSticher.compare(c, sip.c) != 0)
401                return DimensionId.C;
402            if (SequenceFileSticher.compare(y, sip.y) != 0)
403                return DimensionId.Y;
404            if (SequenceFileSticher.compare(x, sip.x) != 0)
405                return DimensionId.X;
406
407            return null;
408        }
409
410        @Override
411        public int hashCode()
412        {
413            return x ^ (y << 6) ^ (z << 12) ^ (t << 18) ^ (c << 24);
414        }
415
416        @Override
417        public boolean equals(Object obj)
418        {
419            if (obj instanceof SequenceIndexPosition)
420            {
421                final SequenceIndexPosition sip = (SequenceIndexPosition) obj;
422
423                return (sip.x == x) && (sip.y == y) && (sip.z == z) && (sip.t == t) && (sip.c == c);
424            }
425
426            return super.equals(obj);
427        }
428
429        public int compare(SequenceIndexPosition sip)
430        {
431            int result = 0;
432
433            if (result == 0)
434                result = SequenceFileSticher.compare(t, sip.t);
435            if (result == 0)
436                result = SequenceFileSticher.compare(z, sip.z);
437            if (result == 0)
438                result = SequenceFileSticher.compare(c, sip.c);
439            if (result == 0)
440                result = SequenceFileSticher.compare(y, sip.y);
441            if (result == 0)
442                result = SequenceFileSticher.compare(x, sip.x);
443
444            return result;
445        }
446
447        @Override
448        public int compareTo(SequenceIndexPosition sip)
449        {
450            return compare(sip);
451        }
452    }
453
454    public static class SequencePosition implements Comparable<SequencePosition>
455    {
456        // /**
457        // * file path
458        // */
459        // final String path;
460        //
461        // /**
462        // * importer for this path
463        // */
464        // SequenceFileImporter importer;
465        // /**
466        // * metadata for this path
467        // */
468        // OMEXMLMetadata metadata;
469
470        // /**
471        // * Absolute position
472        // */
473        // final SequenceAbsolutePosition absPos;
474
475        /**
476         * index position
477         */
478        final SequenceIndexPosition indPos;
479
480        // /**
481        // * size & type info
482        // */
483        // final SequenceType type;
484
485        /**
486         * helper to find position
487         */
488        FilePosition filePosition;
489
490        // public SequencePosition(String path)
491        // {
492        // super();
493        //
494        // // type = new SequenceType();
495        // filePosition = new FilePosition(path);
496        // indPos = new SequenceIndexPosition();
497        //
498        // // not affected
499        // // absPos = new SequenceAbsolutePosition();
500        // // importer = null;
501        // // metadata = null;
502        // }
503
504        public SequencePosition(FilePosition filePosition)
505        {
506            super();
507
508            // type = new SequenceType();
509            this.filePosition = filePosition;
510            indPos = new SequenceIndexPosition();
511
512            // not affected
513            // absPos = new SequenceAbsolutePosition();
514            // importer = null;
515            // metadata = null;
516        }
517
518        public String getBase()
519        {
520            return filePosition.base;
521        }
522
523        public String getPath()
524        {
525            return filePosition.path;
526        }
527
528        public int getIndexS()
529        {
530            int result = filePosition.getValue(DimensionId.NULL);
531
532            // if not defined then series = 0
533            if (result == -1)
534                result = 0;
535
536            return result;
537        }
538
539        public int getIndexX()
540        {
541            if (indPos.x != -1)
542                return indPos.x;
543
544            // default
545            return 0;
546        }
547
548        public int getIndexY()
549        {
550            if (indPos.y != -1)
551                return indPos.x;
552
553            // default
554            return 0;
555        }
556
557        public int getIndexC()
558        {
559            if (indPos.c != -1)
560                return indPos.c;
561
562            // default
563            return 0;
564        }
565
566        public int getIndexZ()
567        {
568            if (indPos.z != -1)
569                return indPos.z;
570
571            // default
572            return 0;
573        }
574
575        public int getIndexT()
576        {
577            if (indPos.t != -1)
578                return indPos.t;
579
580            // default
581            return 0;
582        }
583
584        // public int getSizeZ()
585        // {
586        // if (type.sizeZ != -1)
587        // return type.sizeZ;
588        //
589        // // default
590        // return 1;
591        // }
592        //
593        // public int getSizeT()
594        // {
595        // if (type.sizeT != -1)
596        // return type.sizeT;
597        //
598        // // default
599        // return 1;
600        // }
601        //
602        // public int getSizeC()
603        // {
604        // if (type.sizeC != -1)
605        // return type.sizeC;
606        //
607        // // default
608        // return 1;
609        // }
610
611        public int compareSeries(SequencePosition sp)
612        {
613            return filePosition.compareSeries(sp.filePosition);
614        }
615
616        public DimensionId getDifference(SequencePosition sp)
617        {
618            // not the same series (always compare first)
619            if (compareSeries(sp) != 0)
620                return DimensionId.NULL;
621
622            // DimensionId result = absPos.getDifference(sp.absPos);
623            // if (result == null)
624            DimensionId result = filePosition.getDifference(sp.filePosition, false);
625            if (result == null)
626                result = indPos.getDifference(indPos);
627
628            return result;
629        }
630
631        @Override
632        public int compareTo(SequencePosition sp)
633        {
634            int result = compareSeries(sp);
635            if (result == 0)
636                // result = absPos.compareTo(sp.absPos);
637                // if (result == 0)
638                result = filePosition.compare(sp.filePosition, false);
639            if (result == 0)
640                result = indPos.compare(sp.indPos);
641
642            return result;
643        }
644
645        @Override
646        public String toString()
647        {
648            return "Path=" + getPath() + " Position=[S:" + getIndexS() + " T:" + getIndexT() + " Z:" + getIndexZ()
649                    + " C:" + getIndexC() + " Y:" + getIndexY() + " X:" + getIndexX() + "]";
650        }
651    }
652
653    /**
654     * Class used to build a FilePosition from an <i>path</i>
655     * 
656     * @author Stephane
657     */
658    public static class FilePosition implements Comparable<FilePosition>
659    {
660        /**
661         * Class representing a position for a specific dimension.
662         * 
663         * @author Stephane
664         */
665        private static class PositionChunk
666        {
667            /** X dimension prefixes */
668            static final String[] prefixesX = {"x", "xpos", "posx", "xposition", "positionx"};
669
670            /** Y dimension prefixes */
671            static final String[] prefixesY = {"y", "ypos", "posy", "yposition", "positiony"};
672
673            /** Depth (Z) dimension prefixes (taken from Bio-Formats for almost) */
674            static final String[] prefixesZ = {"fp", "sec", "z", "zs", "plane", "focal", "focalplane"};
675
676            /** Time (T) dimension prefixes (taken from Bio-Formats for almost) */
677            static final String[] prefixesT = {"t", "tl", "tp", "time", "frame"};
678
679            /** Channel (C) dimension prefixes (taken from Bio-Formats for almost) */
680            static final String[] prefixesC = {"c", "ch", "channel", "b", "band", "w", "wl", "wave", "wavelength"};
681
682            /** Series (S)dimension prefixes (taken from Bio-Formats for almost) */
683            static final String[] prefixesS = {"s", "series", "sp", "f", "field"};
684
685            public DimensionId dim;
686            public int value;
687
688            PositionChunk(String prefix, int value)
689            {
690                super();
691
692                dim = null;
693                if (!StringUtil.isEmpty(prefix))
694                {
695                    final String prefixLC = prefix.toLowerCase();
696
697                    if (dim == null)
698                        dim = getDim(prefixLC, prefixesX, DimensionId.X);
699                    if (dim == null)
700                        dim = getDim(prefixLC, prefixesY, DimensionId.Y);
701                    if (dim == null)
702                        dim = getDim(prefixLC, prefixesZ, DimensionId.Z);
703                    if (dim == null)
704                        dim = getDim(prefixLC, prefixesT, DimensionId.T);
705                    if (dim == null)
706                        dim = getDim(prefixLC, prefixesC, DimensionId.C);
707                    if (dim == null)
708                        dim = getDim(prefixLC, prefixesS, DimensionId.NULL);
709                }
710
711                this.value = value;
712            }
713
714            private static DimensionId getDim(String prefix, String prefixes[], DimensionId d)
715            {
716                for (String p : prefixes)
717                    if (prefix.endsWith(p))
718                        return d;
719
720                return null;
721            }
722        }
723
724        final String path;
725        final String base;
726        final List<PositionChunk> chunks;
727
728        FilePosition(String path)
729        {
730            super();
731
732            this.path = path;
733            this.base = getBase(path);
734
735            chunks = new ArrayList<PositionChunk>();
736
737            build();
738        }
739
740        private void build()
741        {
742            // we need to extract position from filename (not from the complete path)
743            final String name = FileUtil.getFileName(path);
744            final int len = name.length();
745
746            // int value;
747            int index = 0;
748            while (index < len)
749            {
750                // get starting digit char index
751                final int startInd = StringUtil.getNextDigitCharIndex(name, index);
752
753                // we find a digit char ?
754                if (startInd >= 0)
755                {
756                    // get ending digit char index
757                    int endInd = StringUtil.getNextNonDigitCharIndex(name, startInd);
758                    if (endInd < 0)
759                        endInd = len;
760
761                    // add number only if < 100000 (else it can be a date or id...)
762                    // if ((endInd - startInd) < 6)
763                    // {
764                    // get prefix
765                    final String prefix = getPositionPrefix(name, startInd - 1);
766                    // get value
767                    final int value = StringUtil.parseInt(name.substring(startInd, endInd), -1);
768
769                    // add the position info
770                    addChunk(prefix, value);
771                    // }
772
773                    // adjust index
774                    index = endInd;
775                }
776                else
777                    index = len;
778            }
779        }
780
781        private static String getBase(String path)
782        {
783            final String folder = FileUtil.getDirectory(path, true);
784
785            // we extract position from filename (not from the complete path)
786            String result = FileUtil.getFileName(path);
787            int pos = 0;
788
789            while (pos < result.length())
790            {
791                final int st = StringUtil.getNextDigitCharIndex(result, pos);
792
793                if (st != -1)
794                {
795                    // get ending digit char index
796                    int end = StringUtil.getNextNonDigitCharIndex(result, st);
797                    if (end < 0)
798                        end = result.length();
799                    // final int size = end - st;
800
801                    // remove number from name if number size < 6
802                    // if (size < 6)
803                    result = result.substring(0, st) + result.substring(end);
804                    // pass to next
805                    // else
806                    // pos = end;
807                }
808                else
809                    // done
810                    break;
811            }
812
813            return folder + result;
814        }
815
816        private static String getPositionPrefix(String text, int ind)
817        {
818            if ((ind >= 0) && (ind < text.length()))
819            {
820                // we have a letter at this position
821                if (Character.isLetter(text.charAt(ind)))
822                    // get complete prefix
823                    return text.substring(StringUtil.getPreviousNonLetterCharIndex(text, ind) + 1, ind + 1);
824            }
825
826            return "";
827        }
828
829        private void addChunk(String prefix, int value)
830        {
831            final PositionChunk chunk = new PositionChunk(prefix, value);
832            // get the previous chunk for this dimension
833            final PositionChunk previousChunk = getChunk(chunk.dim, false);
834
835            // // already have a chunk for this dimension ? --> remove it (keep last found)
836            // if (previousChunk != null)
837            // removeChunk(previousChunk);
838            // already have a chunk for this dimension --> detach its from current dim (keep last found)
839            if (previousChunk != null)
840                previousChunk.dim = null;
841
842            // add the chunk
843            chunks.add(chunk);
844        }
845
846        private boolean removeChunk(PositionChunk chunk)
847        {
848            return chunks.remove(chunk);
849        }
850
851        boolean removeChunk(DimensionId dim)
852        {
853            return removeChunk(getChunk(dim, true));
854        }
855
856        public int getValue(DimensionId dim)
857        {
858            final PositionChunk chunk = getChunk(dim, true);
859
860            if (chunk != null)
861                return chunk.value;
862
863            // -1 --> dimension not affected
864            return -1;
865        }
866
867        boolean isUnknowDim(DimensionId dim)
868        {
869            return getChunk(dim, false) == null;
870        }
871
872        public PositionChunk getChunk(DimensionId dim, boolean allowUnknown)
873        {
874            if (dim != null)
875            {
876                for (PositionChunk chunk : chunks)
877                    if (chunk.dim == dim)
878                        return chunk;
879
880                if (allowUnknown)
881                    return getChunkFromUnknown(dim);
882            }
883
884            return null;
885        }
886
887        /**
888         * Try to attribute given dimension position from unknown chunk(x).<br>
889         * Work only for Z, T and C dimension (unlikely to have unaffected X and Y dimension)
890         */
891        private PositionChunk getChunkFromUnknown(DimensionId dim)
892        {
893            final boolean hasCChunk = (getChunk(DimensionId.C, false) != null);
894            final boolean hasZChunk = (getChunk(DimensionId.Z, false) != null);
895            final boolean hasTChunk = (getChunk(DimensionId.T, false) != null);
896
897            // priority order for affectation: T, Z, C
898            switch (dim)
899            {
900                case Z:
901                    // shouldn't happen (Z already affected)
902                    if (hasZChunk)
903                        return null;
904
905                    // T chunk present --> Z = unknown[0]
906                    if (hasTChunk)
907                        return getUnknownChunk(0);
908
909                    // T chunk not present --> T = unknown[0]; Z = unknown[1]
910                    return getUnknownChunk(1);
911
912                case T:
913                    // shouldn't happen (T already affected)
914                    if (hasTChunk)
915                        return null;
916
917                    // T = unknown[0]
918                    return getUnknownChunk(0);
919
920                case C:
921                    // shouldn't happen (C already affected)
922                    if (hasCChunk)
923                        return null;
924
925                    if (hasTChunk)
926                    {
927                        // T and Z chunk present --> C = unknown[0]
928                        if (hasZChunk)
929                            return getUnknownChunk(0);
930
931                        // T chunk present --> Z = unknown[0]; C = unknown[1]
932                        return getUnknownChunk(1);
933                    }
934                    // Z chunk present --> T = unknown[0]; C = unknown[1]
935                    else if (hasZChunk)
936                        return getUnknownChunk(1);
937
938                    // no other chunk present --> T = unknown[0]; Z = unknown[1]; C = unknown[2]
939                    return getUnknownChunk(2);
940            }
941
942            return null;
943        }
944
945        private PositionChunk getUnknownChunk(int i)
946        {
947            int ind = 0;
948
949            for (PositionChunk chunk : chunks)
950            {
951                if (chunk.dim == null)
952                {
953                    if (ind == i)
954                        return chunk;
955
956                    ind++;
957                }
958            }
959
960            return null;
961        }
962
963        int getUnknownChunkCount()
964        {
965            int result = 0;
966
967            for (PositionChunk chunk : chunks)
968                if (chunk.dim == null)
969                    result++;
970
971            return result;
972        }
973
974        public int compareSeries(FilePosition ipb)
975        {
976            int result = 0;
977            final String bn1 = base;
978            final String bn2 = ipb.base;
979
980            // can compare on base name ?
981            if (!StringUtil.isEmpty(bn1) && !StringUtil.isEmpty(bn2))
982                result = bn1.compareTo(bn2);
983
984            // compare on series path position
985            if (result == 0)
986                result = SequenceFileSticher.compare(getValue(DimensionId.NULL), ipb.getValue(DimensionId.NULL));
987
988            return result;
989        }
990
991        public int compare(FilePosition ipb, boolean compareSeries)
992        {
993            int result = 0;
994
995            // always compare series first
996            if (compareSeries)
997                result = compareSeries(ipb);
998
999            if (result == 0)
1000                result = SequenceFileSticher.compare(getValue(DimensionId.T), ipb.getValue(DimensionId.T));
1001            if (result == 0)
1002                result = SequenceFileSticher.compare(getValue(DimensionId.Z), ipb.getValue(DimensionId.Z));
1003            if (result == 0)
1004                result = SequenceFileSticher.compare(getValue(DimensionId.C), ipb.getValue(DimensionId.C));
1005            if (result == 0)
1006                result = SequenceFileSticher.compare(getValue(DimensionId.Y), ipb.getValue(DimensionId.Y));
1007            if (result == 0)
1008                result = SequenceFileSticher.compare(getValue(DimensionId.X), ipb.getValue(DimensionId.X));
1009
1010            return result;
1011        }
1012
1013        public DimensionId getDifference(FilePosition ipb, boolean compareSeries)
1014        {
1015            if (compareSeries)
1016            {
1017                // always compare series first
1018                if (compareSeries(ipb) != 0)
1019                    return DimensionId.NULL;
1020            }
1021
1022            if (SequenceFileSticher.compare(getValue(DimensionId.T), ipb.getValue(DimensionId.T)) != 0)
1023                return DimensionId.T;
1024            if (SequenceFileSticher.compare(getValue(DimensionId.Z), ipb.getValue(DimensionId.Z)) != 0)
1025                return DimensionId.Z;
1026            if (SequenceFileSticher.compare(getValue(DimensionId.C), ipb.getValue(DimensionId.C)) != 0)
1027                return DimensionId.C;
1028            if (SequenceFileSticher.compare(getValue(DimensionId.Y), ipb.getValue(DimensionId.Y)) != 0)
1029                return DimensionId.Y;
1030            if (SequenceFileSticher.compare(getValue(DimensionId.X), ipb.getValue(DimensionId.X)) != 0)
1031                return DimensionId.X;
1032
1033            return null;
1034        }
1035
1036        @Override
1037        public int compareTo(FilePosition ipb)
1038        {
1039            return compare(ipb, false);
1040        }
1041
1042        @Override
1043        public String toString()
1044        {
1045            return "FilePosition [S:" + getValue(DimensionId.NULL) + " C:" + getValue(DimensionId.C) + " T:"
1046                    + getValue(DimensionId.T) + " Z:" + getValue(DimensionId.Z) + " Y:" + getValue(DimensionId.Y)
1047                    + " X:" + getValue(DimensionId.X) + "]";
1048        }
1049    }
1050
1051    public static class SequenceFileGroup
1052    {
1053        public final SequenceIdent ident;
1054        public final List<SequencePosition> positions;
1055
1056        // final sequence dimension
1057        public int totalSizeX;
1058        public int totalSizeY;
1059        public int totalSizeZ;
1060        public int totalSizeT;
1061        public int totalSizeC;
1062
1063        /**
1064         * Internal use only, use {@link SequenceFileSticher#groupFiles(SequenceFileImporter, Collection, boolean, FileFrame)} instead.
1065         */
1066        public SequenceFileGroup(SequenceIdent ident)
1067        {
1068            super();
1069
1070            this.ident = ident;
1071            positions = new ArrayList<SequencePosition>();
1072
1073            // we will compute them with buildIndexesFromPositions
1074            totalSizeX = 0;
1075            totalSizeY = 0;
1076            totalSizeZ = 0;
1077            totalSizeT = 0;
1078            totalSizeC = 0;
1079        }
1080
1081        // void cleanFixedAbsPos()
1082        // {
1083        // // nothing to do
1084        // if (positions.isEmpty())
1085        // return;
1086        //
1087        // final SequencePosition fpos = positions.get(0);
1088        //
1089        // // init
1090        // double indX = fpos.absPos.indX;
1091        // double indY = fpos.absPos.indY;
1092        // double indZ = fpos.absPos.indZ;
1093        // double indT = fpos.absPos.indT;
1094        // boolean posXChanged = false;
1095        // boolean posYChanged = false;
1096        // boolean posZChanged = false;
1097        // boolean posTChanged = false;
1098        //
1099        // for (int i = 1; i < positions.size(); i++)
1100        // {
1101        // final SequencePosition pos = positions.get(i);
1102        //
1103        // if (indX != pos.absPos.indX)
1104        // posXChanged = true;
1105        // if (indY != pos.absPos.indY)
1106        // posYChanged = true;
1107        // if (indZ != pos.absPos.indZ)
1108        // posZChanged = true;
1109        // if (indT != pos.absPos.indT)
1110        // posTChanged = true;
1111        // }
1112        //
1113        // for (SequencePosition pos : positions)
1114        // {
1115        // // fixed X position --> useless
1116        // if (!posXChanged)
1117        // pos.absPos.clearIndX();
1118        // // fixed Y position --> useless
1119        // if (!posYChanged)
1120        // pos.absPos.clearIndY();
1121        // // fixed Z position --> useless
1122        // if (!posZChanged)
1123        // pos.absPos.clearIndZ();
1124        // // fixed T position --> useless
1125        // if (!posTChanged)
1126        // pos.absPos.clearIndT();
1127        // }
1128        // }
1129
1130        void checkZTDimIdPos()
1131        {
1132            final boolean zMulti = ident.baseType.sizeZ > 1;
1133            final boolean tMulti = ident.baseType.sizeT > 1;
1134
1135            // determine if we need to swap out Z and T position (if only one of the dimension can move)
1136            if (tMulti ^ zMulti)
1137            {
1138                boolean tSet = false;
1139                boolean tCanChange = true;
1140                boolean zSet = false;
1141                boolean zCanChange = true;
1142
1143                for (SequencePosition pos : positions)
1144                {
1145                    final FilePosition idPos = pos.filePosition;
1146
1147                    if (idPos != null)
1148                    {
1149                        if (idPos.getValue(DimensionId.T) != -1)
1150                            tSet = true;
1151                        if (idPos.getValue(DimensionId.Z) != -1)
1152                            zSet = true;
1153
1154                        if (!idPos.isUnknowDim(DimensionId.T))
1155                            tCanChange = false;
1156                        if (!idPos.isUnknowDim(DimensionId.Z))
1157                            zCanChange = false;
1158                    }
1159                }
1160
1161                // Z and T position are not fixed, and one of the dimension , try to swap if possible
1162                if (tCanChange && zCanChange)
1163                {
1164                    boolean swapZT = false;
1165
1166                    // multi T but single Z
1167                    if (tMulti)
1168                    {
1169                        // T position set but can be swapped with Z
1170                        if (tSet && tCanChange && !zSet)
1171                            swapZT = true;
1172                    }
1173                    else
1174                    // multi Z but single T
1175                    {
1176                        // Z position set but can be swapped with T
1177                        if (zSet && zCanChange && !tSet)
1178                            swapZT = true;
1179                    }
1180
1181                    // swap T and Z dimension
1182                    if (swapZT)
1183                    {
1184                        for (SequencePosition pos : positions)
1185                        {
1186                            final FilePosition idPos = pos.filePosition;
1187
1188                            if (idPos != null)
1189                            {
1190                                final FilePosition.PositionChunk zChunk = idPos.getChunk(DimensionId.Z, true);
1191                                final FilePosition.PositionChunk tChunk = idPos.getChunk(DimensionId.T, true);
1192
1193                                // swap dim
1194                                if (zChunk != null)
1195                                    zChunk.dim = DimensionId.T;
1196                                if (tChunk != null)
1197                                    tChunk.dim = DimensionId.Z;
1198                            }
1199                        }
1200                    }
1201                }
1202            }
1203        }
1204
1205        void buildIndexesAndSizesFromPositions(boolean findPosition)
1206        {
1207            final int size = positions.size();
1208
1209            // nothing to do
1210            if (size <= 0)
1211                return;
1212
1213            final SequenceType baseType = ident.baseType;
1214
1215            // store final sequence dimension
1216            final int sc = baseType.sizeC;
1217            final int st = baseType.sizeT;
1218            final int sz = baseType.sizeZ;
1219            final int sy = baseType.sizeY;
1220            final int sx = baseType.sizeX;
1221
1222            // compact indexes
1223            int t = 0;
1224            int z = 0;
1225            int c = 0;
1226            int y = 0;
1227            int x = 0;
1228            int mt = 0;
1229            int mz = 0;
1230            int mc = 0;
1231            int my = 0;
1232            int mx = 0;
1233
1234            SequencePosition previous = positions.get(0);
1235            SequenceIndexPosition indPos = previous.indPos;
1236
1237            indPos.t = t;
1238            indPos.z = z;
1239            indPos.c = c;
1240            indPos.y = y;
1241            indPos.x = x;
1242
1243            for (int i = 1; i < size; i++)
1244            {
1245                final SequencePosition current = positions.get(i);
1246                DimensionId diff = null;
1247
1248                // if we don't want real position we just use T ordering
1249                if (findPosition)
1250                    diff = previous.getDifference(current);
1251
1252                // default = T dimension
1253                if (diff == null)
1254                    diff = DimensionId.T;
1255
1256                // base path changed
1257                switch (diff)
1258                {
1259                    // // series position change (shouldn't arrive, group are here for that)
1260                    // case NULL:
1261                    // s++;
1262                    // // keep maximum
1263                    // ms = Math.max(ms, s);
1264                    // // reset others indexes
1265                    // t = 0;
1266                    // z = 0;
1267                    // c = 0;
1268                    // y = 0;
1269                    // x = 0;
1270                    // break;
1271
1272                    // T position changed (default case)
1273                    case T:
1274                    default:
1275                        t += st;
1276                        // keep maximum
1277                        mt = Math.max(mt, t);
1278                        // reset others indexes
1279                        z = 0;
1280                        c = 0;
1281                        y = 0;
1282                        x = 0;
1283                        break;
1284
1285                    // Z position changed
1286                    case Z:
1287                        z += sz;
1288                        // keep maximum
1289                        mz = Math.max(mz, z);
1290                        // reset others indexes
1291                        c = 0;
1292                        y = 0;
1293                        x = 0;
1294                        break;
1295
1296                    // C position changed
1297                    case C:
1298                        c += sc;
1299                        // keep maximum
1300                        mc = Math.max(mc, c);
1301                        // reset others indexes
1302                        y = 0;
1303                        x = 0;
1304                        break;
1305
1306                    // Y position changed
1307                    case Y:
1308                        y++;
1309                        // keep maximum
1310                        my = Math.max(my, y);
1311                        // reset others indexes
1312                        x = 0;
1313                        break;
1314
1315                    // X position changed
1316                    case X:
1317                        x++;
1318                        // keep maximum
1319                        mx = Math.max(mx, x);
1320                        break;
1321                }
1322
1323                // update current position
1324                indPos = current.indPos;
1325                indPos.t = t;
1326                indPos.z = z;
1327                indPos.c = c;
1328                indPos.y = y;
1329                indPos.x = x;
1330
1331                // keep trace of last position
1332                previous = current;
1333            }
1334
1335            // we want size for each dimension
1336            mt++;
1337            mz++;
1338            mc++;
1339            my++;
1340            mx++;
1341
1342            // normally we want the equality here
1343            if ((mt * mz * mc * my * mx) != size)
1344            {
1345                // note that this can happen when thread is interrupted so just put a warning here
1346                System.err.println("Warning: SequenceFileSticher - number of image doesn't match: " + size
1347                        + " (expected = " + (mt * mz * mc * my * mx) + ")");
1348            }
1349
1350            // store final sequence dimension
1351            totalSizeC = mc * sc;
1352            totalSizeT = mt * st;
1353            totalSizeZ = mz * sz;
1354            totalSizeY = my * sy;
1355            totalSizeX = mx * sx;
1356        }
1357
1358        /**
1359         * Return all contained path in this group
1360         */
1361        public List<String> getPaths()
1362        {
1363            final List<String> results = new ArrayList<String>();
1364
1365            for (SequencePosition pos : positions)
1366                results.add(pos.getPath());
1367
1368            return results;
1369        }
1370
1371    }
1372
1373    /**
1374     * Same as {@link SequenceFileSticher#groupFiles(SequenceFileImporter, Collection, boolean, FileFrame)} except it does several groups if all image file path
1375     * cannot be grouped to form a single Sequence.<br>
1376     * The grouping is done using the path name information (recognizing and parsing specific patterns in the path) and assume file shares the same properties
1377     * (dimensions).<br>
1378     * The method returns a set of {@link SequenceFileGroup} where each group define a Sequence.<br>
1379     * 
1380     * @param importer
1381     *        {@link SequenceFileImporter} to use to open image.<br>
1382     *        If set to <i>null</i> the method automatically try to find a compatible {@link SequenceFileImporter}.
1383     * @param paths
1384     *        image file paths we want to group
1385     * @param findPosition
1386     *        if true we try to determine the X, Y, Z, T and C image position otherwise a simple ascending T ordering is done
1387     * @param loadingFrame
1388     *        Loading dialog if any to show progress
1389     * @see #groupFiles(SequenceFileImporter, Collection, boolean, FileFrame)
1390     */
1391    public static Collection<SequenceFileGroup> groupAllFiles(SequenceFileImporter importer, Collection<String> paths,
1392            boolean findPosition, FileFrame loadingFrame)
1393    {
1394        final List<String> sortedPaths = Loader.cleanNonImageFile(new ArrayList<String>(paths));
1395
1396        if (sortedPaths.isEmpty())
1397            return new ArrayList<SequenceFileGroup>();
1398
1399        // final List<FilePosition> filePositions = new ArrayList<FilePosition>();
1400
1401        if (loadingFrame != null)
1402            loadingFrame.setAction("Sort paths...");
1403
1404        // sort paths on name using smart sorter
1405        if (sortedPaths.size() > 1)
1406            Collections.sort(sortedPaths, new AlphanumComparator());
1407
1408        // we do a 1st pass to build all FilePosition
1409        if (loadingFrame != null)
1410            loadingFrame.setAction("Extracting positions from paths...");
1411
1412        // group FilePosition by 'base' path
1413        final Map<String, List<FilePosition>> pathPositionsMap = new HashMap<String, List<FilePosition>>();
1414
1415        // build FilePosition
1416        for (String path : sortedPaths)
1417        {
1418            final FilePosition filePosition = new FilePosition(path);
1419            final String base = filePosition.base;
1420
1421            // we want to group by 'base' path
1422            List<FilePosition> positions = pathPositionsMap.get(base);
1423
1424            // list not yet created ?
1425            if (positions == null)
1426            {
1427                // create and add it
1428                positions = new ArrayList<FilePosition>();
1429                pathPositionsMap.put(base, positions);
1430            }
1431
1432            // add it
1433            positions.add(filePosition);
1434            // // add it to global list as well
1435            // filePositions.add(filePosition);
1436        }
1437
1438        final Map<SequenceIdent, SequenceFileGroup> result = new HashMap<SequenceIdent, SequenceFileGroup>();
1439
1440        // clean FilePosition grouped by base path and add them to group
1441        for (List<FilePosition> positions : pathPositionsMap.values())
1442        {
1443            // remove position information which never change
1444            while (cleanPositions(positions, DimensionId.NULL))
1445                ;
1446            while (cleanPositions(positions, DimensionId.T))
1447                ;
1448            while (cleanPositions(positions, DimensionId.Z))
1449                ;
1450            while (cleanPositions(positions, DimensionId.C))
1451                ;
1452            while (cleanPositions(positions, DimensionId.Y))
1453                ;
1454            while (cleanPositions(positions, DimensionId.X))
1455                ;
1456
1457            // add position to group(s)
1458            for (FilePosition pos : positions)
1459                addToGroup(result, new SequencePosition(pos), importer);
1460        }
1461
1462        /*
1463         * if (loadingFrame != null)
1464         * loadingFrame.setAction("Get positions information from metadata...");
1465         * 
1466         * SequenceFileImporter imp = importer;
1467         * int indT = 0;
1468         * 
1469         * for (int i = 0; i < sortedPaths.size(); i++)
1470         * {
1471         * final String path = sortedPaths.get(i);
1472         * final SequencePosition position = new SequencePosition(path);
1473         * final SequenceType type = position.type;
1474         * final SequenceAbsolutePosition absPos = position.absPos;
1475         * final SequenceIndexPosition indPos = position.indPos;
1476         * 
1477         * // try to open the image
1478         * imp = tryOpen(imp, path);
1479         * 
1480         * // correctly opened ?
1481         * if (imp != null)
1482         * {
1483         * try
1484         * {
1485         * // get metadata
1486         * final OMEXMLMetadata meta = imp.getOMEXMLMetaData();
1487         * 
1488         * // set type information
1489         * type.sizeX = MetaDataUtil.getSizeX(meta, 0);
1490         * type.sizeY = MetaDataUtil.getSizeY(meta, 0);
1491         * type.sizeZ = MetaDataUtil.getSizeZ(meta, 0);
1492         * type.sizeT = MetaDataUtil.getSizeT(meta, 0);
1493         * type.sizeC = MetaDataUtil.getSizeC(meta, 0);
1494         * type.dataType = MetaDataUtil.getDataType(meta, 0);
1495         * // use -1 as default value to detect when position is not set
1496         * type.pixelSizeX = MetaDataUtil.getPixelSizeX(meta, 0, 0d);
1497         * type.pixelSizeY = MetaDataUtil.getPixelSizeY(meta, 0, 0d);
1498         * type.pixelSizeZ = MetaDataUtil.getPixelSizeZ(meta, 0, 0d);
1499         * type.timeInterval = MetaDataUtil.getTimeInterval(meta, 0, 0d);
1500         * // can compute hash code
1501         * type.computeHashCode();
1502         * 
1503         * if (findPosition)
1504         * {
1505         * // use -1 as default value to detect when position is not set
1506         * absPos.posX = MathUtil.roundSignificant(MetaDataUtil.getPositionX(meta, 0, 0, 0, 0, -1d),
1507         * 5);
1508         * absPos.posY = MathUtil.roundSignificant(MetaDataUtil.getPositionY(meta, 0, 0, 0, 0, -1d),
1509         * 5);
1510         * absPos.posZ = MathUtil.roundSignificant(MetaDataUtil.getPositionZ(meta, 0, 0, 0, 0, -1d),
1511         * 5);
1512         * absPos.posT = MathUtil.roundSignificant(MetaDataUtil.getPositionT(meta, 0, 0, 0, 0, -1d),
1513         * 5);
1514         * // try to compute index from absolute and pixel size info
1515         * absPos.setIndexX(type);
1516         * absPos.setIndexY(type);
1517         * absPos.setIndexZ(type);
1518         * absPos.setIndexT(type);
1519         * // can compute hash code
1520         * absPos.computeHashCode();
1521         * }
1522         * 
1523         * // store importer & metadata object
1524         * position.importer = imp;
1525         * position.metadata = meta;
1526         * }
1527         * catch (Throwable t)
1528         * {
1529         * // error while retrieve metadata
1530         * t.printStackTrace();
1531         * }
1532         * finally
1533         * {
1534         * try
1535         * {
1536         * // close importer
1537         * imp.close();
1538         * }
1539         * catch (IOException e)
1540         * {
1541         * // just ignore...
1542         * }
1543         * }
1544         * 
1545         * // store filePosition in position object
1546         * if (findPosition)
1547         * position.filePosition = filePositions.get(i);
1548         * else
1549         * {
1550         * indPos.x = 0;
1551         * indPos.y = 0;
1552         * indPos.z = 0;
1553         * // simple T ordering
1554         * indPos.t = indT;
1555         * indPos.c = 0;
1556         * 
1557         * // next T position
1558         * indT += type.sizeT;
1559         * }
1560         * 
1561         * // add to result map (important to have position informations first)
1562         * addToGroup(result, position);
1563         * }
1564         * }
1565         * 
1566         */
1567        if (loadingFrame != null)
1568            loadingFrame.setAction("Cleanup up positions and rebuilding indexes...");
1569
1570        // need to improve position informations
1571        for (SequenceFileGroup group : result.values())
1572        {
1573            // // clean absolute positions
1574            // group.cleanFixedAbsPos();
1575            // check if we can revert Z and T dimension from FilePosition
1576            if (findPosition)
1577            {
1578                group.checkZTDimIdPos();
1579                // sort group positions on cleaned up position (S, T, Z, C, Y, X order)
1580                Collections.sort(group.positions);
1581            }
1582
1583            // build final index position from internal position (absolute or path)
1584            group.buildIndexesAndSizesFromPositions(findPosition);
1585        }
1586
1587        return result.values();
1588    }
1589
1590    /**
1591     * Take a list of image file path as input and try to group them to form a unique Sequence.<br>
1592     * The grouping is done using the path name information (recognizing and parsing specific patterns in the path) and assume file shares the same properties
1593     * (dimensions).<br>
1594     * The method returns the "biggest" group found, use {@link SequenceFileSticher#groupAllFiles(SequenceFileImporter, Collection, boolean, FileFrame)} to
1595     * retrieve all possible groups.<br>
1596     * 
1597     * @param importer
1598     *        {@link SequenceFileImporter} to use to open image.<br>
1599     *        If set to <i>null</i> the method automatically try to find a compatible
1600     *        {@link SequenceFileImporter}
1601     * @param paths
1602     *        image file paths we want to group
1603     * @param findPosition
1604     *        if true we try to determine the X, Y, Z, T and C image position otherwise a simple ascending T ordering is done
1605     * @param loadingFrame
1606     *        Loading dialog if any to show progress
1607     * @see #groupAllFiles(SequenceFileImporter, Collection, boolean, FileFrame)
1608     */
1609    public static SequenceFileGroup groupFiles(SequenceFileImporter importer, Collection<String> paths,
1610            boolean findPosition, FileFrame loadingFrame)
1611    {
1612        SequenceFileGroup result = null;
1613
1614        for (SequenceFileGroup group : groupAllFiles(importer, paths, findPosition, loadingFrame))
1615        {
1616            if (result == null)
1617                result = group;
1618            else if (result.positions.size() < group.positions.size())
1619                result = group;
1620        }
1621
1622        return result;
1623    }
1624
1625    static int compare(double v1, double v2)
1626    {
1627        // can compare ?
1628        if ((v1 != -1d) && (v2 != -1d))
1629        {
1630            if (v1 < v2)
1631                return -1;
1632            else if (v1 > v2)
1633                return 1;
1634        }
1635
1636        return 0;
1637    }
1638
1639    /**
1640     * Returns opened {@link SequenceFileImporter} or <i>null</i> if we can't open the given path
1641     */
1642    @SuppressWarnings("resource")
1643    static SequenceFileImporter tryOpen(SequenceFileImporter importer, String path)
1644    {
1645        final boolean tryAnotherImporter;
1646        SequenceFileImporter imp;
1647
1648        // importer not defined ?
1649        if (importer == null)
1650        {
1651            // try to find a compatible file importer
1652            imp = Loader.getSequenceFileImporter(path, true);
1653            // we don't need to try another importer
1654            tryAnotherImporter = false;
1655        }
1656        else
1657        {
1658            // use given importer
1659            imp = importer;
1660            // we may need to test another importer
1661            tryAnotherImporter = true;
1662        }
1663
1664        // we have an importer ?
1665        if (imp != null)
1666        {
1667            // disable original metadata for LOCI importer
1668            if (imp instanceof LociImporterPlugin)
1669            {
1670                // disable grouping and extra metadata
1671                ((LociImporterPlugin) imp).setGroupFiles(false);
1672                ((LociImporterPlugin) imp).setReadOriginalMetadata(false);
1673            }
1674
1675            try
1676            {
1677                // try to open it (require default metadata otherwise pixel size may miss)
1678                imp.open(path, 0);
1679            }
1680            catch (ClosedByInterruptException e)
1681            {
1682                // interrupted --> just return null
1683                return null;
1684            }
1685            catch (Throwable t)
1686            {
1687                // can't be opened... try with an other importer
1688                if (tryAnotherImporter)
1689                    return tryOpen(null, path);
1690
1691                // can't open importer
1692                return null;
1693            }
1694        }
1695
1696        return imp;
1697    }
1698
1699    // private static void addToGroup(Map<SequenceIdent, SequenceFileGroup> groups, SequencePosition position)
1700    // {
1701    // final SequenceIdent ident = new SequenceIdent(position.getBase(), position.getSeriesFromPath(), position.type,
1702    // position.importer);
1703    // SequenceFileGroup group = groups.get(ident);
1704    //
1705    // // group not yet created ?
1706    // if (group == null)
1707    // {
1708    // // create and add it
1709    // group = new SequenceFileGroup(ident);
1710    // groups.put(ident, group);
1711    // }
1712    //
1713    // // add to the group
1714    // group.positions.add(position);
1715    // }
1716
1717    private static void addToGroup(Map<SequenceIdent, SequenceFileGroup> groups, SequencePosition position,
1718            SequenceFileImporter importer)
1719    {
1720        SequenceFileGroup group = groups.get(new SequenceIdent(position.getBase(), position.getIndexS()));
1721
1722        // no group yet for this base path
1723        if (group == null)
1724        {
1725            // get complete ident for this position
1726            final SequenceIdent ident = getSequenceIdent(importer, position);
1727
1728            // can't add this position...
1729            if (ident == null)
1730                return;
1731
1732            // create and add it
1733            group = new SequenceFileGroup(ident);
1734            groups.put(ident, group);
1735        }
1736
1737        // add to the group
1738        group.positions.add(position);
1739    }
1740
1741    /**
1742     * Build and return sequence ident for specified {@link SequencePosition}
1743     */
1744    private static SequenceIdent getSequenceIdent(SequenceFileImporter importer, SequencePosition position)
1745    {
1746        // try to open the image
1747        final SequenceFileImporter imp = tryOpen(importer, position.getPath());
1748
1749        // can't open it (or interrupted) ? --> return null
1750        if (imp == null)
1751            return null;
1752
1753        try
1754        {
1755            // get metadata
1756            final OMEXMLMetadata meta = imp.getOMEXMLMetaData();
1757            final SequenceType type = new SequenceType();
1758
1759            // set type information
1760            type.sizeX = MetaDataUtil.getSizeX(meta, 0);
1761            type.sizeY = MetaDataUtil.getSizeY(meta, 0);
1762            type.sizeZ = MetaDataUtil.getSizeZ(meta, 0);
1763            type.sizeT = MetaDataUtil.getSizeT(meta, 0);
1764            type.sizeC = MetaDataUtil.getSizeC(meta, 0);
1765            type.dataType = MetaDataUtil.getDataType(meta, 0);
1766            // use -1 as default value to detect when position is not set
1767            type.pixelSizeX = MetaDataUtil.getPixelSizeX(meta, 0, 0d);
1768            type.pixelSizeY = MetaDataUtil.getPixelSizeY(meta, 0, 0d);
1769            type.pixelSizeZ = MetaDataUtil.getPixelSizeZ(meta, 0, 0d);
1770            type.timeInterval = MetaDataUtil.getTimeInterval(meta, 0, 0d);
1771            // can compute hash code
1772            type.computeHashCode();
1773
1774            return new SequenceIdent(position.getBase(), position.getIndexS(), type, imp);
1775        }
1776        catch (Throwable t)
1777        {
1778            // error while retrieve metadata
1779            t.printStackTrace();
1780            return null;
1781        }
1782        finally
1783        {
1784            try
1785            {
1786                // close importer
1787                imp.close();
1788            }
1789            catch (IOException e)
1790            {
1791                // just ignore...
1792            }
1793        }
1794    }
1795
1796    private static boolean cleanPositions(Collection<FilePosition> filePositions, DimensionId dim)
1797    {
1798        // remove fixed dim
1799        int value = -1;
1800        for (FilePosition position : filePositions)
1801        {
1802            final int v = position.getValue(dim);
1803
1804            if (v != -1)
1805            {
1806                if (value == -1)
1807                    value = v;
1808                else if (value != v)
1809                {
1810                    // variable --> stop
1811                    value = -1;
1812                    break;
1813                }
1814            }
1815        }
1816
1817        // fixed dimension ? --> remove it
1818        if (value != -1)
1819        {
1820            for (FilePosition position : filePositions)
1821            {
1822                if (position.getValue(dim) != -1)
1823                    position.removeChunk(dim);
1824            }
1825
1826            return true;
1827        }
1828
1829        return false;
1830    }
1831}