diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 316093ccc974d296bacbb4833b91cb3a30830f21..a1066d505b40b22fd13392a3278068881d86fd80 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,11 +1,11 @@
 test_app:
   script:
-    - . /home/greg/lalsuite-install/etc/lalapps-user-env.sh
-    - /home/greg/anaconda2/bin/python tests.py Writer
-    - /home/greg/anaconda2/bin/python tests.py par
-    - /home/greg/anaconda2/bin/python tests.py BaseSearchClass
-    - /home/greg/anaconda2/bin/python tests.py ComputeFstat
-    - /home/greg/anaconda2/bin/python tests.py SemiCoherentSearch
-    - /home/greg/anaconda2/bin/python tests.py SemiCoherentGlitchSearch
-    - /home/greg/anaconda2/bin/python tests.py MCMCSearch
-    - /home/greg/anaconda2/bin/python tests.py GridSearch
+    - . /home/user1/lalsuite-install/etc/lalapps-user-env.sh
+    - /home/user1/anaconda2/bin/python tests.py Writer
+    - /home/user1/anaconda2/bin/python tests.py par
+    - /home/user1/anaconda2/bin/python tests.py BaseSearchClass
+    - /home/user1/anaconda2/bin/python tests.py ComputeFstat
+    - /home/user1/anaconda2/bin/python tests.py SemiCoherentSearch
+    - /home/user1/anaconda2/bin/python tests.py SemiCoherentGlitchSearch
+    - /home/user1/anaconda2/bin/python tests.py MCMCSearch
+    - /home/user1/anaconda2/bin/python tests.py GridSearch
diff --git a/examples/transient_examples/short_transient_search_gridded.py b/examples/transient_examples/short_transient_search_gridded.py
index 64e642342f40b088a5c68c1d44c7b69e68be20e9..dea9c26745e968bc6af825b61aaeb78b72fef22a 100644
--- a/examples/transient_examples/short_transient_search_gridded.py
+++ b/examples/transient_examples/short_transient_search_gridded.py
@@ -35,8 +35,7 @@ search1 = pyfstat.GridSearch(
     sftfilepattern=os.path.join(datadir,'*simulated_transient_signal*sft'),
     F0s=F0s, F1s=F1s, F2s=F2s, Alphas=Alphas, Deltas=Deltas, tref=tref,
     minStartTime=minStartTime, maxStartTime=maxStartTime,
-    BSGL=False,
-    outputTransientFstatMap=True)
+    BSGL=False)
 search1.run()
 search1.print_max_twoF()
 
@@ -44,7 +43,7 @@ search1.plot_1D(xkey='F0',
                xlabel='freq [Hz]', ylabel='$2\mathcal{F}$')
 
 print('with t0,tau bands:')
