diff --git a/src/geomtools/__init__.py b/src/geomtools/__init__.py
index 9bfac93a0a75598f5a08fcf32a93216b428ddb01..7dbf4299006baa3ccf2065d0257ab1ab2c2c93b6 100644
--- a/src/geomtools/__init__.py
+++ b/src/geomtools/__init__.py
@@ -1,12 +1,11 @@
-# flake8: noqa E401
-from .detector import (assemble_data, get_pixel_positions,
-                       plot_data_on_detector, plot_detector_layout,
-                       read_crystfel_geom, write_crystfel_geom)
-from .sfx import (avg_pixel_displacement, badpixels_mask, cell_volume, ellipse,
-                  extract_cell, extract_geometry, gauss2d_fit,
-                  get_min_bragg_dist, get_peak_position, get_q_from_xyz,
-                  parse_crystfel_streamfile, parse_xwiz_summary,
-                  ph_en_to_lambda, pixels_to_image, plot_cell_parameters,
-                  plot_center_shift, plot_geoptimiser_errormap, plot_peakogram,
-                  plot_powder, read_crystfel_cell, read_crystfel_streamfile,
-                  refine_geometry, rmsd_per_group, spacing)
+from .detector import (  # noqa E401
+    assemble_data, get_detector_shape, get_pixel_positions, pixels_to_image,
+    plot_data_on_detector, plot_detector_layout, read_crystfel_geom,
+    write_crystfel_geom)
+from .sfx import (  # noqa E401
+    avg_pixel_displacement, badpixels_mask, cell_volume, ellipse, extract_cell,
+    extract_geometry, gauss2d_fit, get_min_bragg_dist, get_peak_position,
+    get_q_from_xyz, parse_crystfel_streamfile, parse_xwiz_summary,
+    ph_en_to_lambda, plot_cell_parameters, plot_center_shift,
+    plot_geoptimiser_errormap, plot_peakogram, plot_powder, read_crystfel_cell,
+    read_crystfel_streamfile, refine_geometry, rmsd_per_group, spacing)
diff --git a/src/geomtools/detector/__init__.py b/src/geomtools/detector/__init__.py
index bfc389204b1835945c60bb4813235f6e4be695bd..e6f2a82aef150f5b363be5a3ffbb45ecbcb04718 100644
--- a/src/geomtools/detector/__init__.py
+++ b/src/geomtools/detector/__init__.py
@@ -1,4 +1,4 @@
-# flake8: noqa E401
-from .crystfel_frm import read_crystfel_geom, write_crystfel_geom
-from .geom import get_pixel_positions, assemble_data
-from .draw import plot_detector_layout, plot_data_on_detector
+from .crystfel_frm import read_crystfel_geom, write_crystfel_geom  # noqa E401
+from .draw import plot_data_on_detector, plot_detector_layout  # noqa E401
+from .geom import (  # noqa E401
+    assemble_data, get_detector_shape, get_pixel_positions, pixels_to_image)
diff --git a/src/geomtools/detector/crystfel_frm.py b/src/geomtools/detector/crystfel_frm.py
index 77d94d6504caf2e3db6a267fef76b2a759fee0c7..008ea5fdcd5ea20945218408838f868cf20e2e67 100644
--- a/src/geomtools/detector/crystfel_frm.py
+++ b/src/geomtools/detector/crystfel_frm.py
@@ -1,9 +1,10 @@
+import h5py
 import numpy as np
 import pandas as pd
-
 from cfelpyutils.geometry import load_crystfel_geometry
 from natsort import natsorted
 
