View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2025.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.xray.parsers;
39  
40  import ffx.crystal.Crystal;
41  
42  import java.io.DataOutputStream;
43  import java.io.File;
44  import java.io.FileOutputStream;
45  import java.nio.ByteBuffer;
46  import java.nio.ByteOrder;
47  import java.util.logging.Level;
48  import java.util.logging.Logger;
49  
50  import static ffx.utilities.TinkerUtils.version;
51  import static java.lang.String.format;
52  import static org.apache.commons.math3.util.FastMath.pow;
53  import static org.apache.commons.math3.util.FastMath.sqrt;
54  
55  /**
56   * CCP4MapWriter class.
57   *
58   * @author Timothy D. Fenn
59   * @see <ul>
60   *     <li><a href="http://www.ccp4.ac.uk/html/maplib.html" target="_blank">CCP4 map format</a>
61   *     <li><a href="http://www.ccp4.ac.uk/dist/html/library.html" target="_blank">CCP4 library
62   *     documentation </a>
63   *     <li><a href="http://www.ccp4.ac.uk/html/maplib.html" target="_blank">CCP4 map format </a>
64   *     <li><a href="http://www.ccp4.ac.uk/dist/html/library.html" target="_blank">CCP4 library
65   *     documentation </a>
66   *     </ul>
67   * @since 1.0
68   */
69  public class CCP4MapWriter {
70  
71    private static final Logger logger = Logger.getLogger(CCP4MapWriter.class.getName());
72    private final String filename;
73    private final Crystal crystal;
74    private final int extx, exty, extz;
75    private final int orix, oriy, oriz, nx, ny, nz;
76    private int stride;
77  
78    /**
79     * construct mapwriter object
80     *
81     * @param extx slices in x
82     * @param exty slices in y
83     * @param extz slices in z
84     * @param crystal {@link Crystal} object
85     * @param filename output filename
86     */
87    public CCP4MapWriter(int extx, int exty, int extz, Crystal crystal, String filename) {
88      this.orix = 0;
89      this.oriy = 0;
90      this.oriz = 0;
91      this.extx = extx;
92      this.exty = exty;
93      this.extz = extz;
94      this.nx = extx;
95      this.ny = exty;
96      this.nz = extz;
97      this.crystal = crystal;
98      this.filename = filename;
99      this.stride = 2;
100   }
101 
102   /**
103    * Constructor for CCP4MapWriter.
104    *
105    * @param orix an int.
106    * @param oriy an int.
107    * @param oriz an int.
108    * @param extx an int.
109    * @param exty an int.
110    * @param extz an int.
111    * @param nx an int.
112    * @param ny an int.
113    * @param nz an int.
114    * @param crystal a {@link ffx.crystal.Crystal} object.
115    * @param filename a {@link java.lang.String} object.
116    */
117   public CCP4MapWriter(
118       int orix, int oriy, int oriz,
119       int extx, int exty, int extz,
120       int nx, int ny, int nz,
121       Crystal crystal, String filename) {
122     this.orix = orix;
123     this.oriy = oriy;
124     this.oriz = oriz;
125     this.extx = extx;
126     this.exty = exty;
127     this.extz = extz;
128     this.nx = nx;
129     this.ny = ny;
130     this.nz = nz;
131     this.crystal = crystal;
132     this.filename = filename;
133     this.stride = 2;
134   }
135 
136   /**
137    * Set the stepping across the array (e.g. 2 if data is separated by 1 space)
138    *
139    * @param stride the step size desired
140    */
141   public void setStride(int stride) {
142     this.stride = stride;
143   }
144 
145   /**
146    * Write data to file (does not normalize).
147    *
148    * @param data map data to write out
149    */
150   public void write(double[] data) {
151     write(data, false);
152   }
153 
154   /**
155    * write data to file, does not normalize
156    *
157    * @param data map data to write out
158    * @param norm Normalize the data by mean/sd.
159    */
160   public void write(double[] data, boolean norm) {
161     double min = Double.POSITIVE_INFINITY;
162     double max = Double.NEGATIVE_INFINITY;
163     double mean = 0.0;
164     double sd = 0.0;
165 
166     int n = 0;
167     for (int k = 0; k < extz; k++) {
168       for (int j = 0; j < exty; j++) {
169         for (int i = 0; i < extx; i++) {
170           int index = stride * (i + extx * (j + exty * k));
171           n++;
172           if (data[index] < min) {
173             min = data[index];
174           }
175           if (data[index] > max) {
176             max = data[index];
177           }
178           mean += (data[index] - mean) / n;
179         }
180       }
181     }
182 
183     n = 0;
184     for (int k = 0; k < extz; k++) {
185       for (int j = 0; j < exty; j++) {
186         for (int i = 0; i < extx; i++) {
187           int index = stride * (i + extx * (j + exty * k));
188           sd += pow(data[index] - mean, 2.0);
189           n++;
190         }
191       }
192     }
193     sd = sqrt(sd / n);
194 
195     if (norm) {
196       if (logger.isLoggable(Level.INFO)) {
197         StringBuilder sb = new StringBuilder();
198         sb.append("\n Normalizing CCP4 Map");
199         sb.append(format("\n  Min:           %8.4f", min));
200         sb.append(format("\n  Max:           %8.4f", max));
201         sb.append(format("\n  Mean:          %8.4f", mean));
202         sb.append(format("\n  Standard Dev.: %8.4f", sd));
203         logger.info(sb.toString());
204       }
205 
206       for (int k = 0; k < extz; k++) {
207         for (int j = 0; j < exty; j++) {
208           for (int i = 0; i < extx; i++) {
209             int index = stride * (i + extx * (j + exty * k));
210             data[index] = (data[index] - mean) / sd;
211           }
212         }
213       }
214       // recurse
215       write(data, false);
216       return;
217     }
218 
219     File file = version(new File(filename));
220     String name = file.getName();
221 
222     try {
223       if (logger.isLoggable(Level.INFO)) {
224         StringBuilder sb = new StringBuilder();
225         sb.append(format("\n Writing CCP4 map file: \"%s\"", name));
226         sb.append(format("\n  Min:           %8.4f", min));
227         sb.append(format("\n  Max:           %8.4f", max));
228         sb.append(format("\n  Mean:          %8.4f", mean));
229         sb.append(format("\n  Standard Dev.: %8.4f", sd));
230         logger.info(sb.toString());
231       }
232 
233       FileOutputStream fos = new FileOutputStream(file);
234       DataOutputStream dos = new DataOutputStream(fos);
235 
236       byte[] bytes = new byte[2048];
237       ByteBuffer bb = ByteBuffer.wrap(bytes);
238       // Set the byte order to the native order of the operating system.
239       bb.order(ByteOrder.nativeOrder());
240 
241       // Header
242       bb.putInt(extx).putInt(exty).putInt(extz);
243       // mode (2 = reals, only one we accept)
244       bb.putInt(2);
245       bb.putInt(orix).putInt(oriy).putInt(oriz);
246       bb.putInt(nx).putInt(ny).putInt(nz);
247 
248       bb.putFloat((float) crystal.a);
249       bb.putFloat((float) crystal.b);
250       bb.putFloat((float) crystal.c);
251       bb.putFloat((float) crystal.alpha);
252       bb.putFloat((float) crystal.beta);
253       bb.putFloat((float) crystal.gamma);
254 
255       bb.putInt(1).putInt(2).putInt(3);
256       bb.putFloat((float) min);
257       bb.putFloat((float) max);
258       bb.putFloat((float) mean);
259 
260       bb.putInt(crystal.spaceGroup.number);
261       // bb.putInt(1);
262 
263       // symmetry bytes - should set this up at some point
264       // imapdata = swap ? ByteSwap.swap(320) : 320;
265       bb.putInt(80);
266       bb.putInt(0);
267 
268       for (int i = 0; i < 12; i++) {
269         bb.putFloat(0.0f);
270       }
271 
272       for (int i = 0; i < 15; i++) {
273         bb.putInt(0);
274       }
275 
276       int offset = 0;
277       dos.write(bytes, offset, 208);
278       bb.rewind();
279 
280       String mapstr = "MAP ";
281       dos.writeBytes(mapstr);
282 
283       // machine code: double, float, int, uchar
284       // 0x4441 for LE, 0x1111 for BE
285       int imapdata;
286       if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) {
287         imapdata = 0x4441;
288       } else {
289         imapdata = 0x1111;
290       }
291       bb.putInt(imapdata);
292       bb.putFloat((float) sd);
293       bb.putInt(1);
294       dos.write(bytes, offset, 12);
295 
296       StringBuilder sb = new StringBuilder();
297       sb.append("map data from ffx");
298       while (sb.length() < 80) {
299         sb.append(" ");
300       }
301       dos.writeBytes(sb.toString());
302 
303       sb = new StringBuilder();
304       while (sb.length() < 80) {
305         sb.append(" ");
306       }
307       for (int i = 0; i < 9; i++) {
308         dos.writeBytes(sb.toString());
309       }
310 
311       sb = new StringBuilder();
312       sb.append("x,y,z");
313       while (sb.length() < 80) {
314         sb.append(" ");
315       }
316       dos.writeBytes(sb.toString());
317 
318       bb.rewind();
319       for (int k = 0; k < extz; k++) {
320         for (int j = 0; j < exty; j++) {
321           for (int i = 0; i < extx; i++) {
322             int index = stride * (i + extx * (j + exty * k));
323             float fmapdata = (float) data[index];
324             bb.putFloat(fmapdata);
325             if (!bb.hasRemaining()) {
326               dos.write(bytes);
327               bb.rewind();
328             }
329           }
330         }
331       }
332       if (bb.position() > 0) {
333         dos.write(bytes);
334         bb.rewind();
335       }
336       dos.close();
337     } catch (Exception e) {
338       String message = "Fatal exception evaluating structure factors.\n";
339       logger.log(Level.SEVERE, message, e);
340     }
341   }
342 }