-search2 = pyfstat.GridSearch(
+search2 = pyfstat.TransientGridSearch(
     label='tCW', outdir=datadir,
     sftfilepattern=os.path.join(datadir,'*simulated_transient_signal*sft'),
     F0s=F0s, F1s=F1s, F2s=F2s, Alphas=Alphas, Deltas=Deltas, tref=tref,
diff --git a/pyfstat/__init__.py b/pyfstat/__init__.py
index 5c447084af048abdbb789435a6a48e65511cc713..3516c4a8fdffbedb1697a40dbed4ba2f37dd835c 100644
--- a/pyfstat/__init__.py
+++ b/pyfstat/__init__.py
@@ -3,4 +3,4 @@ from __future__ import division as _division
 from .core import BaseSearchClass, ComputeFstat, SemiCoherentSearch, SemiCoherentGlitchSearch
 from .make_sfts import Writer, GlitchWriter, FrequencyModulatedArtifactWriter, FrequencyAmplitudeModulatedArtifactWriter
 from .mcmc_based_searches import MCMCSearch, MCMCGlitchSearch, MCMCSemiCoherentSearch, MCMCFollowUpSearch, MCMCTransientSearch
-from .grid_based_searches import GridSearch, GridUniformPriorSearch, GridGlitchSearch, FrequencySlidingWindow, DMoff_NO_SPIN, SliceGridSearch
+from .grid_based_searches import GridSearch, GridUniformPriorSearch, GridGlitchSearch, FrequencySlidingWindow, DMoff_NO_SPIN, SliceGridSearch, TransientGridSearch
diff --git a/pyfstat/core.py b/pyfstat/core.py
index 1e7452257b53260eff2ac69c34024f73cc3b579e..d0731a87a4032db9b397180ac765b22a0fa0f7b9 100755
--- a/pyfstat/core.py
+++ b/pyfstat/core.py
@@ -331,6 +331,7 @@ class ComputeFstat(BaseSearchClass):
     def __init__(self, tref, sftfilepattern=None, minStartTime=None,
                  maxStartTime=None, binary=False, BSGL=False,
                  transientWindowType=None, t0Band=None, tauBand=None,
+                 dt0=None, dtau=None,
                  detectors=None, minCoverFreq=None, maxCoverFreq=None,
                  injectSources=None, injectSqrtSX=None, assumeSqrtSX=None,
                  SSBprec=None):
@@ -359,6 +360,9 @@ class ComputeFstat(BaseSearchClass):
                    and tau in (2*Tsft,2*Tsft+tauBand).
             if =0, only compute CW Fstat with t0=minStartTime,
                    tau=maxStartTime-minStartTime.
+        dt0, dtau: int
+            grid resolutions in transient start-time and duration,
+            both default to Tsft
         detectors : str
             Two character reference to the data to use, specify None for no
             contraint. If multiple-separate by comma.
@@ -615,27 +619,38 @@ class ComputeFstat(BaseSearchClass):
                     .format(self.transientWindowType,
                             ', '.join(transientWindowTypes)))
 
+            # default spacing
             self.Tsft = int(1.0/SFTCatalog.data[0].header.deltaF)
-            if self.t0Band is None:
+            self.windowRange.dt0 = self.Tsft
+            self.windowRange.dtau = self.Tsft
+
+            # special treatment of window_type = none ==> replace by rectangular window spanning all the data
+            if self.windowRange.type == lalpulsar.TRANSIENT_NONE:
+                self.windowRange.t0 = int(self.minStartTime)
                 self.windowRange.t0Band = 0
-                self.windowRange.dt0 = 1
-            else:
-                if not isinstance(self.t0Band, int):
-                    logging.warn('Casting non-integer t0Band={} to int...'
-                                 .format(self.t0Band))
-                    self.t0Band = int(self.t0Band)
-                self.windowRange.t0Band = self.t0Band
-                self.windowRange.dt0 = self.Tsft
-            if self.tauBand is None:
+                self.windowRange.tau = int(self.maxStartTime-self.minStartTime)
                 self.windowRange.tauBand = 0
-                self.windowRange.dtau = 1
-            else:
-                if not isinstance(self.tauBand, int):
-                    logging.warn('Casting non-integer tauBand={} to int...'
-                                 .format(self.tauBand))
-                    self.tauBand = int(self.tauBand)
-                self.windowRange.tauBand = self.tauBand
-                self.windowRange.dtau = self.Tsft
+            else: # user-set bands and spacings
+                if self.t0Band is None:
+                    self.windowRange.t0Band = 0
+                else:
+                    if not isinstance(self.t0Band, int):
+                        logging.warn('Casting non-integer t0Band={} to int...'
+                                     .format(self.t0Band))
+                        self.t0Band = int(self.t0Band)
+                    self.windowRange.t0Band = self.t0Band
+                    if self.dt0:
+                        self.windowRange.dt0 = self.dt0
+                if self.tauBand is None:
+                    self.windowRange.tauBand = 0
+                else:
+                    if not isinstance(self.tauBand, int):
+                        logging.warn('Casting non-integer tauBand={} to int...'
+                                     .format(self.tauBand))
+                        self.tauBand = int(self.tauBand)
+                    self.windowRange.tauBand = self.tauBand
+                    if self.dtau:
+                        self.windowRange.dtau = self.dtau
 
     def get_fullycoherent_twoF(self, tstart, tend, F0, F1, F2, Alpha, Delta,
                                asini=None, period=None, ecc=None, tp=None,
@@ -891,6 +906,18 @@ class ComputeFstat(BaseSearchClass):
         else:
             return ax
 
+    def write_atoms_to_file(self, fnamebase=''):
+        multiFatoms = getattr(self.FstatResults, 'multiFatoms', None)
+        if multiFatoms and multiFatoms[0]:
+            dopplerName = lalpulsar.PulsarDopplerParams2String ( self.PulsarDopplerParams )
+            #fnameAtoms = os.path.join(self.outdir,'Fstatatoms_%s.dat' % dopplerName)
+            fnameAtoms = fnamebase + '_Fstatatoms_%s.dat' % dopplerName
+            fo = lal.FileOpen(fnameAtoms, 'w')
+            lalpulsar.write_MultiFstatAtoms_to_fp ( fo, multiFatoms[0] )
+            del fo # instead of lal.FileClose() which is not SWIG-exported
+        else:
+            raise RuntimeError('Cannot print atoms vector to file: no FstatResults.multiFatoms, or it is None!')
+
 
 class SemiCoherentSearch(ComputeFstat):
     """ A semi-coherent search """
diff --git a/pyfstat/grid_based_searches.py b/pyfstat/grid_based_searches.py
index de51d0fa03936a72ca2d3fdbe1a852671b49ef0f..2126906e194d9b6784366362cd42d4104510c5cc 100644
--- a/pyfstat/grid_based_searches.py
+++ b/pyfstat/grid_based_searches.py
@@ -36,9 +36,7 @@ class GridSearch(BaseSearchClass):
                  Deltas, tref=None, minStartTime=None, maxStartTime=None,
                  nsegs=1, BSGL=False, minCoverFreq=None, maxCoverFreq=None,
                  detectors=None, SSBprec=None, injectSources=None,
-                 input_arrays=False, assumeSqrtSX=None,
-                 transientWindowType=None, t0Band=None, tauBand=None,
-                 outputTransientFstatMap=False):
+                 input_arrays=False, assumeSqrtSX=None):
         """
         Parameters
         ----------
@@ -55,19 +53,6 @@ class GridSearch(BaseSearchClass):
             GPS seconds of the reference time, start time and end time
         input_arrays: bool
             if true, use the F0s, F1s, etc as is
-        transientWindowType: str
-            If 'rect' or 'exp', compute atoms so that a transient (t0,tau) map
-            can later be computed.  ('none' instead of None explicitly calls
-            the transient-window function, but with the full range, for
-            debugging). Currently only supported for nsegs=1.
-        t0Band, tauBand: int
-            if >0, search t0 in (minStartTime,minStartTime+t0Band)
-                   and tau in (2*Tsft,2*Tsft+tauBand).
-            if =0, only compute CW Fstat with t0=minStartTime,
-                   tau=maxStartTime-minStartTime.
-        outputTransientFstatMap: bool
-            if true, write output files for (t0,tau) Fstat maps
-            (one file for each doppler grid point!)
 
         For all other parameters, see `pyfstat.ComputeFStat` for details
 
@@ -91,8 +76,6 @@ class GridSearch(BaseSearchClass):
                 tref=self.tref, sftfilepattern=self.sftfilepattern,
                 minCoverFreq=self.minCoverFreq, maxCoverFreq=self.maxCoverFreq,
                 detectors=self.detectors,
-                transientWindowType=self.transientWindowType,
-                t0Band=self.t0Band, tauBand=self.tauBand,
                 minStartTime=self.minStartTime, maxStartTime=self.maxStartTime,
                 BSGL=self.BSGL, SSBprec=self.SSBprec,
                 injectSources=self.injectSources,
@@ -185,19 +168,7 @@ class GridSearch(BaseSearchClass):
         for vals in tqdm(iterable,
                          total=getattr(self, 'total_iterations', None)):
             detstat = self.search.get_det_stat(*vals)
-            windowRange = getattr(self.search, 'windowRange', None)
-            FstatMap = getattr(self.search, 'FstatMap', None)
             thisCand = list(vals) + [detstat]
-            if getattr(self, 'transientWindowType', None):
-                if self.outputTransientFstatMap:
-                    tCWfile = os.path.splitext(self.out_file)[0]+'_tCW_%.16f_%.16f_%.16f_%.16g_%.16g.dat' % (vals[2],vals[5],vals[6],vals[3],vals[4]) # freq alpha delta f1dot f2dot
-                    fo = lal.FileOpen(tCWfile, 'w')
-                    lalpulsar.write_transientFstatMap_to_fp ( fo, FstatMap, windowRange, None )
-                    del fo # instead of lal.FileClose() which is not SWIG-exported
-                Fmn = FstatMap.F_mn.data
-                maxidx = np.unravel_index(Fmn.argmax(), Fmn.shape)
-                thisCand += [windowRange.t0+maxidx[0]*windowRange.dt0,
-                             windowRange.tau+maxidx[1]*windowRange.dtau]
             data.append(thisCand)
 
         data = np.array(data, dtype=np.float)
@@ -387,6 +358,117 @@ class GridSearch(BaseSearchClass):
                 self.outdir, self.label, dets, type(self).__name__)
 
 
+class TransientGridSearch(GridSearch):
+    """ Gridded transient-continous search using ComputeFstat """
+
+    @helper_functions.initializer
+    def __init__(self, label, outdir, sftfilepattern, F0s, F1s, F2s, Alphas,
+                 Deltas, tref=None, minStartTime=None, maxStartTime=None,
+                 BSGL=False, minCoverFreq=None, maxCoverFreq=None,
+                 detectors=None, SSBprec=None, injectSources=None,
+                 input_arrays=False, assumeSqrtSX=None,
+                 transientWindowType=None, t0Band=None, tauBand=None,
+                 dt0=None, dtau=None,
+                 outputTransientFstatMap=False,
+                 outputAtoms=False):
+        """
+        Parameters
+        ----------
+        label, outdir: str
+            A label and directory to read/write data from/to
+        sftfilepattern: str
+            Pattern to match SFTs using wildcards (*?) and ranges [0-9];
+            mutiple patterns can be given separated by colons.
+        F0s, F1s, F2s, delta_F0s, delta_F1s, tglitchs, Alphas, Deltas: tuple
+            Length 3 tuple describing the grid for each parameter, e.g
+            [F0min, F0max, dF0], for a fixed value simply give [F0]. Unless
+            input_arrays == True, then these are the values to search at.
+        tref, minStartTime, maxStartTime: int
+            GPS seconds of the reference time, start time and end time
+        input_arrays: bool
+            if true, use the F0s, F1s, etc as is
+        transientWindowType: str
+            If 'rect' or 'exp', compute atoms so that a transient (t0,tau) map
+            can later be computed.  ('none' instead of None explicitly calls
+            the transient-window function, but with the full range, for
+            debugging). Currently only supported for nsegs=1.
+        t0Band, tauBand: int
+            if >0, search t0 in (minStartTime,minStartTime+t0Band)
+                   and tau in (2*Tsft,2*Tsft+tauBand).
+            if =0, only compute CW Fstat with t0=minStartTime,
+                   tau=maxStartTime-minStartTime.
+        dt0, dtau: int
+            grid resolutions in transient start-time and duration,
+            both default to Tsft
+        outputTransientFstatMap: bool
+            if true, write output files for (t0,tau) Fstat maps
+            (one file for each doppler grid point!)
+
+        For all other parameters, see `pyfstat.ComputeFStat` for details
+        """
+
+        self.nsegs = 1
+        if os.path.isdir(outdir) is False:
+            os.mkdir(outdir)
+        self.set_out_file()
+        self.keys = ['_', '_', 'F0', 'F1', 'F2', 'Alpha', 'Delta']
+        self.search_keys = [x+'s' for x in self.keys[2:]]
+        for k in self.search_keys:
+            setattr(self, k, np.atleast_1d(getattr(self, k)))
+
+    def inititate_search_object(self):
+        logging.info('Setting up search object')
+        self.search = ComputeFstat(
+            tref=self.tref, sftfilepattern=self.sftfilepattern,
+            minCoverFreq=self.minCoverFreq, maxCoverFreq=self.maxCoverFreq,
+            detectors=self.detectors,
+            transientWindowType=self.transientWindowType,
+            t0Band=self.t0Band, tauBand=self.tauBand,
+            dt0=self.dt0, dtau=self.dtau,
+            minStartTime=self.minStartTime, maxStartTime=self.maxStartTime,
+            BSGL=self.BSGL, SSBprec=self.SSBprec,
+            injectSources=self.injectSources,
+            assumeSqrtSX=self.assumeSqrtSX)
+        self.search.get_det_stat = self.search.get_fullycoherent_twoF
+
+    def run(self, return_data=False):
+        self.get_input_data_array()
+        old_data = self.check_old_data_is_okay_to_use()
+        if old_data is not False:
+            self.data = old_data
+            return
+
+        if hasattr(self, 'search') is False:
+            self.inititate_search_object()
+
+        data = []
+        for vals in tqdm(self.input_data):
+            detstat = self.search.get_det_stat(*vals)
+            windowRange = getattr(self.search, 'windowRange', None)
+            FstatMap = getattr(self.search, 'FstatMap', None)
+            thisCand = list(vals) + [detstat]
+            if getattr(self, 'transientWindowType', None):
+                if self.outputTransientFstatMap:
+                    tCWfile = os.path.splitext(self.out_file)[0]+'_tCW_%.16f_%.16f_%.16f_%.16g_%.16g.dat' % (vals[2],vals[5],vals[6],vals[3],vals[4]) # freq alpha delta f1dot f2dot
+                    fo = lal.FileOpen(tCWfile, 'w')
+                    lalpulsar.write_transientFstatMap_to_fp ( fo, FstatMap, windowRange, None )
+                    del fo # instead of lal.FileClose() which is not SWIG-exported
+                Fmn = FstatMap.F_mn.data
+                maxidx = np.unravel_index(Fmn.argmax(), Fmn.shape)
+                thisCand += [windowRange.t0+maxidx[0]*windowRange.dt0,
+                             windowRange.tau+maxidx[1]*windowRange.dtau]
+            data.append(thisCand)
+            if self.outputAtoms:
+                self.search.write_atoms_to_file(os.path.splitext(self.out_file)[0])
+
+        data = np.array(data, dtype=np.float)
+        if return_data:
+            return data
+        else:
+            self.save_array_to_disk(data)
+            self.data = data
+
+
 class SliceGridSearch(GridSearch):
     """ Slice gridded search using ComputeFstat """
     @helper_functions.initializer
diff --git a/pyfstat/helper_functions.py b/pyfstat/helper_functions.py
index 5c011fd91fda3a2785ee596d7c5b85a14e00baf0..556503e347080d1ab785de5bd8048be537ed5505 100644
--- a/pyfstat/helper_functions.py
+++ b/pyfstat/helper_functions.py
@@ -11,12 +11,19 @@ import inspect
 import peakutils
 from functools import wraps
 from scipy.stats.distributions import ncx2
+import numpy as np
 import lal
 import lalpulsar
 
-import matplotlib.pyplot as plt
-import numpy as np
-
+# workaround for matplotlib on X-less remote logins
+if 'DISPLAY' in os.environ:
+    import matplotlib.pyplot as plt
+else:
+    logging.info('No $DISPLAY environment variable found, so importing \
+                  matplotlib.pyplot with non-interactive "Agg" backend.')
+    import matplotlib
+    matplotlib.use('Agg')
+    import matplotlib.pyplot as plt
 
 def set_up_optional_tqdm():
     try:
diff --git a/tests.py b/tests.py
index 4b853e4fd0d61247fe68a75c481ffc935a542ba8..bf92c9ffb3aa9b417d2aefce64111128f83f329b 100644
--- a/tests.py
+++ b/tests.py
@@ -4,6 +4,7 @@ import os
 import shutil
 import pyfstat
 import lalpulsar
+import logging
 
 
 class Test(unittest.TestCase):
@@ -12,7 +13,11 @@ class Test(unittest.TestCase):
     @classmethod
     def setUpClass(self):
         if os.path.isdir(self.outdir):
-            shutil.rmtree(self.outdir)
+            try:
+                shutil.rmtree(self.outdir)
+            except OSError:
+                logging.warning(
+                    "{} not removed prior to tests".format(self.outdir))
         h0 = 1
         sqrtSX = 1
         F0 = 30
@@ -38,7 +43,11 @@ class Test(unittest.TestCase):
     @classmethod
     def tearDownClass(self):
         if os.path.isdir(self.outdir):
-            shutil.rmtree(self.outdir)
+            try:
+                shutil.rmtree(self.outdir)
+            except OSError:
+                logging.warning(
+                    "{} not removed prior to tests".format(self.outdir))
 
 
 class Writer(Test):