+from .geom import get_detector_shape
 
 HEADER_TEMPLATE = """\
 ; {detector} geometry file written by geomtools
@@ -39,23 +40,32 @@ def _format_vec(vec):
     ])
 
 
-def write_crystfel_geom(file, panels, beam, rigid_groups):
+def write_crystfel_geom(file, panels, beam, rigid_groups, mask=None):
     """Writes geometry in CrystFEL format in file."""
     # header
     clen = panels.clen[0]
+    res = panels.res[0]
     paths = {}
     paths['data'] = (
         panels.data[0] if panels.data[0]
         else "/entry_1/data_1/data"
     )
-    mask = panels['mask'][0]
-    if mask:
-        paths['mask'] = mask
+    if mask is None:
+        if 'mask' in panels.colums:
+            paths['mask'] = panels['mask'][0]
+        else:
+            paths['mask'] = "/entry_1/instrument_1/detector_1/mask"
+        if 'mask_file' in panels.columns:
+            paths['mask_file'] = panels.mask_file[0]
     else:
-        paths['mask'] = "/entry_1/instrument_1/detector_1/mask"
+        paths['mask'] = mask['path']
+        paths['mask_file'] = mask['file']
+        paths['mask_good'] = hex(mask['good'])
+        paths['mask_bad'] = hex(mask['bad'])
+
     path_str = '\n'.join('{} = {} ;'.format(i, j) for i, j in paths.items())
     header = HEADER_TEMPLATE.format(
-        detector="AGIPD1M", resolution=1/200e-6, frame_dim=0,
+        detector="AGIPD1M", resolution=res, frame_dim=0,
         clen=clen, photon_energy=beam["photon_energy"],
         adu_per_ev=1, paths=path_str,
     )
@@ -139,4 +149,26 @@ def read_crystfel_geom(filename, indexes=dict()):
     for column, dimno in indexes.items():
         panels[column] = panels.dim_structure.apply(lambda x: x[dimno])
 
-    return panels, geom.beam
+    if "mask_file" in panels.columns and panels.mask_file[0]:
+        mask_file = panels.mask_file[0]
+    else:
+        mask_file = None
+    if "mask" in panels.columns and panels['mask'][0]:
+        mask_path = panels['mask'][0]
+    else:
+        mask_path = "/entry_1/data_1/mask"
+    mask_goodbits = geom.detector.get('mask_good', 0)
+    mask_badbits = geom.detector.get('mask_bad', 0)
+    if mask_file:
+        with h5py.File(mask_file, "r") as f:
+            mask = f[mask_path][:]
+            mask = mask.astype(int)
+            mask = (
+                (np.bitwise_and(mask, mask_goodbits) != mask_goodbits) |
+                (np.bitwise_and(mask, mask_badbits) != 0)
+            )
+    else:
+        shape = get_detector_shape(panels)
+        mask = np.zeros(shape, bool)
+
+    return panels, geom.beam, mask
diff --git a/src/geomtools/detector/geom.py b/src/geomtools/detector/geom.py
index e55e78a8fb1cb549e756e676ae1fbd64ac2b1bc7..b8b11de1de0060379ef258ab96156acdce73c5a8 100644
--- a/src/geomtools/detector/geom.py
+++ b/src/geomtools/detector/geom.py
@@ -55,3 +55,23 @@ def assemble_data(data, pos):
     img[y - ymin, x - xmin] = data
 
     return img, (-xmin, -ymin)
+
+
+def get_detector_shape(panels):
+    """Returns the shape of detector image array."""
+    ixmax = (
+        panels[['modno', 'orig_max_ss', 'orig_max_fs']].max()
+        .rename({'orig_max_fs': 'fs', 'orig_max_ss': 'ss'})
+    )
+    ixmin = (
+        panels[['modno', 'orig_min_ss', 'orig_min_fs']].min()
+        .rename({'orig_min_fs': 'fs', 'orig_min_ss': 'ss'})
+    )
+    return tuple((ixmax - ixmin + 1).tolist())
+
+
+def pixels_to_image(shape, px, attr='msk'):
+    """Transforms dataset of pixels to the image array."""
+    img = np.zeros(shape, px[attr].dtype)
+    img[px.modno, px.ss, px.fs] = px[attr]
+    return img
diff --git a/src/geomtools/sfx/__init__.py b/src/geomtools/sfx/__init__.py
index a9c1675513a14903ca32bfdd28f7d94ec08207bd..c00fdfe5bade7b708501a9134ca5a289f832ceae 100644
--- a/src/geomtools/sfx/__init__.py
+++ b/src/geomtools/sfx/__init__.py
@@ -1,12 +1,14 @@
-# flake8: noqa E401
-from .crystfelio import (extract_cell, extract_geometry,
-                         parse_crystfel_streamfile, read_crystfel_streamfile)
-from .draw import (plot_cell_parameters, plot_center_shift,
-                   plot_geoptimiser_errormap, plot_peakogram, plot_powder)
-from .lattice import (cell_volume, get_min_bragg_dist, get_q_from_xyz,
-                      ph_en_to_lambda, read_crystfel_cell, spacing)
-from .misc import (avg_pixel_displacement, badpixels_mask, ellipse,
-                   gauss2d_fit, get_detector_shape, get_peak_position,
-                   pixels_to_image, rmsd_per_group)
-from .refine import refine_geometry
-from .xwizio import parse_xwiz_summary
+from .crystfelio import (  # noqa E401
+    extract_cell, extract_geometry, parse_crystfel_streamfile,
+    read_crystfel_streamfile)
+from .draw import (  # noqa E401
+    plot_cell_parameters, plot_center_shift, plot_geoptimiser_errormap,
+    plot_peakogram, plot_powder)
+from .lattice import (  # noqa E401
+    cell_volume, get_min_bragg_dist, get_q_from_xyz, ph_en_to_lambda,
+    read_crystfel_cell, spacing)
+from .misc import (  # noqa E401
+    avg_pixel_displacement, badpixels_mask, ellipse, gauss2d_fit,
+    get_peak_position, rmsd_per_group)
+from .refine import refine_geometry  # noqa E401
+from .xwizio import parse_xwiz_summary  # noqa E401
diff --git a/src/geomtools/sfx/crystfelio.py b/src/geomtools/sfx/crystfelio.py
index d906373ea6bbc7bbc41db4672f845226d1846fd9..0ed80aa9c642f74782a975a05a81ae6f1ee6c0b1 100644
--- a/src/geomtools/sfx/crystfelio.py
+++ b/src/geomtools/sfx/crystfelio.py
@@ -159,7 +159,7 @@ def parse_crystfel_streamfile(stream_filename, panels, connected_groups,
                 ])
                 hkl = np.array([re.h.values, re.k.values, re.l.values])
                 u, v, w = M.T @ hkl
-                s = clen / (1e-9 / lmd + w) / 200e-6
+                s = clen / (1e-9 / lmd + w) * res_avg
                 re['xa'] = u * s
                 re['ya'] = v * s
 
diff --git a/src/geomtools/sfx/misc.py b/src/geomtools/sfx/misc.py
index e3ef0a6054f388d929cec60d9df0c4f1a1cb179d..927d099679b74e2aa12b6f782cfdedd6a8b95214 100644
--- a/src/geomtools/sfx/misc.py
+++ b/src/geomtools/sfx/misc.py
@@ -116,23 +116,3 @@ def badpixels_mask(pe, panels, snr=6, min_peak_count=4):
     px = px.join(rp, on="ri")
     px['msk'] = (px.num_peaks > px.num_peaks_hi)
     return px
-
-
-def get_detector_shape(panels):
-    """Returns the shape of detector image array."""
-    ixmax = (
-        panels[['modno', 'orig_max_ss', 'orig_max_fs']].max()
-        .rename({'orig_max_fs': 'fs', 'orig_max_ss': 'ss'})
-    )
-    ixmin = (
-        panels[['modno', 'orig_min_ss', 'orig_min_fs']].min()
-        .rename({'orig_min_fs': 'fs', 'orig_min_ss': 'ss'})
-    )
-    return tuple((ixmax - ixmin + 1).tolist())
-
-
-def pixels_to_image(shape, px, attr='msk'):
-    """Transforms dataset of pixels to the image array."""
-    img = np.zeros(shape, px[attr].dtype)
-    img[px.modno, px.ss, px.fs] = px[attr]
-    return img
diff --git a/src/geomtools/sfx/optimiser.py b/src/geomtools/sfx/optimiser.py
index 7802380f0cdb87a64f4aef8cb74c3e8e80ed3517..e657f1026248d955c315f7867cdec75de9a074ed 100644
--- a/src/geomtools/sfx/optimiser.py
+++ b/src/geomtools/sfx/optimiser.py
@@ -4,9 +4,11 @@ import tempfile
 
 import h5py
 
-from ..detector import read_crystfel_geom, write_crystfel_geom
+from ..detector import (
+    get_detector_shape, pixels_to_image, read_crystfel_geom,
+    write_crystfel_geom)
 from .crystfelio import extract_geometry, read_crystfel_streamfile
-from .misc import badpixels_mask, get_detector_shape, pixels_to_image
+from .misc import badpixels_mask
 from .refine import refine_geometry
 
 
@@ -32,7 +34,7 @@ def refine():
     temp_geom_file.write(geom_file.read())
     geom_file.seek(0)
 
-    panels, beam = read_crystfel_geom(
+    panels, beam, mask = read_crystfel_geom(
         temp_geom_file.name, indexes={'modno': 1})
 
     temp_geom_file.close()
@@ -41,19 +43,27 @@ def refine():
         stream_filename, panels, args.connected, disp=True)
 
     panels_new, transform, clen = refine_geometry(
-        ma, panels, 1.0, args.min_counts)
+        ma, panels, 1.0, args.min_counts, args.connected)
 
     px = badpixels_mask(pe, panels)
     shape = get_detector_shape(panels)
-    msk = pixels_to_image(shape, px)
+    badpx = pixels_to_image(shape, px)
 
     geom_fn = args.output
-    with open(geom_fn.absolute(), "w") as f:
-        write_crystfel_geom(f, panels_new, beam, ["modules", "quads"])
+
+    mask_path = "/entry_1/data_1/mask"
+    mask_fn = geom_fn.parent / (geom_fn.stem + "_hotpixels.h5")
+    with h5py.File(mask_fn, "w") as f:
+        f[mask_path] = badpx.astype("u2")
 
     mask_fn = geom_fn.parent / (geom_fn.stem + "_mask.h5")
     with h5py.File(mask_fn, "w") as f:
-        f["/entry_1/data_1/mask"] = msk.astype("u2")
+        f[mask_path] = (mask | badpx).astype("u2")
+
+    mask = dict(path=mask_path, file=mask_fn.resolve(), good=0, bad=0xffff)
+
+    with open(geom_fn.absolute(), "w") as f:
+        write_crystfel_geom(f, panels_new, beam, ["modules", "quads"], mask)
 
 
 if __name__ == "__main__":
diff --git a/src/geomtools/sfx/report.ipynb b/src/geomtools/sfx/report.ipynb
index a266245165955dba25b1c2c9f13289fbac497f88..527d86eae5fbcd3c082adeeef70180337cd86d35 100644
--- a/src/geomtools/sfx/report.ipynb
+++ b/src/geomtools/sfx/report.ipynb
@@ -14,7 +14,7 @@
     "xwiz_dir = \"\"\n",
     "stream_file = \"\"\n",
     "summary_file = \"\"\n",
-    "prefix=prefix = \"\"\n",
+    "prefix = \"\"\n",
     "output_dir = \"\"\n",
     "geometry_file = \"\"\n",
     "connected_groups = \"\""
@@ -37,7 +37,8 @@
     "    read_crystfel_geom, plot_center_shift, plot_cell_parameters,\n",
     "    plot_peakogram, plot_powder, get_peak_position, rmsd_per_group,\n",
     "    avg_pixel_displacement, plot_geoptimiser_errormap,\n",
-    "    plot_data_on_detector, pixels_to_image, badpixels_mask\n",
+    "    plot_data_on_detector, pixels_to_image, badpixels_mask,\n",
+    "    get_detector_shape\n",
     ")"
    ]
   },
@@ -52,7 +53,7 @@
     "propno = int(xwiz_conf['data']['proposal'])\n",
     "runs = eval(xwiz_conf['data']['runs'])\n",
     "\n",
-    "panels, beam = read_crystfel_geom(geometry_file, indexes={'modno': 1})\n",
+    "panels, beam, mask = read_crystfel_geom(geometry_file, indexes={'modno': 1})\n",
     "\n",
     "panel_columns = panels.columns.tolist() + ['panel']\n",
     "if connected_groups not in panel_columns:\n",
@@ -205,7 +206,8 @@
    "outputs": [],
    "source": [
     "px = badpixels_mask(pe, panels)\n",
-    "msk = pixels_to_image((16, 512, 128), px, 'msk')\n",
+    "shape = get_detector_shape(panels)\n",
+    "msk = pixels_to_image(shape, px, 'msk')\n",
     "fig, ax = plot_data_on_detector(msk, panels, colorbar=False, cmap=plt.cm.copper)"
    ]
   },
diff --git a/src/geomtools/sfx/xwizio.py b/src/geomtools/sfx/xwizio.py
index 1930f5bbb735bf5bde06a3f7c645c36933aeebab..4743a2f8e420d0453a8ad489019376ad19be5e39 100644
--- a/src/geomtools/sfx/xwizio.py
+++ b/src/geomtools/sfx/xwizio.py
@@ -26,7 +26,9 @@ def parse_xwiz_summary(filename):
             else:
                 group[key] = val
 
-        cell_path = xwiz_conf["unit_cell"]["file"]
+        cell_path = xwiz_conf["unit_cell"].get("file_path")
+        if cell_path is None:
+            cell_path = xwiz_conf["unit_cell"].get("file")
         cell_name = os.path.basename(cell_path)
         summary = ""
         for line in f: