WizardLQ’s | 魔法师の小茶馆

Keep moving, never give up. | 锲而不舍,金石可镂.

1619年的辽东战役

万历四十七年,公元1619年的辽东战役,这一战役以努尔哈赤所带领的满族军队以少胜多大败明军结束,并成为明朝军队在东北丧失军力优势的转折点。
战役的起因由努尔哈赤挑起,前一年清太祖攻陷抚顺城,屠戮明军,明朝派遣张承荫讨伐失败。努尔哈赤的要求补偿的条件被明朝拒绝,前者继续侵犯辽东,明朝随招兵募款,战势一触即发。

作者考据,明朝这边总领杨镐所带兵力十万左右,努尔哈赤军队数量在5到六万左右。

杨镐将明军分为北,西,南,东南四路,分别为马林,杜松,李如柏,刘𫄧,战线绵延二百里,没有确定的攻击目标和重点。于3月26日出兵,扬言4月28日对努尔哈赤以四十七万兵力攻之,显然是虚张声势,努尔哈赤不吃这一套。4月4日,万历收到出兵之奏本。4月13日,努尔哈赤才得知杨镐出兵的消息。从这一点看似明军占据了主动性。然而,四路大军分别行动后,杨镐只能在辽宁司令部中静候各路战况,直到4月14日,西路杜松部队失败主帅战死。

杜松部队全军覆没的经过如下,14日午前其率兵度过浑河,奇怪的是其将火炮辎重置于河对岸。渡河之后,开战告捷,连克两道栅,生擒满洲兵十四人。遂深入,竟有满洲兵在此埋伏,足有三万之众。杜松部队想占领高地,扭转局势,没想到再次中伏,无人生还。

杜松渡河日期乃是完全符合杨镐所赐的军令时间。抛弃火器,乃是其麾下车营参将所为,原因在于浑河水深流急,渡过很困难,且观察到南面有满人骑兵小分队。杜松低估了车马渡河的困难,匆匆挺近。部下也没有上报这些困难以及给出提议,于是在杜松和满人血刃之时,后方火炮和支援却按兵不动。

明朝官员将此次失败归结于杜松本人的“贪功邀赏”。但作者考证,总领杨镐没有亲赴现场,也没有派遣使者搜集情报,至关重要的一点,其本人不知道满军主力就位于杜松部队途经的线路上。

而且非常讽刺的是,北方马林军就在距离杜松部队不远处的尚间崖附近,在杜松部队和敌人死战时居然不知也没能驰援。14日晚,马林收到西线杜松战败的消息,15日早晨其率领主力四万人撤到之前宿营地,利用先前构筑好的壕沟等,布置好方阵准备反击,监军潘宗颜带一万后卫营断后。书中说还有一万明军(并未讲清是什么部队),被努尔哈赤组织一千骑兵冲击下消灭。最后又和大贝勒一起和马林主力会战,以总共不足一万兵力将明军四万人击溃。潘宗颜在菲芬山被满洲军队尽数消灭。

仅仅又一天时间,明军北线也完全溃散,文官将罪责归咎于马林,马林于三个月后开原之战中战死。

得知西线和北线失利,杨镐召回南线李如柏部队,很迷的是东南线的刘𫄧并未收到停战撤退的命令。李如柏及时撤回,没有受到损失,但受到指控,说他和敌人勾结。


刘𫄧部队三万人,其中有一万三千人的朝鲜部队。朝鲜部队(其中约有五千炮兵),其都元帅姜弘立受命于刘綖,据称受迫参战,且明朝不提供粮饷。刘綖二十年前抗倭时被派驻到朝鲜,据传不得人心并且和杨镐不和(这也有可能是其没有收到停战命令的原因)。

4月20日,满军几路和刘𫄧主力激战,刘綖被俘。南部明、朝联军两万人以火器抵抗,后受到风力影响,姜弘立率朝鲜步兵投降,并将明军残部交给满洲人。

有人说1619年的辽东战争失败几乎是必然的。“匆匆召集,缺乏指导,未经训练,供应不足,装备低劣,纪律松懈”,“个人应尽最大努力,用自己的才干和自我牺牲掩盖制度上的弱点”。

三个月后,努尔哈赤接连兵围开原,攻下铁岭。杨镐被逮论死。总而言之,辽东战役让努尔哈赤清军对明朝军队的战斗力和制度有了清楚认识,也对明朝的国运产生了深远的影响。

黄仁宇其大历史观

本章介绍了作者【美国】黄仁宇写此书的背景,其并非典型的商业性的或者学术性的书。在我看来有点像是纪传体断代史。后文的介绍中说,黄仁宇小时候读书受太史公司马迁影像甚多,印证了笔者的看法。

作者说此书只代表作者的一部分意见,不代表全部的历史观点。这一部分意见,笔者感觉在书中字里行间反反复复出现和批评,那就是“道德代替法律”。作者说:“检察中国的官僚制度,不是否认中国的全部文化”,其“大历史”观,即从世界的空间维度以及长时间的时间维度上看待历史,但从后文可以看出,作者的历史观建立在“地理气候决定论”的基础上。


作者指出,中国立国向来以贫农及小自耕农的经济立场作为基础,农村内部情况复杂,从数量上和关系上理清并管理,必然要经过很长时间以及很大的代价。

后文作者对本书中的观点进一步阐明,作者在书中对于道德,态度是这样的,道德并非万能,道德不能代替技术,也不能代替道德,作者也并未说道德可以完全不要,而是要求能够使用法律和计数解决的问题,应当将道德评判排后。“狭义的道德观念基于狭义的宇宙观,就是武断地说出世界的根源如是,它的结局也必是如此”。

此外作者就“一国两制”,自己的宇宙观作了简要概括。

VTK 从DICOM数据中重建三维

VTK读入DICOM数据

图像来自www.dicomlibrarymedDream

DICOM等医学影像本来将图像存储为多维的数组,因此从中恢复成3维的模型,而不是按照层次切片来查看是很自然的需求。

参考

[1] https://vtk.org/Wiki/VTK/Examples/Cxx/IO/ReadDICOMSeries

[2] https://lorensen.github.io/VTKExamples/site/Cxx/Medical/MedicalDemo4/

附录

https://lorensen.github.io/VTKExamples/site/Java/Medical/MedicalDemo4/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import vtk.vtkNamedColors;
import vtk.vtkNativeLibrary;
import vtk.vtkRenderWindow;
import vtk.vtkRenderWindowInteractor;
import vtk.vtkRenderer;
import vtk.vtkMetaImageReader;
import vtk.vtkCamera;
import vtk.vtkColorTransferFunction;
import vtk.vtkPiecewiseFunction;
import vtk.vtkFixedPointVolumeRayCastMapper;
import vtk.vtkVolumeProperty;
import vtk.vtkVolume;

public class MedicalDemo4
{
// -----------------------------------------------------------------
// Load VTK library and print which library was not properly loaded
static
{
if (!vtkNativeLibrary.LoadAllNativeLibraries())
{
for (vtkNativeLibrary lib : vtkNativeLibrary.values())
{
if (!lib.IsLoaded())
{
System.out.println(lib.GetLibraryName() + " not loaded");
}
}
}
vtkNativeLibrary.DisableOutputWindow(null);
}
// -----------------------------------------------------------------


public static void main(String args[])
{

//parse command line arguments
if (args.length != 1)
{
System.err.println("Usage: java -classpath ... Filename(.mhd) e.g FullHead.mhd");
return;
}
String inputFilename = args[0];

vtkNamedColors colors = new vtkNamedColors();

double Bgcolor[] = new double[4];

colors.GetColor("SteelBlue", Bgcolor);

// Create the renderer, render window and interactor.
vtkRenderer ren = new vtkRenderer();
vtkRenderWindow renWin = new vtkRenderWindow();
renWin.AddRenderer(ren);
vtkRenderWindowInteractor iren = new vtkRenderWindowInteractor();
iren.SetRenderWindow(renWin);

// The following reader is used to read a series of 2D slices (images)
// that compose the volume. The slice dimensions are set, and the
// pixel spacing. The data Endianness must also be specified. The reader
// uses the FilePrefix in combination with the slice number to construct
// filenames using the format FilePrefix.%d. (In this case the FilePrefix
// is the root name of the file: quarter.)

vtkMetaImageReader reader = new vtkMetaImageReader();
reader.SetFileName(inputFilename);

// The volume will be displayed by ray-cast alpha compositing.
// A ray-cast mapper is needed to do the ray-casting.
vtkFixedPointVolumeRayCastMapper volumeMapper = new vtkFixedPointVolumeRayCastMapper();
volumeMapper.SetInputConnection(reader.GetOutputPort());

// The color transfer function maps voxel intensities to colors.
// It is modality-specific, and often anatomy-specific as well.
// The goal is to one color for flesh (between 500 and 1000)
// and another color for bone (1150 and over).
vtkColorTransferFunction volumeColor = new vtkColorTransferFunction();
volumeColor.AddRGBPoint(0, 0.0, 0.0, 0.0);
volumeColor.AddRGBPoint(500, 1.0, 0.5, 0.3);
volumeColor.AddRGBPoint(1000, 1.0, 0.5, 0.3);
volumeColor.AddRGBPoint(1150, 1.0, 1.0, 0.9);

// The opacity transfer function is used to control the opacity
// of different tissue types.
vtkPiecewiseFunction volumeScalarOpacity = new vtkPiecewiseFunction();
volumeScalarOpacity.AddPoint(0, 0.00);
volumeScalarOpacity.AddPoint(500, 0.15);
volumeScalarOpacity.AddPoint(1000, 0.15);
volumeScalarOpacity.AddPoint(1150, 0.85);

// The gradient opacity function is used to decrease the opacity
// in the "flat" regions of the volume while maintaining the opacity
// at the boundaries between tissue types. The gradient is measured
// as the amount by which the intensity changes over unit distance.
// For most medical data, the unit distance is 1mm.
vtkPiecewiseFunction volumeGradientOpacity = new vtkPiecewiseFunction();
volumeGradientOpacity.AddPoint(0, 0.0);
volumeGradientOpacity.AddPoint(90, 0.5);
volumeGradientOpacity.AddPoint(100, 1.0);

// The VolumeProperty attaches the color and opacity functions to the
// volume, and sets other volume properties. The interpolation should
// be set to linear to do a high-quality rendering. The ShadeOn option
// turns on directional lighting, which will usually enhance the
// appearance of the volume and make it look more "3D". However,
// the quality of the shading depends on how accurately the gradient
// of the volume can be calculated, and for noisy data the gradient
// estimation will be very poor. The impact of the shading can be
// decreased by increasing the Ambient coefficient while decreasing
// the Diffuse and Specular coefficient. To increase the impact
// of shading, decrease the Ambient and increase the Diffuse and Specular.
vtkVolumeProperty volumeProperty = new vtkVolumeProperty();
volumeProperty.SetColor(volumeColor);
volumeProperty.SetScalarOpacity(volumeScalarOpacity);
volumeProperty.SetGradientOpacity(volumeGradientOpacity);
volumeProperty.SetInterpolationTypeToLinear();
volumeProperty.ShadeOn();
volumeProperty.SetAmbient(0.4);
volumeProperty.SetDiffuse(0.6);
volumeProperty.SetSpecular(0.2);

// The vtkVolume is a vtkProp3D (like a vtkActor) and controls the position
// and orientation of the volume in world coordinates.
vtkVolume volume = new vtkVolume();
volume.SetMapper(volumeMapper);
volume.SetProperty(volumeProperty);
double c[] = new double[3];
c=volume.GetCenter();

ren.AddViewProp(volume);

// Set up an initial view of the volume. The focal point will be the
// center of the volume, and the camera position will be 400mm to the
// patient's left (which is our right).

vtkCamera camera = new vtkCamera();
camera.SetViewUp (0, 0, -1);
camera.SetPosition (c[0], c[1] - 400, c[2]);
camera.SetFocalPoint (c[0], c[1], c[2]);
camera.Azimuth(30.0);
camera.Elevation(30.0);
camera.Dolly(0.75);

// Actors are added to the renderer. An initial camera view is created.
// The Dolly() method moves the camera towards the FocalPoint, thereby enlarging the image.

ren.SetActiveCamera(camera);

// Set a background color for the renderer and set the size of the
// render window (expressed in pixels).
ren.SetBackground(Bgcolor);

// Note that when camera movement occurs (as it does in the Dolly()
// method), the clipping planes often need adjusting. Clipping planes
// consist of two planes: near and far along the view direction. The
// near plane clips out objects in front of the plane; the far plane
// clips out objects behind the plane. This way only what is drawn
// between the planes is actually rendered.
ren.ResetCameraClippingRange ();

renWin.SetSize(300, 300);
renWin.Render();

iren.Initialize();
iren.Start();
}
}